Skip to content

Commit 87e2ca2

Browse files
committed
NodeFileStorage: Initial commit
- Added NodeFileStorage - Added DevDependency: mock-fs (https://github.com/tschaub/mock-fs) - Modified InMemoryStorage specs to work for both kinds of storage implementations
1 parent 7261943 commit 87e2ca2

File tree

5 files changed

+408
-170
lines changed

5 files changed

+408
-170
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"gulp-wrap-umd": "0.2.1",
4242
"rimraf": "2.5.1",
4343
"source-map-support": "0.4.0",
44+
"mock-fs": "3.6.0",
4445
"tracekit": "0.3.1",
4546
"tslint": "3.3.0",
4647
"tsproject": "1.2.0-rc.4",

src/storage/InMemoryStorage-spec.ts

-169
This file was deleted.

src/storage/NodeFileStorage.ts

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { IStorage } from './IStorage';
2+
import { IStorageItem } from './IStorageItem';
3+
4+
import * as Fs from 'fs';
5+
import * as Path from 'path';
6+
7+
interface IndexEntry {
8+
name: string;
9+
timestamp: number;
10+
}
11+
12+
export class NodeFileStorage implements IStorage {
13+
private directory: string;
14+
private maxItems: number;
15+
private timestamp: number;
16+
private index: IndexEntry[];
17+
private fs: any;
18+
19+
constructor(folder: string, maxItems: number = 20, fs?: any) {
20+
this.directory = Path.resolve(folder);
21+
this.maxItems = maxItems;
22+
this.fs = fs ? fs : Fs;
23+
24+
mkdirParent(this.directory);
25+
this.index = this.createIndex(this.directory);
26+
this.timestamp = this.index.length > 0
27+
? this.index[this.index.length - 1].timestamp
28+
: 0;
29+
}
30+
31+
save(path: string, value: any): boolean {
32+
if (!path || !value) {
33+
return false;
34+
}
35+
36+
this.remove(path);
37+
let entry = { name: path, timestamp: ++this.timestamp };
38+
this.index.push(entry);
39+
let fullPath = this.getFullPath(entry);
40+
let json = JSON.stringify(value);
41+
this.fs.writeFileSync(fullPath, json);
42+
43+
return true;
44+
}
45+
46+
get(path: string): any {
47+
try {
48+
let entry = this.findEntry(path);
49+
if (!entry) {
50+
return null;
51+
}
52+
53+
let fullPath = this.getFullPath(entry);
54+
let json = this.fs.readFileSync(fullPath, 'utf8');
55+
return JSON.parse(json, parseDate);
56+
} catch (e) {
57+
return null;
58+
}
59+
}
60+
61+
getList(searchPattern?: string, limit?: number): IStorageItem[] {
62+
let entries = this.index;
63+
64+
if (searchPattern) {
65+
let regex = new RegExp(searchPattern);
66+
entries = entries.filter(entry => regex.test(entry.name));
67+
}
68+
69+
if (entries.length > this.maxItems) {
70+
entries = entries.slice(entries.length - this.maxItems);
71+
}
72+
73+
if (entries.length > limit) {
74+
entries = entries.slice(0, limit);
75+
}
76+
77+
let items = entries.map(e => this.loadEntry(e));
78+
return items;
79+
}
80+
81+
remove(path: string): void {
82+
try {
83+
let entry = this.findEntry(path);
84+
if (!entry) {
85+
return null;
86+
}
87+
88+
let fullPath = this.getFullPath(entry);
89+
this.fs.unlinkSync(fullPath);
90+
this.removeEntry(entry);
91+
} catch (e) { }
92+
}
93+
94+
private loadEntry(entry: IndexEntry) {
95+
let fullPath = this.getFullPath(entry);
96+
let created = this.fs.statSync(fullPath).birthtime.getTime();
97+
let json = this.fs.readFileSync(fullPath, 'utf8');
98+
let value = JSON.parse(json, parseDate);
99+
return {
100+
created: created,
101+
path: entry.name,
102+
value
103+
};
104+
}
105+
106+
private findEntry(path: string) {
107+
for (let i = this.index.length - 1; i >= 0; i--) {
108+
if (this.index[i].name === path) {
109+
return this.index[i];
110+
}
111+
}
112+
return null;
113+
}
114+
115+
private removeEntry(entry: IndexEntry) {
116+
let i = this.index.indexOf(entry);
117+
if (i > -1) {
118+
this.index.splice(i, 1);
119+
}
120+
}
121+
122+
private getFullPath(entry: IndexEntry) {
123+
let filename = entry.name + '__' + entry.timestamp;
124+
return Path.join(this.directory, filename);
125+
}
126+
127+
private createIndex(path: string) {
128+
let files = this.fs.readdirSync(path);
129+
return files
130+
.map(file => {
131+
let parts = file.split('__');
132+
return {
133+
name: parts[0],
134+
timestamp: parseInt(parts[1], 10)
135+
};
136+
}).sort((a, b) => a.timestamp - b.timestamp);
137+
}
138+
}
139+
140+
function parseDate(key, value) {
141+
let dateRegx = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/g;
142+
if (typeof value === 'string') {
143+
let a = dateRegx.exec(value);
144+
if (a) {
145+
return new Date(value);
146+
}
147+
}
148+
return value;
149+
};
150+
151+
function mkdirParent(dirPath) {
152+
try {
153+
this.fs.mkdirSync(dirPath);
154+
} catch (e) {
155+
if (e.errno === 34) {
156+
mkdirParent(Path.dirname(dirPath));
157+
mkdirParent(dirPath);
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)