1/**
2 * Copyright (c) 2006-2017, JGraph Ltd
3 * Copyright (c) 2006-2017, Gaudenz Alder
4 */
5/**
6 * Constructs a new point for the optional x and y coordinates. If no
7 * coordinates are given, then the default values for <x> and <y> are used.
8 * @constructor
9 * @class Implements a basic 2D point. Known subclassers = {@link mxRectangle}.
10 * @param {number} x X-coordinate of the point.
11 * @param {number} y Y-coordinate of the point.
12 */
13StorageFile = function(ui, data, title)
14{
15	DrawioFile.call(this, ui, data);
16
17	this.title = title;
18};
19
20//Extends mxEventSource
21mxUtils.extend(StorageFile, DrawioFile);
22
23/**
24 * Sets the delay for autosave in milliseconds. Default is 2000.
25 */
26StorageFile.prototype.autosaveDelay = 2000;
27
28/**
29 * Sets the delay for autosave in milliseconds. Default is 20000.
30 */
31StorageFile.prototype.maxAutosaveDelay = 20000;
32
33/**
34 * A differentiator of the stored object type (file or lib)
35 */
36StorageFile.prototype.type = 'F';
37
38/**
39 * Translates this point by the given vector.
40 *
41 * @param {number} dx X-coordinate of the translation.
42 * @param {number} dy Y-coordinate of the translation.
43 */
44StorageFile.prototype.getMode = function()
45{
46	return App.MODE_BROWSER;
47};
48
49/**
50 * Overridden to enable the autosave option in the document properties dialog.
51 */
52StorageFile.prototype.isAutosaveOptional = function()
53{
54	return true;
55};
56
57/**
58 * Translates this point by the given vector.
59 *
60 * @param {number} dx X-coordinate of the translation.
61 * @param {number} dy Y-coordinate of the translation.
62 */
63StorageFile.prototype.getHash = function()
64{
65	return 'L' + encodeURIComponent(this.getTitle());
66};
67
68/**
69 * Translates this point by the given vector.
70 *
71 * @param {number} dx X-coordinate of the translation.
72 * @param {number} dy Y-coordinate of the translation.
73 */
74StorageFile.prototype.getTitle = function()
75{
76	return this.title;
77};
78
79/**
80 * Translates this point by the given vector.
81 *
82 * @param {number} dx X-coordinate of the translation.
83 * @param {number} dy Y-coordinate of the translation.
84 */
85StorageFile.prototype.isRenamable = function()
86{
87	return true;
88};
89
90/**
91 * Translates this point by the given vector.
92 *
93 * @param {number} dx X-coordinate of the translation.
94 * @param {number} dy Y-coordinate of the translation.
95 */
96StorageFile.prototype.save = function(revision, success, error)
97{
98	this.saveAs(this.getTitle(), success, error);
99};
100
101/**
102 * Translates this point by the given vector.
103 *
104 * @param {number} dx X-coordinate of the translation.
105 * @param {number} dy Y-coordinate of the translation.
106 */
107StorageFile.prototype.saveAs = function(title, success, error)
108{
109	DrawioFile.prototype.save.apply(this, arguments);
110	this.saveFile(title, false, success, error);
111};
112
113/**
114 * Translates this point by the given vector.
115 *
116 * @param {number} dx X-coordinate of the translation.
117 * @param {number} dy Y-coordinate of the translation.
118 */
119StorageFile.insertFile = function(ui, title, data, success, error)
120{
121	var createStorageFile = mxUtils.bind(this, function(exists)
122	{
123		var fn = function()
124		{
125			var file = new StorageFile(ui, data, title);
126
127			// Inserts data into local storage
128			file.saveFile(title, false, function()
129			{
130				success(file);
131			}, error);
132		};
133
134		if (exists)
135		{
136			ui.confirm(mxResources.get('replaceIt', [title]), fn, error);
137		}
138		else
139		{
140			fn();
141		}
142	});
143
144	StorageFile.getFileContent(ui, title, function(data)
145	{
146		createStorageFile(data != null);
147	}, function()
148	{
149		createStorageFile(false);
150	});
151};
152
153/**
154 * Translates this point by the given vector.
155 *
156 * @param {number} dx X-coordinate of the translation.
157 * @param {number} dy Y-coordinate of the translation.
158 */
159StorageFile.getFileContent = function(ui, title, success, error)
160{
161	ui.getDatabaseItem(title, function(obj)
162	{
163		success(obj != null? obj.data : null);
164	},
165	mxUtils.bind(this, function()
166	{
167		if (ui.database == null) //fallback to localstorage
168		{
169			ui.getLocalData(title, success);
170		}
171		else if (error != null)
172		{
173			error();
174		}
175	}), 'files');
176};
177
178/**
179 * Translates this point by the given vector.
180 *
181 * @param {number} dx X-coordinate of the translation.
182 * @param {number} dy Y-coordinate of the translation.
183 */
184StorageFile.getFileInfo = function(ui, title, success, error)
185{
186	ui.getDatabaseItem(title, function(obj)
187	{
188		success(obj);
189	},
190	mxUtils.bind(this, function()
191	{
192		if (ui.database == null) //fallback to localstorage
193		{
194			ui.getLocalData(title, function(data)
195			{
196				success(data != null? {title: title} : null);
197			});
198		}
199		else if (error != null)
200		{
201			error();
202		}
203	}), 'filesInfo');
204};
205
206/**
207 * Translates this point by the given vector.
208 *
209 * @param {number} dx X-coordinate of the translation.
210 * @param {number} dy Y-coordinate of the translation.
211 */
212StorageFile.prototype.saveFile = function(title, revision, success, error)
213{
214	if (!this.isEditable())
215	{
216		if (success != null)
217		{
218			success();
219		}
220	}
221	else
222	{
223		var fn = mxUtils.bind(this, function()
224		{
225			if (this.isRenamable())
226			{
227				this.title = title;
228			}
229
230			try
231			{
232				var saveDone = mxUtils.bind(this, function()
233				{
234					this.setModified(this.getShadowModified());
235					this.contentChanged();
236
237					if (success != null)
238					{
239						success();
240					}
241		        });
242
243				this.setShadowModified(false);
244				var data = this.getData();
245
246				this.ui.setDatabaseItem(null, [{
247						title: this.title,
248						size: data.length,
249						lastModified: Date.now(),
250						type: this.type
251					}, {
252						title: this.title,
253						data: data
254					}], saveDone, mxUtils.bind(this, function()
255					{
256						if (this.ui.database == null) //fallback to localstorage
257						{
258							this.ui.setLocalData(this.title, data, saveDone);
259						}
260						else if (error != null)
261						{
262							error();
263						}
264					}), ['filesInfo', 'files']);
265			}
266			catch (e)
267			{
268				if (error != null)
269				{
270					error(e);
271				}
272			}
273		});
274
275		// Checks for trailing dots
276		if (this.isRenamable() && title.charAt(0) == '.' && error != null)
277		{
278			error({message: mxResources.get('invalidName')});
279		}
280		else
281		{
282			StorageFile.getFileInfo(this.ui, title, mxUtils.bind(this, function(data)
283			{
284				if (!this.isRenamable() || this.getTitle() == title || data == null)
285				{
286					fn();
287				}
288				else
289				{
290					this.ui.confirm(mxResources.get('replaceIt', [title]), fn, error);
291				}
292			}), error);
293		}
294	}
295};
296
297/**
298 * Translates this point by the given vector.
299 *
300 * @param {number} dx X-coordinate of the translation.
301 * @param {number} dy Y-coordinate of the translation.
302 */
303StorageFile.prototype.rename = function(title, success, error)
304{
305	var oldTitle = this.getTitle();
306
307	if (oldTitle != title)
308	{
309		StorageFile.getFileInfo(this.ui, title, mxUtils.bind(this, function(data)
310		{
311			var fn = mxUtils.bind(this, function()
312			{
313				this.title = title;
314
315				// Updates the data if the extension has changed
316				if (!this.hasSameExtension(oldTitle, title))
317				{
318					this.setData(this.ui.getFileData());
319				}
320
321				this.saveFile(title, false, mxUtils.bind(this, function()
322				{
323					this.ui.removeLocalData(oldTitle, success);
324				}), error);
325			});
326
327			if (data != null)
328			{
329				this.ui.confirm(mxResources.get('replaceIt', [title]), fn, error);
330			}
331			else
332			{
333				fn();
334			}
335		}), error);
336	}
337	else
338	{
339		success();
340	}
341};
342
343/**
344 * Returns the location as a new object.
345 * @type mx.Point
346 */
347StorageFile.prototype.open = function()
348{
349	DrawioFile.prototype.open.apply(this, arguments);
350
351	// Immediately creates the storage entry
352	this.saveFile(this.getTitle());
353};
354
355/**
356 * Adds the listener for automatically saving the diagram for local changes.
357 */
358StorageFile.prototype.getLatestVersion = function(success, error)
359{
360	StorageFile.getFileContent(this.ui, this.title, mxUtils.bind(this, function(data)
361	{
362		success(new StorageFile(this.ui, data, this.title));
363	}), error);
364};
365
366/**
367 * Stops any pending autosaves and removes all listeners.
368 */
369StorageFile.prototype.destroy = function()
370{
371	DrawioFile.prototype.destroy.apply(this, arguments);
372
373	if (this.storageListener != null)
374	{
375		mxEvent.removeListener(window, 'storage', this.storageListener);
376		this.storageListener = null;
377	}
378};
379
380/**
381 * Translates this point by the given vector.
382 *
383 * @param {number} dx X-coordinate of the translation.
384 * @param {number} dy Y-coordinate of the translation.
385 */
386StorageFile.listLocalStorageFiles = function(type)
387{
388	var filesInfo = [];
389
390	for (var i = 0; i < localStorage.length; i++)
391	{
392		var key = localStorage.key(i);
393		var value = localStorage.getItem(key);
394
395		if (key.length > 0 && key.charAt(0) != '.' && value.length > 0)
396		{
397			var isFile = (type == null || type == 'F') && (value.substring(0, 8) === '<mxfile ' ||
398						value.substring(0, 5) === '<?xml' || value.substring(0, 12) === '<!--[if IE]>');
399			var isLib = (type == null || type == 'L') && (value.substring(0, 11) === '<mxlibrary>');
400
401			if (isFile || isLib)
402			{
403				filesInfo.push({
404					title: key,
405					type: isFile? 'F' : 'L',
406					size: value.length,
407					lastModified: Date.now()
408				});
409			}
410		}
411	}
412
413	return filesInfo;
414};
415
416/**
417 * Translates this point by the given vector.
418 *
419 * @param {number} dx X-coordinate of the translation.
420 * @param {number} dy Y-coordinate of the translation.
421 */
422StorageFile.migrate = function(db)
423{
424	var lsFilesInfo = StorageFile.listLocalStorageFiles();
425	lsFilesInfo.push({title: '.scratchpad', type: 'L'}); //Adding scratchpad also since it is a library (storage file)
426	var tx = db.transaction(['files', 'filesInfo'], 'readwrite');
427	var files = tx.objectStore('files');
428	var filesInfo = tx.objectStore('filesInfo');
429
430	for (var i = 0; i < lsFilesInfo.length; i++)
431	{
432		var lsFileInfo = lsFilesInfo[i];
433		var data = localStorage.getItem(lsFileInfo.title);
434		files.add({
435			title: lsFileInfo.title,
436			data: data
437		});
438		filesInfo.add(lsFileInfo);
439	}
440};
441
442/**
443 * Translates this point by the given vector.
444 *
445 * @param {number} dx X-coordinate of the translation.
446 * @param {number} dy Y-coordinate of the translation.
447 */
448StorageFile.listFiles = function(ui, type, success, error)
449{
450	ui.getDatabaseItems(function(filesInfo)
451	{
452		var files = [];
453
454		if (filesInfo != null)
455		{
456			for (var i = 0; i < filesInfo.length; i++)
457			{
458				if (filesInfo[i].title.charAt(0) != '.' && (type == null || filesInfo[i].type == type))
459				{
460					files.push(filesInfo[i]);
461				}
462			}
463		}
464
465		success(files);
466	}, function()
467	{
468		if (ui.database == null) //fallback to localstorage
469		{
470			success(StorageFile.listLocalStorageFiles(type));
471		}
472		else if (error != null)
473		{
474			error();
475		}
476	}, 'filesInfo');
477};
478
479/**
480 * Translates this point by the given vector.
481 *
482 * @param {number} dx X-coordinate of the translation.
483 * @param {number} dy Y-coordinate of the translation.
484 */
485StorageFile.deleteFile = function(ui, title, success, error)
486{
487	ui.removeDatabaseItem([title, title], success, function()
488	{
489		if (ui.database == null) //fallback to localstorage
490		{
491			localStorage.removeItem(title)
492			success();
493		}
494		else if (error != null)
495		{
496			error();
497		}
498	}, ['files', 'filesInfo']);
499};