1- const path = require ( 'path' )
1+ const { join , dirname } = require ( 'path' )
22const url = require ( 'url' )
33const fs = require ( 'fs' )
44const $rdf = require ( 'rdflib' )
@@ -17,6 +17,7 @@ const parse = require('./utils').parse
1717const fetch = require ( 'node-fetch' )
1818const { promisify } = require ( 'util' )
1919const URI = require ( 'urijs' )
20+ const { lock } = require ( 'proper-lockfile' )
2021
2122const RDF_MIME_TYPES = new Set ( [
2223 'text/turtle' , // .ttl
@@ -75,20 +76,18 @@ class LDP {
7576 } )
7677 }
7778
78- createReadStream ( filename , start , end ) {
79- if ( start && end ) {
80- return fs . createReadStream ( filename , { 'start' : start , 'end' : end } )
81- } else {
82- return fs . createReadStream ( filename )
83- }
84- }
85-
8679 async readResource ( url ) {
80+ let releaseLock
8781 try {
8882 const { path } = await this . resourceMapper . mapUrlToFile ( { url } )
89- return await promisify ( fs . readFile ) ( path , { 'encoding' : 'utf8' } )
83+ releaseLock = await lock ( path , { retries : 10 } )
84+ return await promisify ( fs . readFile ) ( path , { encoding : 'utf8' } )
9085 } catch ( err ) {
9186 throw error ( err . status , err . message )
87+ } finally {
88+ if ( releaseLock ) {
89+ await releaseLock ( )
90+ }
9291 }
9392 }
9493
@@ -99,7 +98,7 @@ class LDP {
9998 return this . readResource ( url + this . suffixMeta )
10099 }
101100
102- async listContainer ( filename , reqUri , containerData , hostname ) {
101+ async listContainer ( container , reqUri , containerData , hostname ) {
103102 const resourceGraph = $rdf . graph ( )
104103
105104 try {
@@ -111,14 +110,14 @@ class LDP {
111110
112111 try {
113112 // add container stats
114- await ldpContainer . addContainerStats ( this , reqUri , filename , resourceGraph )
113+ await ldpContainer . addContainerStats ( this , reqUri , container , resourceGraph )
115114 // read directory
116- const files = await ldpContainer . readdir ( filename )
115+ const files = await ldpContainer . readdir ( container )
117116 // iterate through all the files
118117 await Promise . all ( files . map ( async file => {
119118 const { url : fileUri } = await this . resourceMapper . mapFileToUrl (
120- { path : path . join ( filename , file ) , hostname } )
121- return await ldpContainer . addFile ( this , resourceGraph , reqUri , fileUri , filename , file )
119+ { path : join ( container , file ) , hostname } )
120+ return await ldpContainer . addFile ( this , resourceGraph , reqUri , fileUri , container , file )
122121 } ) )
123122 } catch ( err ) {
124123 throw error ( 500 , "Can't list container" )
@@ -154,7 +153,7 @@ class LDP {
154153 let originalPath = resourcePath
155154 if ( container ) {
156155 // Create directory by an LDP PUT to the container's .meta resource
157- resourcePath = path . join ( originalPath , ldp . suffixMeta )
156+ resourcePath = join ( originalPath , ldp . suffixMeta )
158157 if ( originalPath && ! originalPath . endsWith ( '/' ) ) {
159158 originalPath += '/'
160159 }
@@ -226,8 +225,8 @@ class LDP {
226225 }
227226
228227 // Second, create the enclosing directory, if necessary
229- const { path : filePath } = await this . resourceMapper . mapUrlToFile ( { url, contentType, createIfNotExists : true } )
230- const dirName = path . dirname ( filePath )
228+ const { path } = await this . resourceMapper . mapUrlToFile ( { url, contentType, createIfNotExists : true } )
229+ const dirName = dirname ( path )
231230 try {
232231 await promisify ( mkdirp ) ( dirName )
233232 } catch ( err ) {
@@ -237,16 +236,24 @@ class LDP {
237236 }
238237
239238 // Directory created, now write the file
240- return await new Promise ( ( resolve , reject ) => {
241- const file = stream . pipe ( fs . createWriteStream ( filePath ) )
242- file . on ( 'error' , function ( ) {
243- reject ( error ( 500 , 'Error writing data' ) )
244- } )
245- file . on ( 'finish' , function ( ) {
246- debug . handlers ( 'PUT -- Wrote data to: ' + filePath )
247- resolve ( )
239+ let releaseLock
240+ try {
241+ releaseLock = await lock ( path , { retries : 10 , realpath : false } )
242+ return await new Promise ( ( resolve , reject ) => {
243+ const file = stream . pipe ( fs . createWriteStream ( path ) )
244+ file . on ( 'error' , function ( ) {
245+ reject ( error ( 500 , 'Error writing data' ) )
246+ } )
247+ file . on ( 'finish' , function ( ) {
248+ debug . handlers ( 'PUT -- Wrote data to: ' + path )
249+ resolve ( )
250+ } )
248251 } )
249- } )
252+ } finally {
253+ if ( releaseLock ) {
254+ await releaseLock ( )
255+ }
256+ }
250257 }
251258
252259 async exists ( hostname , path , searchIndex = true ) {
@@ -320,82 +327,86 @@ class LDP {
320327 }
321328
322329 async get ( options , searchIndex = true ) {
323- let filename , contentType , stats
330+ let path , contentType , stats
324331 try {
325- ( { path : filename , contentType } = await this . resourceMapper . mapUrlToFile ( { url : options , searchIndex } ) )
326- stats = await this . stat ( filename )
332+ ( { path, contentType } = await this . resourceMapper . mapUrlToFile ( { url : options , searchIndex } ) )
333+ stats = await this . stat ( path )
327334 } catch ( err ) {
328335 throw error ( 404 , 'Can\'t find file requested: ' + options )
329336 }
330337
331338 // Just return, since resource exists
332339 if ( ! options . includeBody ) {
333- return { ' stream' : stats , ' contentType' : contentType , ' container' : stats . isDirectory ( ) }
340+ return { stream : stats , contentType, container : stats . isDirectory ( ) }
334341 }
335342
336343 // Found a container
337344 if ( stats . isDirectory ( ) ) {
338345 const { url : absContainerUri } = await this . resourceMapper
339- . mapFileToUrl ( { path : filename , hostname : options . hostname } )
346+ . mapFileToUrl ( { path, hostname : options . hostname } )
340347 const metaFile = await this . readContainerMeta ( absContainerUri )
341348 . catch ( ( ) => '' ) // Default to an empty meta file if it is missing
342349 let data
343350 try {
344- data = await this . listContainer ( filename , absContainerUri , metaFile , options . hostname )
351+ data = await this . listContainer ( path , absContainerUri , metaFile , options . hostname )
345352 } catch ( err ) {
346353 debug . handlers ( 'GET container -- Read error:' + err . message )
347354 throw err
348355 }
349356 const stream = stringToStream ( data )
350357 // TODO 'text/turtle' is fixed, should be contentType instead
351358 // This forces one more translation turtle -> desired
352- return { ' stream' : stream , ' contentType' : 'text/turtle' , ' container' : true }
359+ return { stream, contentType : 'text/turtle' , container : true }
353360 } else {
354- let stream
355- let chunksize
356- let contentRange
361+ let chunksize , contentRange , start , end
357362 if ( options . range ) {
358- const total = fs . statSync ( filename ) . size
363+ const total = fs . statSync ( path ) . size
359364 const parts = options . range . replace ( / b y t e s = / , '' ) . split ( '-' )
360365 const partialstart = parts [ 0 ]
361366 const partialend = parts [ 1 ]
362- const start = parseInt ( partialstart , 10 )
363- const end = partialend ? parseInt ( partialend , 10 ) : total - 1
367+ start = parseInt ( partialstart , 10 )
368+ end = partialend ? parseInt ( partialend , 10 ) : total - 1
364369 chunksize = ( end - start ) + 1
365370 contentRange = 'bytes ' + start + '-' + end + '/' + total
366- stream = this . createReadStream ( filename , start , end )
367- } else {
368- stream = this . createReadStream ( filename )
369371 }
370- return new Promise ( ( resolve , reject ) => {
371- stream
372- . on ( 'error' , function ( err ) {
373- debug . handlers ( `GET -- error reading ${ filename } : ${ err . message } ` )
374- return reject ( error ( err , "Can't read file " + err ) )
375- } )
376- . on ( 'open' , function ( ) {
377- debug . handlers ( `GET -- Reading ${ filename } ` )
378- return resolve ( { stream, contentType, container : false , contentRange, chunksize } )
379- } )
380- } )
372+ let releaseLock
373+ try {
374+ releaseLock = await lock ( path , { retries : 10 } )
375+ return await new Promise ( ( resolve , reject ) => {
376+ const stream = fs . createReadStream ( path , start && end && { start, end} )
377+ stream
378+ . on ( 'error' , function ( err ) {
379+ debug . handlers ( `GET -- error reading ${ path } : ${ err . message } ` )
380+ return reject ( error ( err , "Can't read file " + err ) )
381+ } )
382+ . on ( 'open' , function ( ) {
383+ debug . handlers ( `GET -- Reading ${ path } ` )
384+ return resolve ( { stream, contentType, container : false , contentRange, chunksize } )
385+ } )
386+ } )
387+ } finally {
388+ if ( releaseLock ) {
389+ await releaseLock ( )
390+ }
391+ }
381392 }
382393 }
383394
384395 async delete ( url ) {
385396 // First check if the path points to a valid file
386- let filePath , stats
397+ let path , stats
387398 try {
388- ( { path : filePath } = await this . resourceMapper . mapUrlToFile ( { url } ) )
389- stats = await this . stat ( filePath )
399+ ( { path } = await this . resourceMapper . mapUrlToFile ( { url } ) )
400+ stats = await this . stat ( path )
390401 } catch ( err ) {
391402 throw error ( 404 , "Can't find " + err )
392403 }
393404
394405 // If so, delete the directory or file
395406 if ( stats . isDirectory ( ) ) {
396- return this . deleteContainer ( filePath )
407+ return this . deleteContainer ( path )
397408 } else {
398- return this . deleteResource ( filePath )
409+ return this . deleteResource ( path )
399410 }
400411 }
401412
@@ -425,28 +436,34 @@ class LDP {
425436 }
426437 }
427438
428- async deleteResource ( filename ) {
439+ async deleteResource ( path ) {
440+ let releaseLock
429441 try {
430- return promisify ( fs . unlink ) ( filename )
442+ releaseLock = await lock ( path , { retries : 10 } )
443+ return await promisify ( fs . unlink ) ( path )
431444 } catch ( err ) {
432445 debug . container ( 'DELETE -- unlink() error: ' + err )
433446 throw error ( err , 'Failed to delete resource' )
447+ } finally {
448+ if ( releaseLock ) {
449+ await releaseLock ( )
450+ }
434451 }
435452 }
436453
437454 getAvailablePath ( host , containerURI , { slug = uuid . v1 ( ) , extension } ) {
438- const filename = slug + extension
455+ const path = slug + extension
439456 function ensureNotExists ( self , newPath ) {
440457 // Verify whether the new path already exists
441458 return self . exists ( host , newPath ) . then (
442459 // If it does, generate another one
443460 ( ) => ensureNotExists ( self , URI . joinPaths ( containerURI ,
444- `${ uuid . v1 ( ) . split ( '-' ) [ 0 ] } -${ filename } ` ) . toString ( ) ) ,
461+ `${ uuid . v1 ( ) . split ( '-' ) [ 0 ] } -${ path } ` ) . toString ( ) ) ,
445462 // If not, we found an appropriate path
446463 ( ) => newPath
447464 )
448465 }
449- return ensureNotExists ( this , URI . joinPaths ( containerURI , filename ) . toString ( ) )
466+ return ensureNotExists ( this , URI . joinPaths ( containerURI , path ) . toString ( ) )
450467 }
451468
452469 getTrustedOrigins ( req ) {
0 commit comments