| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| // Metadata is stored in files as serialized to JSON maps. See contents of |
| // example1.fake and example2.fake. |
| |
| // Multiple volumes can be opened at the same time. The key is the |
| // fileSystemId, which is the same as the file's displayPath. |
| // The value is a Volume object. |
| var volumes = {}; |
| |
| // Defines a volume object that contains information about a mounted file |
| // system. |
| function Volume(entry, metadata, opt_openedFiles) { |
| // Used for restoring the opened file entry after resuming the event page. |
| this.entry = entry; |
| |
| // The volume metadata. |
| this.metadata = []; |
| for (var path in metadata) { |
| this.metadata[path] = metadata[path]; |
| // Date object is serialized in JSON as string. |
| this.metadata[path].modificationTime = |
| new Date(metadata[path].modificationTime); |
| } |
| |
| // A map with currently opened files. The key is a requestId value from the |
| // openFileRequested event, and the value is the file path. |
| this.openedFiles = opt_openedFiles ? opt_openedFiles : {}; |
| }; |
| |
| function onUnmountRequested(options, onSuccess, onError) { |
| if (Object.keys(volumes[options.fileSystemId].openedFiles).length != 0) { |
| onError('IN_USE'); |
| return; |
| } |
| |
| chrome.fileSystemProvider.unmount( |
| {fileSystemId: options.fileSystemId}, |
| function() { |
| delete volumes[options.fileSystemId]; |
| saveState(); // Remove volume from local storage state. |
| onSuccess(); |
| }, |
| function() { |
| onError('FAILED'); |
| }); |
| }; |
| |
| function onGetMetadataRequested(options, onSuccess, onError) { |
| restoreState(options.fileSystemId, function () { |
| var entryMetadata = |
| volumes[options.fileSystemId].metadata[options.entryPath]; |
| if (!entryMetadata) |
| error('NOT_FOUND'); |
| else |
| onSuccess(entryMetadata); |
| }, onError); |
| }; |
| |
| function onReadDirectoryRequested(options, onSuccess, onError) { |
| restoreState(options.fileSystemId, function () { |
| var directoryMetadata = |
| volumes[options.fileSystemId].metadata[options.directoryPath]; |
| if (!directoryMetadata) { |
| onError('NOT_FOUND'); |
| return; |
| } |
| if (!directoryMetadata.isDirectory) { |
| onError('NOT_A_DIRECTORY'); |
| return; |
| } |
| |
| // Retrieve directory contents from metadata. |
| var entries = []; |
| for (var entry in volumes[options.fileSystemId].metadata) { |
| // Do not add itself on the list. |
| if (entry == options.directoryPath) |
| continue; |
| // Check if the entry is a child of the requested directory. |
| if (entry.indexOf(options.directoryPath) != 0) |
| continue; |
| // Restrict to direct children only. |
| if (entry.substring(options.directoryPath.length + 1).indexOf('/') != -1) |
| continue; |
| |
| entries.push(volumes[options.fileSystemId].metadata[entry]); |
| } |
| onSuccess(entries, false /* Last call. */); |
| }, onError); |
| }; |
| |
| function onOpenFileRequested(options, onSuccess, onError) { |
| restoreState(options.fileSystemId, function () { |
| if (options.mode != 'READ' || options.create) { |
| onError('INVALID_OPERATION'); |
| } else { |
| volumes[options.fileSystemId].openedFiles[options.requestId] = |
| options.filePath; |
| onSuccess(); |
| } |
| }, onError); |
| }; |
| |
| function onCloseFileRequested(options, onSuccess, onError) { |
| restoreState(options.fileSystemId, function () { |
| if (!volumes[options.fileSystemId].openedFiles[options.openRequestId]) { |
| onError('INVALID_OPERATION'); |
| } else { |
| delete volumes[options.fileSystemId].openedFiles[options.openRequestId]; |
| onSuccess(); |
| } |
| }, onError); |
| }; |
| |
| function onReadFileRequested(options, onSuccess, onError) { |
| restoreState(options.fileSystemId, function () { |
| var filePath = |
| volumes[options.fileSystemId].openedFiles[options.openRequestId]; |
| if (!filePath) { |
| onError('INVALID_OPERATION'); |
| return; |
| } |
| |
| var contents = volumes[options.fileSystemId].metadata[filePath].contents; |
| |
| // Write the contents as ASCII text. |
| var buffer = new ArrayBuffer(options.length); |
| var bufferView = new Uint8Array(buffer); |
| for (var i = 0; i < options.length; i++) { |
| bufferView[i] = contents.charCodeAt(i); |
| } |
| |
| onSuccess(buffer, false /* Last call. */); |
| }, onError); |
| }; |
| |
| // Saves state in case of restarts, event page suspend, crashes, etc. |
| function saveState() { |
| var state = {}; |
| for (var volumeId in volumes) { |
| var entryId = chrome.fileSystem.retainEntry(volumes[volumeId].entry); |
| state[volumeId] = { |
| entryId: entryId, |
| openedFiles: volumes[volumeId].openedFiles |
| }; |
| } |
| chrome.storage.local.set({state: state}); |
| } |
| |
| // Restores metadata for the passed file system ID. |
| function restoreState(fileSystemId, onSuccess, onError) { |
| chrome.storage.local.get(['state'], function(result) { |
| // Check if metadata for the given file system is alread in memory. |
| if (volumes[fileSystemId]) { |
| onSuccess(); |
| return; |
| } |
| |
| chrome.fileSystem.restoreEntry( |
| result.state[fileSystemId].entryId, |
| function(entry) { |
| readMetadataFromFile(entry, |
| function(metadata) { |
| volumes[fileSystemId] = new Volume(entry, metadata, |
| result.state[fileSystemId].openedFiles); |
| onSuccess(); |
| }, onError); |
| }); |
| }); |
| } |
| |
| // Reads metadata from a file and returns it with the onSuccess callback. |
| function readMetadataFromFile(entry, onSuccess, onError) { |
| entry.file(function(file) { |
| var fileReader = new FileReader(); |
| fileReader.onload = function(event) { |
| onSuccess(JSON.parse(event.target.result)); |
| }; |
| |
| fileReader.onerror = function(event) { |
| onError('FAILED'); |
| }; |
| |
| fileReader.readAsText(file); |
| }); |
| } |
| |
| // Event called on opening a file with the extension or mime type |
| // declared in the manifest file. |
| chrome.app.runtime.onLaunched.addListener(function(event) { |
| event.items.forEach(function(item) { |
| readMetadataFromFile(item.entry, |
| function(metadata) { |
| // Mount the volume and save its information in local storage |
| // in order to be able to recover the metadata in case of |
| // restarts, system crashes, etc. |
| chrome.fileSystem.getDisplayPath(item.entry, function(displayPath) { |
| volumes[displayPath] = new Volume(item.entry, metadata); |
| chrome.fileSystemProvider.mount( |
| {fileSystemId: displayPath, displayName: item.entry.name}, |
| function() { saveState(); }, |
| function() { console.error('Failed to mount.'); }); |
| }); |
| }, |
| function(error) { |
| console.error(error); |
| }); |
| }); |
| }); |
| |
| // Event called on a profile startup. |
| chrome.runtime.onStartup.addListener(function () { |
| chrome.storage.local.get(['state'], function(result) { |
| // Nothing to change. |
| if (!result.state) |
| return; |
| |
| // Remove files opened before the profile shutdown from the local storage. |
| for (var volumeId in result.state) { |
| result.state[volumeId].openedFiles = {}; |
| } |
| chrome.storage.local.set({state: result.state}); |
| }); |
| }); |
| |
| // Save the state before suspending the event page, so we can resume it |
| // once new events arrive. |
| chrome.runtime.onSuspend.addListener(function() { |
| saveState(); |
| }); |
| |
| chrome.fileSystemProvider.onUnmountRequested.addListener( |
| onUnmountRequested); |
| chrome.fileSystemProvider.onGetMetadataRequested.addListener( |
| onGetMetadataRequested); |
| chrome.fileSystemProvider.onReadDirectoryRequested.addListener( |
| onReadDirectoryRequested); |
| chrome.fileSystemProvider.onOpenFileRequested.addListener( |
| onOpenFileRequested); |
| chrome.fileSystemProvider.onCloseFileRequested.addListener( |
| onCloseFileRequested); |
| chrome.fileSystemProvider.onReadFileRequested.addListener( |
| onReadFileRequested); |