Skip to content

Commit 4ce0138

Browse files
Issue #640 - migrate local browser save to indexeddb
1 parent 0ee6343 commit 4ce0138

File tree

7 files changed

+273
-23
lines changed

7 files changed

+273
-23
lines changed

src/js/app.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@
114114
this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController();
115115
this.canvasBackgroundController.init();
116116

117+
this.indexedDbStorageService = new pskl.service.storage.IndexedDbStorageService(this.piskelController);
118+
this.indexedDbStorageService.init();
119+
117120
this.localStorageService = new pskl.service.storage.LocalStorageService(this.piskelController);
118121
this.localStorageService.init();
119122

src/js/controller/dialogs/BrowseLocalController.js

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
this.localStorageItemTemplate_ = pskl.utils.Template.get('local-storage-item-template');
1212

13-
this.service_ = pskl.app.localStorageService;
13+
this.service_ = pskl.app.indexedDbStorageService;
1414
this.piskelList = $('.local-piskel-list');
1515
this.prevSessionContainer = $('.previous-session');
1616

@@ -36,24 +36,24 @@
3636
};
3737

3838
ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () {
39-
var html = '';
40-
var keys = this.service_.getKeys();
41-
42-
keys.sort(function (k1, k2) {
43-
if (k1.date < k2.date) {return 1;}
44-
if (k1.date > k2.date) {return -1;}
45-
return 0;
46-
});
47-
48-
keys.forEach((function (key) {
49-
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
50-
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
51-
name : key.name,
52-
date : date
39+
this.service_.getKeys().then(function (keys) {
40+
var html = '';
41+
keys.sort(function (k1, k2) {
42+
if (k1.date < k2.date) {return 1;}
43+
if (k1.date > k2.date) {return -1;}
44+
return 0;
5345
});
54-
}).bind(this));
5546

56-
var tableBody_ = this.piskelList.get(0).tBodies[0];
57-
tableBody_.innerHTML = html;
47+
keys.forEach((function (key) {
48+
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
49+
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
50+
name : key.name,
51+
date : date
52+
});
53+
}).bind(this));
54+
55+
var tableBody_ = this.piskelList.get(0).tBodies[0];
56+
tableBody_.innerHTML = html;
57+
}.bind(this));
5858
};
5959
})();

src/js/controller/settings/SaveController.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
this.saveDesktopAsNewButton = document.querySelector('#save-desktop-as-new-button');
3535
this.saveFileDownloadButton = document.querySelector('#save-file-download-button');
3636

37-
this.safeAddEventListener_(this.saveLocalStorageButton, 'click', this.saveToLocalStorage_);
37+
this.safeAddEventListener_(this.saveLocalStorageButton, 'click', this.saveToIndexedDb_);
3838
this.safeAddEventListener_(this.saveGalleryButton, 'click', this.saveToGallery_);
3939
this.safeAddEventListener_(this.saveDesktopButton, 'click', this.saveToDesktop_);
4040
this.safeAddEventListener_(this.saveDesktopAsNewButton, 'click', this.saveToDesktopAsNew_);
@@ -99,7 +99,7 @@
9999
if (pskl.app.isLoggedIn()) {
100100
this.saveToGallery_();
101101
} else {
102-
this.saveToLocalStorage_();
102+
this.saveToIndexedDb_();
103103
}
104104
};
105105

@@ -111,8 +111,8 @@
111111
this.saveTo_('saveToGallery', false);
112112
};
113113

114-
ns.SaveController.prototype.saveToLocalStorage_ = function () {
115-
this.saveTo_('saveToLocalStorage', false);
114+
ns.SaveController.prototype.saveToIndexedDb_ = function () {
115+
this.saveTo_('saveToIndexedDb', false);
116116
};
117117

118118
ns.SaveController.prototype.saveToDesktop_ = function () {
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
(function () {
2+
var ns = $.namespace('pskl.service.storage');
3+
var DB_NAME = 'PiskelDatabase';
4+
var DB_VERSION = 1;
5+
6+
ns.IndexedDbStorageService = function (piskelController) {
7+
this.piskelController = piskelController;
8+
};
9+
10+
ns.IndexedDbStorageService.prototype.init = function () {
11+
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
12+
13+
request.onerror = this.onRequestError_.bind(this);
14+
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
15+
request.onsuccess = this.onRequestSuccess_.bind(this);
16+
};
17+
18+
ns.IndexedDbStorageService.prototype.save = function (piskel) {
19+
var name = piskel.getDescriptor().name;
20+
var description = piskel.getDescriptor().description;
21+
var date = Date.now();
22+
var serialized = pskl.utils.serialization.Serializer.serialize(piskel);
23+
24+
return this.save_(name, description, date, serialized);
25+
};
26+
27+
ns.IndexedDbStorageService.prototype.save_ = function (name, description, date, serialized) {
28+
var deferred = Q.defer();
29+
var objectStore = this.db.transaction(['piskels'], 'readwrite').objectStore('piskels');
30+
31+
var getRequest = objectStore.get(name);
32+
getRequest.onsuccess = function (event) {
33+
console.log('get request successful for name: ' + name);
34+
var data = event.target.result;
35+
if (typeof data !== 'undefined') {
36+
37+
data.serialized = serialized;
38+
data.date = date;
39+
data.description = description;
40+
41+
var putRequest = objectStore.put(data);
42+
putRequest.onerror = function(event) {
43+
console.log('put request failed for name: ' + name);
44+
deferred.reject();
45+
};
46+
putRequest.onsuccess = function(event) {
47+
console.log('put request successful for name: ' + name);
48+
deferred.resolve();
49+
};
50+
} else {
51+
var request = objectStore.add({
52+
name: name,
53+
description: description,
54+
serialized: serialized,
55+
date: date
56+
});
57+
58+
request.onerror = function(event) {
59+
console.log('Failed to save a piskel');
60+
deferred.reject();
61+
};
62+
request.onsuccess = function(event) {
63+
console.log('Successfully saved a piskel');
64+
deferred.resolve();
65+
};
66+
}
67+
};
68+
69+
getRequest.onerror = function () {
70+
console.log('get request failed for name: ' + name);
71+
deferred.reject();
72+
};
73+
74+
return deferred.promise;
75+
};
76+
77+
ns.IndexedDbStorageService.prototype.load = function (name) {
78+
var objectStore = this.db.transaction(['piskels'], 'readwrite').objectStore('piskels');
79+
80+
var getRequest = objectStore.get(name);
81+
getRequest.onsuccess = function (event) {
82+
console.log('get request successful for name: ' + name);
83+
var data = event.target.result;
84+
if (typeof data !== 'undefined') {
85+
var serialized = data.serialized;
86+
pskl.utils.serialization.Deserializer.deserialize(
87+
JSON.parse(serialized),
88+
function (piskel) {
89+
pskl.app.piskelController.setPiskel(piskel);
90+
}
91+
);
92+
} else {
93+
console.log('no local browser save found for name: ' + name);
94+
}
95+
};
96+
97+
getRequest.onerror = function () {
98+
console.log('get request failed for name: ' + name);
99+
};
100+
};
101+
102+
ns.IndexedDbStorageService.prototype.remove = function (name) {
103+
var objectStore = this.db.transaction(['piskels'], 'readwrite').objectStore('piskels');
104+
var deleteRequest = objectStore.delete(name);
105+
deleteRequest.onsuccess = function (event) {
106+
console.log('successfully deleted local browser save for name: ' + name);
107+
};
108+
109+
deleteRequest.onerror = function (event) {
110+
console.log('failed to delete local browser save for name: ' + name);
111+
};
112+
};
113+
114+
ns.IndexedDbStorageService.prototype.list = function () {
115+
var deferred = Q.defer();
116+
var piskels = [];
117+
var objectStore = this.db.transaction(['piskels']).objectStore('piskels');
118+
119+
var cursor = objectStore.openCursor();
120+
cursor.onsuccess = function(event) {
121+
var cursor = event.target.result;
122+
if (cursor) {
123+
piskels.push({
124+
name: cursor.value.name,
125+
date: cursor.value.date,
126+
description: cursor.value.description
127+
});
128+
cursor.continue();
129+
} else {
130+
console.log('Cursor consumed all availabled piskels');
131+
deferred.resolve(piskels);
132+
}
133+
};
134+
135+
cursor.onerror = function () {
136+
deferred.reject();
137+
};
138+
139+
return deferred.promise;
140+
};
141+
142+
ns.IndexedDbStorageService.prototype.getKeys = function () {
143+
return this.list();
144+
};
145+
146+
ns.IndexedDbStorageService.prototype.onRequestError_ = function (event) {
147+
console.log('Failed to initialize IndexedDB, local browser saves will be unavailable.');
148+
};
149+
150+
ns.IndexedDbStorageService.prototype.onRequestSuccess_ = function (event) {
151+
this.db = event.target.result;
152+
console.log('Successfully initialized IndexedDB, local browser saves are available.');
153+
};
154+
155+
ns.IndexedDbStorageService.prototype.onUpgradeNeeded_ = function (event) {
156+
// Set this.db early to allow migration scripts to access it in oncomplete.
157+
this.db = event.target.result;
158+
159+
// Create an object store "piskels" with the autoIncrement flag set as true.
160+
var objectStore = this.db.createObjectStore('piskels', { keyPath : 'name' });
161+
objectStore.transaction.oncomplete = function(event) {
162+
// Migrate existing sprites from LocalStorage
163+
pskl.service.storage.migrate.MigrateLocalStorageToIndexedDb.migrate().then(function () {
164+
console.log('Successfully migrated local storage data to indexed db');
165+
}).catch(function (e) {
166+
console.log('Failed to migrate local storage data to indexed db');
167+
console.error(e);
168+
});
169+
};
170+
};
171+
})();

src/js/service/storage/StorageService.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727
return this.delegateSave_(pskl.app.galleryStorageService, piskel);
2828
};
2929

30+
// @deprecated, use saveToIndexedDb unless indexedDb is not available.
3031
ns.StorageService.prototype.saveToLocalStorage = function (piskel) {
3132
return this.delegateSave_(pskl.app.localStorageService, piskel);
3233
};
3334

35+
ns.StorageService.prototype.saveToIndexedDb = function (piskel) {
36+
return this.delegateSave_(pskl.app.indexedDbStorageService, piskel);
37+
};
38+
3439
ns.StorageService.prototype.saveToFileDownload = function (piskel) {
3540
return this.delegateSave_(pskl.app.fileDownloadStorageService, piskel);
3641
};
@@ -67,7 +72,7 @@
6772
// wrap in timeout in order to start saving only after event.preventDefault
6873
// has been done
6974
window.setTimeout(function () {
70-
this.saveToLocalStorage(this.piskelController.getPiskel());
75+
this.saveToIndexedDb(this.piskelController.getPiskel());
7176
}.bind(this), 0);
7277
}
7378
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
(function () {
2+
var ns = $.namespace('pskl.service.storage.migrate');
3+
4+
// Simple migration helper to move local storage saves to indexed db.
5+
ns.MigrateLocalStorageToIndexedDb = {};
6+
7+
ns.MigrateLocalStorageToIndexedDb.migrate = function () {
8+
var deferred = Q.defer();
9+
10+
var localStorageService = pskl.app.localStorageService;
11+
var indexedDbStorageService = pskl.app.indexedDbStorageService;
12+
13+
var localStorageKeys = localStorageService.getKeys();
14+
var migrationData = localStorageKeys.map(function (key) {
15+
return {
16+
name: key.name,
17+
description: key.description,
18+
date: key.date,
19+
serialized: localStorageService.getPiskel(key.name)
20+
};
21+
});
22+
23+
// Define the sequential migration process.
24+
// Wait for each sprite to be saved before saving the next one.
25+
var success = true;
26+
var migrateSprite = function (index) {
27+
var data = migrationData[index];
28+
if (!data) {
29+
console.log('Data migration from local storage to indexed db finished.');
30+
if (success) {
31+
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels();
32+
}
33+
34+
deferred.resolve();
35+
return;
36+
}
37+
indexedDbStorageService.save_(
38+
data.name,
39+
data.description,
40+
data.date,
41+
data.serialized
42+
).then(function () {
43+
migrateSprite(index + 1);
44+
}).catch(function (e) {
45+
var success = false;
46+
console.error('Failed to migrate local storage sprite for name: ' + data.name);
47+
migrateSprite(index + 1);
48+
});
49+
};
50+
51+
// Start the migration.
52+
migrateSprite(0);
53+
54+
return deferred.promise;
55+
};
56+
57+
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels = function () {
58+
var localStorageKeys = pskl.app.localStorageService.getKeys();
59+
60+
// Remove all sprites.
61+
localStorageKeys.forEach(function (key) {
62+
window.localStorage.removeItem('piskel.' + key.name);
63+
});
64+
65+
// Remove keys.
66+
window.localStorage.removeItem('piskel.keys');
67+
};
68+
69+
})();

src/piskel-script-list.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,10 @@
168168
"js/widgets/Wizard.js",
169169

170170
// Services
171+
"js/service/storage/migrate/MigrateLocalStorageToIndexedDb.js",
171172
"js/service/storage/StorageService.js",
172173
"js/service/storage/FileDownloadStorageService.js",
174+
"js/service/storage/IndexedDbStorageService.js",
173175
"js/service/storage/LocalStorageService.js",
174176
"js/service/storage/GalleryStorageService.js",
175177
"js/service/storage/DesktopStorageService.js",

0 commit comments

Comments
 (0)