UNPKG

5.17 kBPlain TextView Raw
1import { Bucket as GoogleBucket, File as GoogleFile, GetFilesOptions, Storage as GoogleStorage } from '@google-cloud/storage';
2import { Readable, Writable } from "stream";
3import { Driver, ListCloudFilesOptions } from "./driver";
4import { BucketFile, BucketType } from './types';
5import micromatch = require('micromatch');
6
7export async function getGsDriver(cfg: GsDriverCfg) {
8 // TODO: valid cfg
9 const googleStorageConf = {
10 projectId: cfg.project_id,
11 credentials: {
12 client_email: cfg.client_email,
13 private_key: cfg.private_key
14 }
15 }
16 const storage = new GoogleStorage(googleStorageConf);
17 const googleBucket = storage.bucket(cfg.bucketName);
18 return new GcpDriver(googleBucket);
19}
20
21export interface GsDriverCfg {
22 bucketName: string;
23 project_id: string;
24 client_email: string;
25 private_key: string;
26}
27
28class GcpDriver implements Driver<GoogleFile> {
29 readonly googleBucket: GoogleBucket;
30
31 get type(): BucketType {
32 return 'gs'
33 }
34
35 get name(): string {
36 return this.googleBucket.name
37 }
38
39 constructor(googleBucket: GoogleBucket) {
40 this.googleBucket = googleBucket;
41 }
42
43 toFile(googleFile: GoogleFile): Omit<BucketFile, 'bucket'> {
44 if (!googleFile) {
45 throw new Error(`No googleFile`);
46 }
47 const size = (googleFile.metadata.size) ? Number(googleFile.metadata.size) : undefined;
48 return {
49 path: googleFile.name,
50 size,
51 updated: googleFile.metadata.updated,
52 contentType: googleFile.metadata.contentType
53 }
54
55 }
56
57 getPath(obj: GoogleFile) {
58 return obj.name;
59 }
60
61 async exists(path: string): Promise<boolean> {
62 // Note: note gcp has specific file.exists method
63 const result = await this.googleBucket.file(path).exists();
64 return result[0];
65 }
66
67 async getCloudFile(path: string): Promise<GoogleFile | null> {
68 const googleFile = this.googleBucket.file(path);
69 try {
70 return (await googleFile.get())[0];
71 } catch (ex) {
72 // not found return null, as per getFile design.
73 if (ex.code === 404) {
74 return null;
75 }
76 // otherwise, propagate exception
77 else {
78 throw ex;
79 }
80 }
81 }
82
83 /**
84 *
85 * @param path prefix path or glob (the string before the first '*' will be used as prefix)
86 */
87 async listCloudFiles(opts: ListCloudFilesOptions): Promise<GoogleFile[]> {
88 const { prefix, glob, delimiter } = opts;
89
90 // build the query options and perform the re quest
91 let baseQuery: GetFilesOptions = { autoPaginate: true };
92 if (delimiter === true) {
93 baseQuery.delimiter = '/';
94 }
95 let getListOpts = (prefix) ? { ...baseQuery, prefix } : baseQuery;
96 const result = await this.googleBucket.getFiles(getListOpts);
97 let gfList = result[0] || [];
98
99
100 // if glob, filter the data further
101 let files: GoogleFile[] = (!glob) ? gfList : gfList.filter(gf => micromatch.isMatch(gf.name, glob));
102
103 return files;
104 }
105
106 async downloadCloudFile(cf: GoogleFile, localPath: string): Promise<void> {
107 await cf.download({ destination: localPath });
108 }
109
110 async uploadCloudFile(localFilePath: string, remoteFilePath: string, contentType?: string): Promise<GoogleFile> {
111 const googleBucket = this.googleBucket;
112 const googleFile = (await googleBucket.upload(localFilePath, { destination: remoteFilePath, contentType }))[0];
113 return googleFile;
114 }
115
116 async copyCloudFile(cf: GoogleFile, dest: BucketFile): Promise<void> {
117 if (dest.bucket.googleBucket == null) {
118 throw new Error(`destBucket type ${dest.bucket.type} does not match source bucket type ${this.type}. For now, cross bucket type copy not supported.`)
119 }
120 const destGoogleBucket = dest.bucket.googleBucket;
121
122 const destFile = destGoogleBucket.file(dest.path);
123 await cf.copy(destFile);
124 }
125
126 async deleteCloudFile(path: string): Promise<boolean> {
127 const googleFile = this.googleBucket.file(path);
128
129 if (googleFile) {
130 try {
131 await googleFile.delete();
132 } catch (ex) {
133 // if not found, just return false.
134 if (ex.code === 404) {
135 process.stdout.write(` - Skipped (object not found)\n`);
136 return false;
137 } else {
138 process.stdout.write(` - FAILED - ABORT - Cause ${ex}\n`);
139 throw ex;
140 }
141 }
142
143 // TODO: Probably needs to return true only if deleted.
144 return true;
145 } else {
146 return false;
147 }
148 }
149
150
151
152 async downloadAsText(path: string): Promise<string> {
153 const googleFile = this.googleBucket.file(path);
154 const buffer = await googleFile.download();
155 return buffer.toString();
156 }
157
158
159 async uploadCloudContent(path: string, content: string, contentType?: string): Promise<void> {
160 const googleFile = this.googleBucket.file(path);
161 const uploadReadable = new Readable();
162 return new Promise(function (resolve, reject) {
163 uploadReadable
164 .pipe(googleFile.createWriteStream({ contentType }))
165 .on('error', function (err: any) {
166 reject(err);
167 })
168 .on('finish', function () {
169 resolve();
170 });
171 uploadReadable.push(content);
172 uploadReadable.push(null);
173 });
174 }
175 async createReadStream(path: string): Promise<Readable> {
176 const googleFile = this.googleBucket.file(path);
177 return googleFile.createReadStream();
178 }
179
180 async createWriteStream(path: string): Promise<Writable> {
181 const googleFile = this.googleBucket.file(path);
182 return googleFile.createWriteStream();
183 }
184
185
186
187
188
189}
190
191
192
193