Skip to content

Commit 8278b64

Browse files
committed
Refactoring: KeyValueStorageBase
Put code from BrowserStorage and NodeFileStorage into common base class
1 parent 02b14b1 commit 8278b64

File tree

3 files changed

+198
-246
lines changed

3 files changed

+198
-246
lines changed

src/storage/BrowserStorage.ts

Lines changed: 18 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
1-
import { IStorage } from './IStorage';
2-
import { IStorageItem } from './IStorageItem';
1+
import { KeyValueStorageBase } from './KeyValueStorageBase';
32

4-
interface IndexEntry {
5-
name: string;
6-
timestamp: number;
7-
}
8-
9-
export class BrowserStorage implements IStorage {
3+
export class BrowserStorage extends KeyValueStorageBase {
104
private prefix: string;
11-
private maxItems: number;
12-
private timestamp: number;
13-
private index: IndexEntry[];
145

156
static isAvailable(): boolean {
167
try {
@@ -25,138 +16,40 @@ export class BrowserStorage implements IStorage {
2516
}
2617

2718
constructor(prefix: string = 'com.exceptionless.', maxItems: number = 20, fs?: any) {
28-
this.prefix = prefix;
29-
this.maxItems = maxItems;
30-
31-
this.index = this.createIndex();
32-
this.timestamp = this.index.length > 0
33-
? this.index[this.index.length - 1].timestamp
34-
: 0;
35-
}
36-
37-
save(path: string, value: any): boolean {
38-
if (!path || !value) {
39-
return false;
40-
}
41-
42-
this.remove(path);
43-
let entry = { name: path, timestamp: ++this.timestamp };
44-
this.index.push(entry);
45-
let fullPath = this.getFullPath(entry);
46-
let json = JSON.stringify(value);
47-
window.localStorage.setItem(fullPath, json);
48-
49-
return true;
50-
}
51-
52-
get(path: string): any {
53-
try {
54-
let entry = this.findEntry(path);
55-
if (!entry) {
56-
return null;
57-
}
58-
59-
let fullPath = this.getFullPath(entry);
60-
let json = window.localStorage.getItem(fullPath);
61-
return JSON.parse(json, parseDate);
62-
} catch (e) {
63-
return null;
64-
}
65-
}
66-
67-
getList(searchPattern?: string, limit?: number): IStorageItem[] {
68-
let entries = this.index;
19+
super(maxItems);
6920

70-
if (searchPattern) {
71-
let regex = new RegExp(searchPattern);
72-
entries = entries.filter(entry => regex.test(entry.name));
73-
}
74-
75-
if (entries.length > this.maxItems) {
76-
entries = entries.slice(entries.length - this.maxItems);
77-
}
78-
79-
if (entries.length > limit) {
80-
entries = entries.slice(0, limit);
81-
}
82-
83-
let items = entries.map(e => this.loadEntry(e));
84-
return items;
85-
}
86-
87-
remove(path: string): void {
88-
try {
89-
let entry = this.findEntry(path);
90-
if (!entry) {
91-
return null;
92-
}
93-
94-
let fullPath = this.getFullPath(entry);
95-
window.localStorage.removeItem(fullPath);
96-
this.removeEntry(entry);
97-
} catch (e) { }
21+
this.prefix = prefix;
9822
}
9923

100-
private loadEntry(entry: IndexEntry) {
101-
let fullPath = this.getFullPath(entry);
102-
let created = Date.now();
103-
let json = window.localStorage.getItem(fullPath);
104-
let value = JSON.parse(json, parseDate);
105-
return {
106-
created: created,
107-
path: entry.name,
108-
value
109-
};
24+
write(key: string, value: string) {
25+
window.localStorage.setItem(key, value);
11026
}
11127

112-
private findEntry(path: string) {
113-
for (let i = this.index.length - 1; i >= 0; i--) {
114-
if (this.index[i].name === path) {
115-
return this.index[i];
116-
}
117-
}
118-
return null;
28+
read(key: string) {
29+
return window.localStorage.getItem(key);
11930
}
12031

121-
private removeEntry(entry: IndexEntry) {
122-
let i = this.index.indexOf(entry);
123-
if (i > -1) {
124-
this.index.splice(i, 1);
125-
}
32+
readDate(key: string) {
33+
return Date.now();
12634
}
12735

128-
private getFullPath(entry: IndexEntry) {
129-
let filename = this.prefix + entry.name + '__' + entry.timestamp;
130-
return filename;
36+
delete(key: string) {
37+
window.localStorage.removeItem(key);
13138
}
13239

133-
private createIndex() {
40+
getEntries() {
13441
let regex = new RegExp('^' + regExEscape(this.prefix));
13542
let files = Object.keys(window.localStorage)
13643
.filter(f => regex.test(f))
13744
.map(f => f.substr(this.prefix.length));
138-
return files
139-
.map(file => {
140-
let parts = file.split('__');
141-
return {
142-
name: parts[0],
143-
timestamp: parseInt(parts[1], 10)
144-
};
145-
}).sort((a, b) => a.timestamp - b.timestamp);
45+
return files;
46+
}
47+
48+
getKey(entry) {
49+
return this.prefix + super.getKey(entry);
14650
}
14751
}
14852

14953
function regExEscape(value) {
15054
return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
15155
}
152-
153-
function parseDate(key, value) {
154-
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;
155-
if (typeof value === 'string') {
156-
let a = dateRegx.exec(value);
157-
if (a) {
158-
return new Date(value);
159-
}
160-
}
161-
return value;
162-
};

src/storage/KeyValueStorageBase.ts

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

0 commit comments

Comments
 (0)