OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 // Metadata is stored in files as serialized to JSON maps. See contents of | |
8 // example1.fake and example2.fake. | |
9 | |
10 // Multiple volumes can be opened at the same time. The key is the | |
11 // fileSystemId, which is the same as the file's entry.name. | |
12 // The value is a Volume object. | |
13 var volumes = {}; | |
14 | |
15 // Defines a volume object that contains information about mounted devices. | |
mtomasz
2014/07/01 00:40:33
nit: about mounted devices -> about a mounted file
cmihail
2014/07/01 01:27:20
Done.
| |
16 function Volume(entry, metadata, opt_openedFiles) { | |
17 // Used for restoring the opened file entry after resuming the event page. | |
18 this.entry = entry; | |
19 | |
20 // The volume metadata. Date object is serialized in JSON as string. | |
mtomasz
2014/07/01 00:40:33
nit: I think the second comment is confusing in th
cmihail
2014/07/01 01:27:21
Done.
| |
21 this.metadata = []; | |
22 for (var path in metadata) { | |
23 this.metadata[path] = metadata[path]; | |
24 this.metadata[path].modificationTime = | |
25 new Date(metadata[path].modificationTime); | |
26 } | |
27 | |
28 // A map with currently opened files. As key it has requestId of | |
mtomasz
2014/07/01 00:40:32
nit: As key it has requestId... -> The key is a re
cmihail
2014/07/01 01:27:21
Done.
| |
29 // openFileRequested and as a value the file path. | |
30 this.openedFiles = opt_openedFiles ? opt_openedFiles : {}; | |
31 }; | |
32 | |
33 function onUnmountRequested(options, onSuccess, onError) { | |
34 if (Object.keys(volumes[options.fileSystemId].openedFiles).length != 0) { | |
35 onError('IN_USE'); | |
36 return; | |
37 } | |
38 | |
39 chrome.fileSystemProvider.unmount( | |
40 {fileSystemId: options.fileSystemId}, | |
41 function() { | |
42 delete volumes[options.fileSystemId]; | |
43 saveState(); // Remove volume from local storage state. | |
44 onSuccess(); | |
45 }, | |
46 function() { | |
47 onError('FAILED'); | |
48 }); | |
49 }; | |
50 | |
51 function onGetMetadataRequested(options, onSuccess, onError) { | |
52 restoreState(options.fileSystemId, function () { | |
53 var entryMetadata = | |
54 volumes[options.fileSystemId].metadata[options.entryPath]; | |
55 if (!entryMetadata) | |
56 error('NOT_FOUND'); | |
57 else | |
58 onSuccess(entryMetadata); | |
59 }, onError); | |
60 }; | |
61 | |
62 function onReadDirectoryRequested(options, onSuccess, onError) { | |
63 restoreState(options.fileSystemId, function () { | |
64 var directoryMetadata = | |
65 volumes[options.fileSystemId].metadata[options.directoryPath]; | |
66 if (!directoryMetadata) { | |
67 onError('NOT_FOUND'); | |
68 return; | |
69 } | |
70 if (!directoryMetadata.isDirectory) { | |
71 onError('NOT_A_DIRECTORY'); | |
72 return; | |
73 } | |
74 | |
75 // Retrieve directory contents from metadata. | |
76 var entries = []; | |
77 for (var entry in volumes[options.fileSystemId].metadata) { | |
78 // Do not add itself on the list. | |
79 if (entry == options.directoryPath) | |
80 continue; | |
81 // Check if the entry is a child of the requested directory. | |
82 if (entry.indexOf(options.directoryPath) != 0) | |
83 continue; | |
84 // Restrict to direct children only. | |
85 if (entry.substring(options.directoryPath.length + 1).indexOf('/') != -1) | |
86 continue; | |
87 | |
88 entries.push(volumes[options.fileSystemId].metadata[entry]); | |
89 } | |
90 onSuccess(entries, false /* Last call. */); | |
91 }, onError); | |
92 }; | |
93 | |
94 function onOpenFileRequested(options, onSuccess, onError) { | |
95 restoreState(options.fileSystemId, function () { | |
96 if (options.mode != 'READ' || options.create) { | |
97 onError('INVALID_OPERATION'); | |
98 } else { | |
99 volumes[options.fileSystemId].openedFiles[options.requestId] = | |
100 options.filePath; | |
101 onSuccess(); | |
102 } | |
103 }, onError); | |
104 }; | |
105 | |
106 function onCloseFileRequested(options, onSuccess, onError) { | |
107 restoreState(options.fileSystemId, function () { | |
108 if (!volumes[options.fileSystemId].openedFiles[options.openRequestId]) { | |
109 onError('INVALID_OPERATION'); | |
110 } else { | |
111 delete volumes[options.fileSystemId].openedFiles[options.openRequestId]; | |
112 onSuccess(); | |
113 } | |
114 }, onError); | |
115 }; | |
116 | |
117 function onReadFileRequested(options, onSuccess, onError) { | |
118 restoreState(options.fileSystemId, function () { | |
119 var filePath = | |
120 volumes[options.fileSystemId].openedFiles[options.openRequestId]; | |
121 if (!filePath) { | |
122 onError('INVALID_OPERATION'); | |
123 return; | |
124 } | |
125 | |
126 var contents = volumes[options.fileSystemId].metadata[filePath].contents; | |
127 | |
128 // Write the contents as ASCII text. | |
129 var buffer = new ArrayBuffer(options.length); | |
130 var bufferView = new Uint8Array(buffer); | |
131 for (var i = 0; i < options.length; i++) { | |
132 bufferView[i] = contents.charCodeAt(i); | |
133 } | |
134 | |
135 onSuccess(buffer, false /* Last call. */); | |
136 }, onError); | |
137 }; | |
138 | |
139 // Save state in case of restarts, event page suspend, crashes, etc. | |
mtomasz
2014/07/01 00:40:32
nit: Save -> Saves
cmihail
2014/07/01 01:27:21
Done.
| |
140 function saveState() { | |
141 var state = {}; | |
142 for (var volumeId in volumes) { | |
143 var entryId = chrome.fileSystem.retainEntry(volumes[volumeId].entry); | |
144 state[volumeId] = { | |
145 entryId: entryId, | |
146 openedFiles: volumes[volumeId].openedFiles | |
147 }; | |
148 } | |
149 chrome.storage.local.set({state: state}); | |
150 } | |
151 | |
152 // Restore state. In this case the file system is already mounted and | |
mtomasz
2014/07/01 00:40:32
nit: Restore -> Restores. Comments for methods sho
cmihail
2014/07/01 01:27:22
Done.
| |
153 // we only need to obtain the metadata, which is done lazily. | |
mtomasz
2014/07/01 00:40:33
The comment about remounting is confusing. How abo
cmihail
2014/07/01 01:27:21
Done.
| |
154 function restoreState(fileSystemId, onSuccess, onError) { | |
155 chrome.storage.local.get(['state'], function(result) { | |
156 // Check if metadata for the given file system is alread in memory. | |
157 if (volumes[fileSystemId]) { | |
158 onSuccess(); | |
159 return; | |
160 } | |
161 | |
162 chrome.fileSystem.restoreEntry( | |
163 result.state[fileSystemId].entryId, | |
164 function(entry) { | |
165 readMetadataFromFile(entry, | |
166 function(metadata) { | |
167 volumes[fileSystemId] = new Volume(entry, metadata, | |
168 result.state[fileSystemId].openedFiles); | |
169 onSuccess(); | |
170 }, onError); | |
171 }); | |
172 }); | |
173 } | |
174 | |
175 // onSuccess has as parameter the metadata read in JSON format. | |
mtomasz
2014/07/01 00:40:33
The returned metadata is already deserialized, so
cmihail
2014/07/01 01:27:21
Yes. I wasn't clear. I was referring to the fact t
| |
176 function readMetadataFromFile(entry, onSuccess, onError) { | |
177 entry.file(function(file) { | |
178 var fileReader = new FileReader(); | |
179 fileReader.onload = function(event) { | |
180 onSuccess(JSON.parse(event.target.result)); | |
181 }; | |
182 | |
183 fileReader.onerror = function(event) { | |
184 onError('FAILED'); | |
185 }; | |
186 | |
187 fileReader.readAsText(file); | |
188 }); | |
189 } | |
190 | |
191 // Event called on clicking the file with the extension or mime type | |
192 // mentioned in the manifest file. | |
mtomasz
2014/07/01 00:40:32
nit: clicking on the file -> opening the file
nit:
cmihail
2014/07/01 01:27:21
Done.
| |
193 chrome.app.runtime.onLaunched.addListener(function(event) { | |
194 event.items.forEach(function(item) { | |
195 readMetadataFromFile(item.entry, | |
196 function(metadata) { | |
197 // Mount the volume and save its information in local storage | |
198 // in order to be able to recover the metadata in case of | |
199 // restarts, system crashes, etc. | |
200 volumes[item.entry.name] = new Volume(item.entry, metadata); | |
201 chrome.fileSystemProvider.mount( | |
202 {fileSystemId: item.entry.name, displayName: item.entry.name}, | |
mtomasz
2014/07/01 00:40:33
This may fail when two files with the same name ar
cmihail
2014/07/01 01:27:20
Done. For displayName I have kept item.entry.name
| |
203 function() { saveState(); }, | |
204 function() { console.error('Failed to mount.'); }); | |
205 }, | |
206 function(error) { | |
207 console.error(error); | |
208 }); | |
209 }); | |
210 }); | |
211 | |
212 // Event called on a profile startup. | |
213 chrome.runtime.onStartup.addListener(function () { | |
214 chrome.storage.local.get(['state'], function(result) { | |
215 // Nothing to change. | |
216 if (!result.state) | |
217 return; | |
218 | |
219 // Remove files opened before the profile shutdown from the local | |
220 // storage state. | |
mtomasz
2014/07/01 00:40:32
nit: local storage state -> local storage.
cmihail
2014/07/01 01:27:21
Done.
| |
221 for (var volumeId in result.state) { | |
222 result.state[volumeId].openedFiles = {}; | |
223 } | |
224 chrome.storage.local.set({state: result.state}); | |
225 }); | |
226 }); | |
227 | |
228 // Save the state before suspending the event page, so we can resume it | |
229 // once new events arrive. | |
230 chrome.runtime.onSuspend.addListener(function() { | |
231 saveState(); | |
232 }); | |
233 | |
234 chrome.fileSystemProvider.onUnmountRequested.addListener( | |
235 onUnmountRequested); | |
236 chrome.fileSystemProvider.onGetMetadataRequested.addListener( | |
237 onGetMetadataRequested); | |
238 chrome.fileSystemProvider.onReadDirectoryRequested.addListener( | |
239 onReadDirectoryRequested); | |
240 chrome.fileSystemProvider.onOpenFileRequested.addListener( | |
241 onOpenFileRequested); | |
242 chrome.fileSystemProvider.onCloseFileRequested.addListener( | |
243 onCloseFileRequested); | |
244 chrome.fileSystemProvider.onReadFileRequested.addListener( | |
245 onReadFileRequested); | |
OLD | NEW |