1
1
import fs from 'fs'
2
2
import path from 'path'
3
3
import { getFile , getContentType , createContainerAt } from "@inrupt/solid-client"
4
- import { isRemote , isDirectory , FileInfo , ensureDirectoryExistence , fixLocalPath , readRemoteDirectoryRecursively , checkRemoteFileExists , writeErrorString , isDirectoryContents , resourceExists } from '../utils/util' ;
4
+ import { isRemote , isDirectory , FileInfo , ensureDirectoryExistence , fixLocalPath , readRemoteDirectoryRecursively , writeErrorString , isDirectoryContents , resourceExists , getLocalFileLastModified , getRemoteResourceLastModified , compareLastModifiedTimes } from '../utils/util' ;
5
5
import Blob from 'fetch-blob'
6
6
import { requestUserCLIConfirmationDefaultNegative } from '../utils/userInteractions' ;
7
7
import BashlibError from '../utils/errors/BashlibError' ;
@@ -20,10 +20,23 @@ interface SourceOptions {
20
20
isDir : boolean
21
21
}
22
22
23
+ interface FileRetrieval {
24
+ buffer : Buffer ,
25
+ contentType : string ,
26
+ lastModified ?: Date
27
+ }
28
+
29
+ interface ResourceRetrieval {
30
+ blob : any ,
31
+ contentType : string ,
32
+ lastModified ?: Date
33
+ }
34
+
23
35
export interface ICommandOptionsCopy extends ICommandOptions {
24
36
all ?: boolean ,
25
37
override ?: boolean ,
26
38
neverOverride ?: boolean ,
39
+ compareLastModified ?: boolean ,
27
40
}
28
41
29
42
export default async function copy ( src : string , dst : string , options ?: ICommandOptionsCopy ) : Promise < {
@@ -35,6 +48,7 @@ export default async function copy(src: string, dst: string, options?: ICommandO
35
48
commandOptions . all = commandOptions . all || false ;
36
49
commandOptions . override = commandOptions . override || false ;
37
50
commandOptions . neverOverride = commandOptions . neverOverride || false ;
51
+ commandOptions . compareLastModified = commandOptions . compareLastModified || false ; // todo: fix
38
52
39
53
/**************************
40
54
* Preprocess src and dst *
@@ -193,47 +207,53 @@ export default async function copy(src: string, dst: string, options?: ICommandO
193
207
* UTILITY FUNCTIONS *
194
208
*********************/
195
209
196
- async function getLocalSourceFiles ( source : SourceOptions , verbose : boolean , all : boolean , options ?: { logger ?: Logger } ) : Promise < { files : FileInfo [ ] , directories : FileInfo [ ] , aclfiles : FileInfo [ ] } > {
210
+ async function getLocalSourceFiles ( source : SourceOptions , verbose : boolean , all : boolean , options ?: { logger ?: Logger , compareLastModified ?: boolean } ) : Promise < { files : FileInfo [ ] , directories : FileInfo [ ] , aclfiles : FileInfo [ ] } > {
197
211
if ( source . isDir ) {
198
212
let filePathInfos = readLocalDirectoryRecursively ( source . path , undefined , { verbose, all} )
199
213
let files = await Promise . all ( filePathInfos . files . map ( async fileInfo => {
200
214
fileInfo . loadFile = async ( ) => readLocalFile ( fileInfo . absolutePath , verbose , options )
215
+ fileInfo . lastModified = options ?. compareLastModified ? getLocalFileLastModified ( source . path ) : undefined ;
201
216
return fileInfo
202
217
} ) )
203
218
let aclfiles = await Promise . all ( filePathInfos . aclfiles . map ( async fileInfo => {
219
+ fileInfo . lastModified = options ?. compareLastModified ? getLocalFileLastModified ( source . path ) : undefined ;
204
220
fileInfo . loadFile = async ( ) => readLocalFile ( fileInfo . absolutePath , verbose , options )
205
221
return fileInfo
206
222
} ) )
207
223
return { files, aclfiles, directories : filePathInfos . directories }
208
224
} else {
209
225
return { files : [ {
226
+ lastModified : options ?. compareLastModified ? getLocalFileLastModified ( source . path ) : undefined ,
210
227
absolutePath : source . path ,
211
228
relativePath : '' ,
212
229
loadFile : async ( ) => readLocalFile ( source . path , verbose , options )
213
230
} ] , aclfiles : [ ] , directories : [ ] }
214
231
}
215
232
}
216
233
217
- async function getRemoteSourceFiles ( source : SourceOptions , fetch : typeof globalThis . fetch , verbose : boolean , all : boolean , options ?: { logger ?: Logger } ) : Promise < { files : FileInfo [ ] , directories : FileInfo [ ] , aclfiles : FileInfo [ ] } > {
234
+ async function getRemoteSourceFiles ( source : SourceOptions , fetch : typeof globalThis . fetch , verbose : boolean , all : boolean , options ?: { logger ?: Logger , compareLastModified ?: boolean } ) : Promise < { files : FileInfo [ ] , directories : FileInfo [ ] , aclfiles : FileInfo [ ] } > {
218
235
if ( source . isDir ) {
219
236
let discoveredResources = await readRemoteDirectoryRecursively ( source . path , { fetch, verbose, all} )
220
237
221
238
// Filter out files that return errors (e.g no authentication privileges)
222
239
let files = ( await Promise . all ( discoveredResources . files . map ( async fileInfo => {
223
240
fileInfo . loadFile = async ( ) => readRemoteFile ( fileInfo . absolutePath , fetch , verbose , options )
241
+ fileInfo . lastModified = await ( options ?. compareLastModified ? getRemoteResourceLastModified ( source . path , fetch ) : undefined )
224
242
return fileInfo
225
243
} ) ) ) . filter ( f => f ) as FileInfo [ ]
226
244
227
245
let aclfiles : FileInfo [ ] = [ ]
228
246
if ( all ) {
229
247
aclfiles = ( await Promise . all ( discoveredResources . aclfiles . map ( async fileInfo => {
230
248
fileInfo . loadFile = async ( ) => readRemoteFile ( fileInfo . absolutePath , fetch , verbose , options )
249
+ fileInfo . lastModified = await ( options ?. compareLastModified ? getRemoteResourceLastModified ( source . path , fetch ) : undefined )
231
250
return fileInfo
232
251
} ) ) ) . filter ( f => f ) as FileInfo [ ]
233
252
}
234
253
return { files, aclfiles, directories : discoveredResources . directories }
235
254
} else {
236
255
return { files : [ {
256
+ lastModified : await ( options ?. compareLastModified ? getRemoteResourceLastModified ( source . path , fetch ) : undefined ) ,
237
257
absolutePath : source . path ,
238
258
relativePath : '' ,
239
259
loadFile : async ( ) => readRemoteFile ( source . path , fetch , verbose , options )
@@ -242,19 +262,28 @@ async function getRemoteSourceFiles(source: SourceOptions, fetch: typeof globalT
242
262
243
263
}
244
264
245
- function readLocalFile ( path : string , verbose : boolean , options ?: { logger ?: Logger } ) : { buffer : Buffer , contentType : string } {
265
+ function readLocalFile ( path : string , verbose : boolean , options ?: { logger ?: Logger , compareLastModified ?: boolean } ) : FileRetrieval {
246
266
if ( verbose ) ( options ?. logger || console ) . log ( 'Reading local file:' , path )
247
267
const file = fs . readFileSync ( path )
248
- let contentType = path . endsWith ( '.acl' ) || path . endsWith ( '.meta' ) ? 'text/turtle' : path . endsWith ( '.acp' ) ? 'application/ld+json' : mime . lookup ( path )
249
- return { buffer : file , contentType } ;
268
+ const contentType = path . endsWith ( '.acl' ) || path . endsWith ( '.meta' ) ? 'text/turtle' : path . endsWith ( '.acp' ) ? 'application/ld+json' : mime . lookup ( path )
269
+ // if (options?.compareLastModified) {
270
+ // const lastModified = getLocalFileLastModified(path)
271
+ // return { buffer: file, contentType, lastModified };
272
+ // } else {
273
+ return { buffer : file , contentType } ;
274
+ // }
250
275
}
251
276
252
- async function readRemoteFile ( path : string , fetch : any , verbose : boolean , options ?: { logger ?: Logger } ) : Promise < { blob : any , contentType : string } > {
277
+ async function readRemoteFile ( path : string , fetch : any , verbose : boolean , options ?: { logger ?: Logger , compareLastModified ?: boolean } ) : Promise < ResourceRetrieval > {
253
278
if ( verbose ) ( options ?. logger || console ) . log ( 'Reading remote file:' , path )
254
- const file = await getFile ( path , { fetch } )
255
- const contentType = await getContentType ( file ) as string // TODO:: error handling?
256
- return { blob : file as any , contentType } ;
257
-
279
+ const resourceFile = await getFile ( path , { fetch } )
280
+ const contentType = await getContentType ( resourceFile ) as string // TODO:: error handling?
281
+ // if (options?.compareLastModified) {
282
+ // const lastModified = await getRemoteResourceLastModified(path, fetch)
283
+ // return { blob: resourceFile as any, contentType, lastModified };
284
+ // } else {
285
+ return { blob : resourceFile as any , contentType } ;
286
+ // }
258
287
}
259
288
260
289
async function writeLocalDirectory ( path : string , fileInfo : FileInfo , options : ICommandOptionsCopy ) : Promise < any > {
@@ -276,6 +305,11 @@ async function writeLocalFile(resourcePath: string, fileInfo: FileInfo, options:
276
305
ensureDirectoryExistence ( resourcePath ) ;
277
306
278
307
let executeWrite = true
308
+ if ( options . compareLastModified ) {
309
+ const targetResourceLastModified = await getLocalFileLastModified ( resourcePath )
310
+ const decision = await compareLastModifiedTimes ( fileInfo . lastModified , targetResourceLastModified )
311
+ executeWrite = decision . write
312
+ }
279
313
if ( options . neverOverride || ! options . override ) {
280
314
if ( await resourceExists ( resourcePath , options . fetch ) ) {
281
315
if ( options . neverOverride ) {
@@ -285,6 +319,7 @@ async function writeLocalFile(resourcePath: string, fileInfo: FileInfo, options:
285
319
}
286
320
}
287
321
}
322
+
288
323
if ( ! executeWrite ) {
289
324
if ( options . verbose ) ( options . logger || console ) . log ( 'Skipping existing local file:' , resourcePath )
290
325
return undefined ;
@@ -325,7 +360,14 @@ async function writeLocalFile(resourcePath: string, fileInfo: FileInfo, options:
325
360
async function writeRemoteFile ( resourcePath : string , fileInfo : FileInfo , fetch : any , options : ICommandOptionsCopy ) : Promise < string | undefined > {
326
361
resourcePath = resourcePath . split ( '$.' ) [ 0 ] ;
327
362
let executeWrite = true
328
- if ( options . neverOverride || ! options . override ) {
363
+ let executeRequest = true ;
364
+ if ( options . compareLastModified ) {
365
+ const targetResourceLastModified = await getRemoteResourceLastModified ( resourcePath , options . fetch )
366
+ const decision = await compareLastModifiedTimes ( fileInfo . lastModified , targetResourceLastModified )
367
+ executeWrite = decision . write
368
+ executeRequest = decision . request
369
+ }
370
+ if ( ! executeWrite && executeRequest && ( options . neverOverride || ! options . override ) ) {
329
371
if ( await resourceExists ( resourcePath , fetch ) ) {
330
372
if ( options . neverOverride ) {
331
373
executeWrite = false ;
@@ -336,7 +378,7 @@ async function writeRemoteFile(resourcePath: string, fileInfo: FileInfo, fetch:
336
378
}
337
379
338
380
if ( ! executeWrite ) {
339
- if ( options . verbose ) ( options . logger || console ) . log ( 'Skipping existing local file:' , resourcePath )
381
+ if ( options . verbose ) ( options . logger || console ) . log ( 'Skipping existing remote file:' , resourcePath )
340
382
return undefined ;
341
383
}
342
384
0 commit comments