Home | History | Annotate | Download | only in archive
      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);
    248