forked from louischatriot/nedb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstorage.js
executable file
·136 lines (116 loc) · 4.11 KB
/
storage.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* Way data is stored for this database
* For a Node.js/Node Webkit database it's the file system
* For a browser-side database it's localforage which chooses the best option depending on user browser (IndexedDB then WebSQL then localStorage)
*
* This version is the Node.js/Node Webkit version
* It's essentially fs, mkdirp and crash safe write and read functions
*/
var fs = require('fs')
, mkdirp = require('mkdirp')
, async = require('async')
, path = require('path')
, storage = {}
;
storage.exists = fs.exists;
storage.rename = fs.rename;
storage.writeFile = fs.writeFile;
storage.unlink = fs.unlink;
storage.appendFile = fs.appendFile;
storage.readFile = fs.readFile;
storage.mkdirp = mkdirp;
/**
* Explicit name ...
*/
storage.ensureFileDoesntExist = function (file, callback) {
storage.exists(file, function (exists) {
if (!exists) { return callback(null); }
storage.unlink(file, function (err) { return callback(err); });
});
};
/**
* Flush data in OS buffer to storage if corresponding option is set
* @param {String} options.filename
* @param {Boolean} options.isDir Optional, defaults to false
* If options is a string, it is assumed that the flush of the file (not dir) called options was requested
*/
storage.flushToStorage = function (options, callback) {
var filename, flags;
if (typeof options === 'string') {
filename = options;
flags = 'r+';
} else {
filename = options.filename;
flags = options.isDir ? 'r' : 'r+';
}
// Windows can't fsync (FlushFileBuffers) directories. We can live with this as it cannot cause 100% dataloss
// except in the very rare event of the first time database is loaded and a crash happens
if (flags === 'r' && (process.platform === 'win32' || process.platform === 'win64')) { return callback(null); }
fs.open(filename, flags, function (err, fd) {
if (err) { return callback(err); }
fs.fsync(fd, function (errFS) {
fs.close(fd, function (errC) {
if (errFS || errC) {
var e = new Error('Failed to flush to storage');
e.errorOnFsync = errFS;
e.errorOnClose = errC;
return callback(e);
} else {
return callback(null);
}
});
});
});
};
/**
* Fully write or rewrite the datafile, immune to crashes during the write operation (data will not be lost)
* @param {String} filename
* @param {String} data
* @param {Function} cb Optional callback, signature: err
*/
storage.crashSafeWriteFile = function (filename, data, cb) {
var callback = cb || function () {}
, tempFilename = filename + '~';
async.waterfall([
async.apply(storage.flushToStorage, { filename: path.dirname(filename), isDir: true })
, function (cb) {
storage.exists(filename, function (exists) {
if (exists) {
storage.flushToStorage(filename, function (err) { return cb(err); });
} else {
return cb();
}
});
}
, function (cb) {
storage.writeFile(tempFilename, data, function (err) { return cb(err); });
}
, async.apply(storage.flushToStorage, tempFilename)
, function (cb) {
storage.rename(tempFilename, filename, function (err) { return cb(err); });
}
, async.apply(storage.flushToStorage, { filename: path.dirname(filename), isDir: true })
], function (err) { return callback(err); })
};
/**
* Ensure the datafile contains all the data, even if there was a crash during a full file write
* @param {String} filename
* @param {Function} callback signature: err
*/
storage.ensureDatafileIntegrity = function (filename, callback) {
var tempFilename = filename + '~';
storage.exists(filename, function (filenameExists) {
// Write was successful
if (filenameExists) { return callback(null); }
storage.exists(tempFilename, function (oldFilenameExists) {
// New database
if (!oldFilenameExists) {
return storage.writeFile(filename, '', 'utf8', function (err) { callback(err); });
}
// Write failed, use old version
storage.rename(tempFilename, filename, function (err) { return callback(err); });
});
});
};
// Interface
module.exports = storage;