blob: 09458cdf25268712704560fb4d02955f63decfa3 [file] [log] [blame]
// 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);