file-system-blob-store.js
1 'use strict'; 2 3 const fs = require('fs-plus'); 4 const path = require('path'); 5 6 module.exports = class FileSystemBlobStore { 7 static load(directory) { 8 let instance = new FileSystemBlobStore(directory); 9 instance.load(); 10 return instance; 11 } 12 13 constructor(directory) { 14 this.blobFilename = path.join(directory, 'BLOB'); 15 this.blobMapFilename = path.join(directory, 'MAP'); 16 this.lockFilename = path.join(directory, 'LOCK'); 17 this.reset(); 18 } 19 20 reset() { 21 this.inMemoryBlobs = new Map(); 22 this.storedBlob = Buffer.alloc(0); 23 this.storedBlobMap = {}; 24 this.usedKeys = new Set(); 25 } 26 27 load() { 28 if (!fs.existsSync(this.blobMapFilename)) { 29 return; 30 } 31 if (!fs.existsSync(this.blobFilename)) { 32 return; 33 } 34 35 try { 36 this.storedBlob = fs.readFileSync(this.blobFilename); 37 this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)); 38 } catch (e) { 39 this.reset(); 40 } 41 } 42 43 save() { 44 let dump = this.getDump(); 45 let blobToStore = Buffer.concat(dump[0]); 46 let mapToStore = JSON.stringify(dump[1]); 47 48 let acquiredLock = false; 49 try { 50 fs.writeFileSync(this.lockFilename, 'LOCK', { flag: 'wx' }); 51 acquiredLock = true; 52 53 fs.writeFileSync(this.blobFilename, blobToStore); 54 fs.writeFileSync(this.blobMapFilename, mapToStore); 55 } catch (error) { 56 // Swallow the exception silently only if we fail to acquire the lock. 57 if (error.code !== 'EEXIST') { 58 throw error; 59 } 60 } finally { 61 if (acquiredLock) { 62 fs.unlinkSync(this.lockFilename); 63 } 64 } 65 } 66 67 has(key) { 68 return ( 69 this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) 70 ); 71 } 72 73 get(key) { 74 if (this.has(key)) { 75 this.usedKeys.add(key); 76 return this.getFromMemory(key) || this.getFromStorage(key); 77 } 78 } 79 80 set(key, buffer) { 81 this.usedKeys.add(key); 82 return this.inMemoryBlobs.set(key, buffer); 83 } 84 85 delete(key) { 86 this.inMemoryBlobs.delete(key); 87 delete this.storedBlobMap[key]; 88 } 89 90 getFromMemory(key) { 91 return this.inMemoryBlobs.get(key); 92 } 93 94 getFromStorage(key) { 95 if (!this.storedBlobMap[key]) { 96 return; 97 } 98 99 return this.storedBlob.slice.apply( 100 this.storedBlob, 101 this.storedBlobMap[key] 102 ); 103 } 104 105 getDump() { 106 let buffers = []; 107 let blobMap = {}; 108 let currentBufferStart = 0; 109 110 function dump(key, getBufferByKey) { 111 let buffer = getBufferByKey(key); 112 buffers.push(buffer); 113 blobMap[key] = [currentBufferStart, currentBufferStart + buffer.length]; 114 currentBufferStart += buffer.length; 115 } 116 117 for (let key of this.inMemoryBlobs.keys()) { 118 if (this.usedKeys.has(key)) { 119 dump(key, this.getFromMemory.bind(this)); 120 } 121 } 122 123 for (let key of Object.keys(this.storedBlobMap)) { 124 if (!blobMap[key] && this.usedKeys.has(key)) { 125 dump(key, this.getFromStorage.bind(this)); 126 } 127 } 128 129 return [buffers, blobMap]; 130 } 131 };