Skip to content

Instantly share code, notes, and snippets.

@Alex4386
Created October 20, 2020 11:25
Show Gist options
  • Save Alex4386/1cb6b0dc7bdf35f11046f6d6d8a9b3dd to your computer and use it in GitHub Desktop.
Save Alex4386/1cb6b0dc7bdf35f11046f6d6d8a9b3dd to your computer and use it in GitHub Desktop.
TypeScript (a.k.a. AnyScript) Implementation of Google Drive Navigator
/* eslint-disable @typescript-eslint/camelcase */
import { drive_v3 } from "googleapis";
import { GaxiosResponse } from "gaxios";
import mime from "mime";
import fs, { ReadStream } from "fs";
import File from "./file";
import path from "path";
import { escapeSingleQuotes } from "../util";
class Directory extends File {
constructor(drive: drive_v3.Drive, id?: string) {
super(drive, id ? id : "root");
}
/**
* 현재 디렉토리를 기준으로 폴더를 하나 생성합니다
* @param name 파일 이름
*/
public async mkdir(name: string): Promise<Directory|null> {
const folder = await this.drive.files.create(
{
requestBody: {
name,
mimeType: 'application/vnd.google-apps.folder',
parents: [this.id]
},
fields: "id"
});
return (typeof folder.data.id === "string") ? new Directory(this.drive, folder.data.id) : null;
}
/**
* 로컬의 파일을 원격으로 업로드 합니다
*
* @param fileName 올릴 파일 로컬 경로 이름
*/
public async upload(fileName: string, statusReport?: (status: number) => void): Promise<File|null> {
const mimeType = mime.getType(fileName);
const parentDirectories = [this.id];
const uploadName = path.parse(fileName).base;
const uploadSize = fs.statSync(fileName).size;
const file = await this.drive.files.create({
requestBody: {
name: uploadName,
parents: parentDirectories,
},
media: {
mimeType: mimeType ? mimeType : "application/octet-stream",
body: fs.createReadStream(fileName)
},
fields: "id",
}, {
onUploadProgress: (e) => {
if (typeof statusReport !== "undefined") {
if ((e.bytesRead * 100 * 100 / uploadSize) % 1 < 0.1) {
statusReport((e.bytesRead / uploadSize) * 100);
}
}
}
});
return (typeof file.data.id === "string") ? new File(this.drive, file.data.id) : null;
}
/**
* 로컬의 파일을 원격으로 업로드 합니다
*
* @param fileName 올릴 파일 로컬 경로 이름
*/
public async uploadStream(fileName: string, stream: ReadStream): Promise<File|null> {
const mimeType = mime.getType(fileName);
const parentDirectories = [this.id];
const uploadName = path.parse(fileName).base;
const file = await this.drive.files.create({
requestBody: {
name: uploadName,
parents: parentDirectories,
},
media: {
mimeType: mimeType ? mimeType : "application/octet-stream",
body: stream
},
fields: "id",
});
return (typeof file.data.id === "string") ? new File(this.drive, file.data.id) : null;
}
/**
* 현재 디렉토리에 존재하는 파일/폴더를 array로 반환합니다.
*/
public async list(): Promise<Array<File|Directory>> {
let pageToken = undefined;
const contents: Array<File|Directory> = [];
do {
// eslint-disable-next-line no-await-in-loop
const files: GaxiosResponse<drive_v3.Schema$FileList> = await this.drive.files.list({
q: `'${escapeSingleQuotes(this.id)}' in parents`,
fields: "nextPageToken, files(id, name, parents, mimeType)",
pageToken
});
if (typeof files.data.files === "undefined") break;
files.data.files.forEach((file) => {
if (typeof file.id === "undefined" || file.id === null) return;
if (file.mimeType === "application/vnd.google-apps.folder") {
contents.push(new Directory(this.drive, file.id));
} else {
contents.push(new File(this.drive, file.id));
}
});
if (typeof files.data.nextPageToken !== "undefined" && files.data.nextPageToken !== null) {
pageToken = files.data.nextPageToken;
}
} while(typeof pageToken !== "undefined");
return contents;
}
/**
* 현재 폴더에 존재하는 파일을 array로 반환합니다.
*/
public async listFiles(): Promise<Array<File>> {
let pageToken = undefined;
const contents: Array<File> = [];
do {
// eslint-disable-next-line no-await-in-loop
const files: GaxiosResponse<drive_v3.Schema$FileList> = await this.drive.files.list({
q: `'${escapeSingleQuotes(this.id)}' in parents and mimeType != 'application/vnd.google-apps.folder' and trashed = false`,
fields: "nextPageToken, files(id, name, parents, mimeType)",
pageToken
});
if (typeof files.data.files === "undefined") break;
files.data.files.forEach((file) => {
if (typeof file.id === "undefined" || file.id === null) return;
if (file.mimeType !== "application/vnd.google-apps.folder") {
contents.push(new File(this.drive, file.id));
}
});
if (typeof files.data.nextPageToken !== "undefined" && files.data.nextPageToken !== null) {
pageToken = files.data.nextPageToken;
}
} while(typeof pageToken !== "undefined");
return contents;
}
/**
* 현재 폴더에 존재하는 폴더를 array로 반환합니다.
*/
public async listDir(): Promise<Array<Directory>> {
let pageToken = undefined;
const contents: Array<Directory> = [];
do {
// eslint-disable-next-line no-await-in-loop
const files: GaxiosResponse<drive_v3.Schema$FileList> = await this.drive.files.list({
q: `'${escapeSingleQuotes(this.id)}' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false`,
fields: "nextPageToken, files(id, name, parents, mimeType)",
pageToken
});
if (typeof files.data.files === "undefined") break;
files.data.files.forEach((file) => {
if (typeof file.id === "undefined" || file.id === null) return;
if (file.mimeType === "application/vnd.google-apps.folder") {
contents.push(new Directory(this.drive, file.id));
}
});
if (typeof files.data.nextPageToken !== "undefined" && files.data.nextPageToken !== null) {
pageToken = files.data.nextPageToken;
}
} while(typeof pageToken !== "undefined");
return contents;
}
/**
* 현재 폴더에 존재하는 특정한 이름의 파일/폴더를 찾아 array로 반환합니다.
*/
public async find(fileName: string): Promise<Array<File|Directory>> {
let pageToken = undefined;
const contents: Array<File|Directory> = [];
do {
// eslint-disable-next-line no-await-in-loop
const files: GaxiosResponse<drive_v3.Schema$FileList> = await this.drive.files.list({
q: `'${escapeSingleQuotes(this.id)}' in parents and name = '${escapeSingleQuotes(fileName)}' and trashed = false`,
fields: "nextPageToken, files(id, name, parents, mimeType)",
pageToken
});
if (typeof files.data.files === "undefined") break;
files.data.files.forEach((file) => {
if (file.id === undefined || file.id === null) return;
if (file.mimeType === "application/vnd.google-apps.folder") {
contents.push(new Directory(this.drive, file.id));
} else {
contents.push(new File(this.drive, file.id));
}
});
if (typeof files.data.nextPageToken !== "undefined" && files.data.nextPageToken !== null) {
pageToken = files.data.nextPageToken;
}
} while(typeof pageToken !== "undefined");
return contents;
}
/**
* 현재 폴더에 존재하는 특정한 이름의 파일/폴더가 존재하는 지 boolean으로 반환합니다.
*/
public async exists(fileName: string): Promise<boolean> {
const findResults = await this.find(fileName);
return findResults.length > 0;
}
/**
* 현재 폴더에 존재하는 특정한 이름의 파일/폴더를 반환합니다.
*/
public async get(fileName: string): Promise<File|Directory> {
const file = (await this.find(fileName))[0];
return file;
}
/**
* 현재 폴더에 존재하는 특정한 이름의 파일을 반환합니다.
*/
public async getFile(fileName: string): Promise<File> {
const file = (await this.findFile(fileName))[0];
return file;
}
/**
* 현재 폴더에 존재하는 특정한 이름의 폴더를 반환합니다.
*/
public async getDir(fileName: string): Promise<Directory> {
const file = (await this.findDir(fileName))[0];
return file;
}
/**
* 현재 폴더에 존재하는 특정한 파일이름을 가진 파일들을 반환합니다.
* @param fileName 파일이름
*/
public async findFile(fileName: string): Promise<Array<File>> {
let pageToken = undefined;
const contents: Array<File> = [];
do {
// eslint-disable-next-line no-await-in-loop
const files: GaxiosResponse<drive_v3.Schema$FileList> = await this.drive.files.list({
q: `'${escapeSingleQuotes(this.id)}' in parents and name = '${escapeSingleQuotes(fileName)}' and mimeType != 'application/vnd.google-apps.folder' and trashed = false`,
fields: "nextPageToken, files(id, name, parents, mimeType)",
pageToken
});
if (typeof files.data.files === "undefined") break;
files.data.files.forEach((file) => {
if (typeof file.id === "undefined" || file.id === null) return;
if (file.mimeType !== "application/vnd.google-apps.folder") {
contents.push(new File(this.drive, file.id));
}
});
if (typeof files.data.nextPageToken !== "undefined" && files.data.nextPageToken !== null) {
pageToken = files.data.nextPageToken;
}
} while(typeof pageToken !== "undefined");
return contents;
}
/**
* 현재 폴더에 존재하는 특정한 폴더이름을 가진 폴더들을 반환합니다.
* @param fileName 파일이름
*/
public async findDir(fileName: string): Promise<Array<Directory>> {
let pageToken = undefined;
const contents: Array<Directory> = [];
do {
// eslint-disable-next-line no-await-in-loop
const files: GaxiosResponse<drive_v3.Schema$FileList> = await this.drive.files.list({
q: `'${escapeSingleQuotes(this.id)}' in parents and name = '${escapeSingleQuotes(fileName)}' and mimeType = 'application/vnd.google-apps.folder' and trashed = false`,
fields: "nextPageToken, files(id, name, parents, mimeType)",
pageToken
});
if (typeof files.data.files === "undefined") break;
files.data.files.forEach((file) => {
if (typeof file.id === "undefined" || file.id === null) return;
if (file.mimeType === "application/vnd.google-apps.folder") {
contents.push(new Directory(this.drive, file.id));
}
});
if (typeof files.data.nextPageToken !== "undefined" && files.data.nextPageToken !== null) {
pageToken = files.data.nextPageToken;
}
} while(typeof pageToken !== "undefined");
return contents;
}
}
export default Directory;
/* eslint-disable @typescript-eslint/camelcase */ // Due to
import { drive_v3 } from "googleapis";
import fs from "fs";
import Directory from "./directory";
class File {
protected drive: drive_v3.Drive;
public id: string;
constructor(drive: drive_v3.Drive, id: string) {
this.drive = drive;
this.id = id;
}
/**
* 현재 파일/폴더의 상위 디렉토리를 표시합니다.
* 최상단의 있을 경우, 루트 디렉토리가 반환됩니다.
*/
public async parentDirectory(): Promise<Directory|null> {
const folder = await this.drive.files.get(
{
fileId: this.id,
fields: "parents"
});
return (folder.data.parents && typeof folder.data.parents[0] === "string") ? new Directory(this.drive, folder.data.parents[0]) : new Directory(this.drive);
}
/**
* 현재 파일의 이름을 반환합니다.
*/
public async getName(): Promise<string> {
const file = await this.getInfo("name");
return file.name as string;
}
/**
* 현재 파일의 크기를 반환합니다.
*/
public async getSize(): Promise<string> {
const file = await this.getInfo("size");
return file.size as string;
}
/**
* 현재 파일의 정보를 반환합니다
*
* @param fields 받을 정보를 정합니다 ex.`name`, `size`
*/
public async getInfo(fields?: string): Promise<drive_v3.Schema$File> {
const file = await this.drive.files.get({
fileId: this.id,
fields
});
return file.data;
}
/**
* 현재 파일을 로컬로 다운로드 합니다.
*
* @param fileName 다운로드할 위치
*/
public async download(fileName: string): Promise<void> {
// For converting document formats, and for downloading template
// documents, see the method drive.files.export():
// https://developers.google.com/drive/api/v3/manage-downloads
return this.drive.files
.get({fileId: this.id, alt: 'media'}, {responseType: 'stream'})
.then(res => {
return new Promise((resolve) => {
const dest = fs.createWriteStream(fileName);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(res.data as any).on('end', () => {
resolve();
}).pipe(dest);
});
});
}
/**
* 현재 파일/폴더를 지정한 디렉토리로 이동합니다
*
* @param destinationDirectory 이동할 도착지 디렉토리
*/
public async move(destinationDirectory: Directory): Promise<boolean> {
const file = await this.drive.files.get({
fileId: this.id,
fields: 'parents'
});
const parents = file.data.parents;
if (parents === undefined || parents === null) return false;
await this.drive.files.update({
fileId: this.id,
addParents: destinationDirectory.id,
removeParents: parents.join(','),
fields: 'id, parents'
});
return true;
}
/**
* 파일/폴더를 삭제합니다.
*
* @returns 현재 파일/폴더의 상위 폴더를 반환합니다.
*/
public async delete(): Promise<Directory|null> {
const parent = await this.parentDirectory();
await this.drive.files.delete({
fileId: this.id
});
return parent;
}
/**
* 파일/폴더의 이름을 변경합니다. 변경후 file의 id 가 갱신되는 경우, 현 오브젝트에 갱신됩니다.
*
* @returns 성공여부를 반환합니다 (boolean)
*/
public async rename(fileName: string): Promise<boolean> {
const file = await this.drive.files.update({
fileId: this.id,
requestBody: {
name: fileName
}
});
if (typeof file.data.id === "undefined" || file.data.id === null) {
return false;
}
this.id = file.data.id;
return true;
}
}
export default File;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment