11import fs from 'fs'
22import path from 'path'
33import { 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' ;
55import Blob from 'fetch-blob'
66import { requestUserCLIConfirmationDefaultNegative } from '../utils/userInteractions' ;
77import BashlibError from '../utils/errors/BashlibError' ;
@@ -20,10 +20,23 @@ interface SourceOptions {
2020 isDir : boolean
2121}
2222
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+
2335export interface ICommandOptionsCopy extends ICommandOptions {
2436 all ?: boolean ,
2537 override ?: boolean ,
2638 neverOverride ?: boolean ,
39+ compareLastModified ?: boolean ,
2740}
2841
2942export 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
3548 commandOptions . all = commandOptions . all || false ;
3649 commandOptions . override = commandOptions . override || false ;
3750 commandOptions . neverOverride = commandOptions . neverOverride || false ;
51+ commandOptions . compareLastModified = commandOptions . compareLastModified || false ; // todo: fix
3852
3953 /**************************
4054 * Preprocess src and dst *
@@ -193,47 +207,53 @@ export default async function copy(src: string, dst: string, options?: ICommandO
193207 * UTILITY FUNCTIONS *
194208 *********************/
195209
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 [ ] } > {
197211 if ( source . isDir ) {
198212 let filePathInfos = readLocalDirectoryRecursively ( source . path , undefined , { verbose, all} )
199213 let files = await Promise . all ( filePathInfos . files . map ( async fileInfo => {
200214 fileInfo . loadFile = async ( ) => readLocalFile ( fileInfo . absolutePath , verbose , options )
215+ fileInfo . lastModified = options ?. compareLastModified ? getLocalFileLastModified ( source . path ) : undefined ;
201216 return fileInfo
202217 } ) )
203218 let aclfiles = await Promise . all ( filePathInfos . aclfiles . map ( async fileInfo => {
219+ fileInfo . lastModified = options ?. compareLastModified ? getLocalFileLastModified ( source . path ) : undefined ;
204220 fileInfo . loadFile = async ( ) => readLocalFile ( fileInfo . absolutePath , verbose , options )
205221 return fileInfo
206222 } ) )
207223 return { files, aclfiles, directories : filePathInfos . directories }
208224 } else {
209225 return { files : [ {
226+ lastModified : options ?. compareLastModified ? getLocalFileLastModified ( source . path ) : undefined ,
210227 absolutePath : source . path ,
211228 relativePath : '' ,
212229 loadFile : async ( ) => readLocalFile ( source . path , verbose , options )
213230 } ] , aclfiles : [ ] , directories : [ ] }
214231 }
215232}
216233
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 [ ] } > {
218235 if ( source . isDir ) {
219236 let discoveredResources = await readRemoteDirectoryRecursively ( source . path , { fetch, verbose, all} )
220237
221238 // Filter out files that return errors (e.g no authentication privileges)
222239 let files = ( await Promise . all ( discoveredResources . files . map ( async fileInfo => {
223240 fileInfo . loadFile = async ( ) => readRemoteFile ( fileInfo . absolutePath , fetch , verbose , options )
241+ fileInfo . lastModified = await ( options ?. compareLastModified ? getRemoteResourceLastModified ( source . path , fetch ) : undefined )
224242 return fileInfo
225243 } ) ) ) . filter ( f => f ) as FileInfo [ ]
226244
227245 let aclfiles : FileInfo [ ] = [ ]
228246 if ( all ) {
229247 aclfiles = ( await Promise . all ( discoveredResources . aclfiles . map ( async fileInfo => {
230248 fileInfo . loadFile = async ( ) => readRemoteFile ( fileInfo . absolutePath , fetch , verbose , options )
249+ fileInfo . lastModified = await ( options ?. compareLastModified ? getRemoteResourceLastModified ( source . path , fetch ) : undefined )
231250 return fileInfo
232251 } ) ) ) . filter ( f => f ) as FileInfo [ ]
233252 }
234253 return { files, aclfiles, directories : discoveredResources . directories }
235254 } else {
236255 return { files : [ {
256+ lastModified : await ( options ?. compareLastModified ? getRemoteResourceLastModified ( source . path , fetch ) : undefined ) ,
237257 absolutePath : source . path ,
238258 relativePath : '' ,
239259 loadFile : async ( ) => readRemoteFile ( source . path , fetch , verbose , options )
@@ -242,19 +262,28 @@ async function getRemoteSourceFiles(source: SourceOptions, fetch: typeof globalT
242262
243263}
244264
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 {
246266 if ( verbose ) ( options ?. logger || console ) . log ( 'Reading local file:' , path )
247267 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+ // }
250275}
251276
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 > {
253278 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+ // }
258287}
259288
260289async function writeLocalDirectory ( path : string , fileInfo : FileInfo , options : ICommandOptionsCopy ) : Promise < any > {
@@ -276,6 +305,11 @@ async function writeLocalFile(resourcePath: string, fileInfo: FileInfo, options:
276305 ensureDirectoryExistence ( resourcePath ) ;
277306
278307 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+ }
279313 if ( options . neverOverride || ! options . override ) {
280314 if ( await resourceExists ( resourcePath , options . fetch ) ) {
281315 if ( options . neverOverride ) {
@@ -285,6 +319,7 @@ async function writeLocalFile(resourcePath: string, fileInfo: FileInfo, options:
285319 }
286320 }
287321 }
322+
288323 if ( ! executeWrite ) {
289324 if ( options . verbose ) ( options . logger || console ) . log ( 'Skipping existing local file:' , resourcePath )
290325 return undefined ;
@@ -325,7 +360,14 @@ async function writeLocalFile(resourcePath: string, fileInfo: FileInfo, options:
325360async function writeRemoteFile ( resourcePath : string , fileInfo : FileInfo , fetch : any , options : ICommandOptionsCopy ) : Promise < string | undefined > {
326361 resourcePath = resourcePath . split ( '$.' ) [ 0 ] ;
327362 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 ) ) {
329371 if ( await resourceExists ( resourcePath , fetch ) ) {
330372 if ( options . neverOverride ) {
331373 executeWrite = false ;
@@ -336,7 +378,7 @@ async function writeRemoteFile(resourcePath: string, fileInfo: FileInfo, fetch:
336378 }
337379
338380 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 )
340382 return undefined ;
341383 }
342384
0 commit comments