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