blob: 46848f031b479d9821c43a9e0f42b90f005520fc [file] [log] [blame]
<h1>Manage Data</h1>
<p>
Almost every aspect of app development involves some element
of sending or receiving data.
Starting with the basics,
you should use an MVC framework to help you design and implement your app
so that data is completely separate from the app's view on that data
(see <a href="app_frameworks.html">MVC Architecture</a>).
</p>
<p>
You also need to think about how data is handled when your app is offline
(see <a href="offline_apps.html">Offline First</a>).
This doc briefly introduces the storage options
for sending, receiving, and saving data locally;
the remainder of the doc shows you how
to use Chrome's File System and Sync File System APIs
(see also <a href="fileSystem.html">fileSystem API</a> and
<a href="syncFileSystem.html">syncFileSystem API</a>).
</p>
<p class="note">
<b>API Samples: </b>
Want to play with the code?
Check out the
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/filesystem-access">filesystem-access</a>,
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/syncfs-editor">syncfs-editor</a>
and <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/storage">storage</a> samples.
</p>
<h2 id="options">Storage options</h2>
<p>
Packaged apps use many different mechanisms
to send and receive data.
For external data (resources, web pages),
you need to be aware of the
<a href="contentSecurityPolicy.html">Content Security Policy (CSP)</a>.
Similar to Chrome Extensions,
you can use
<a href="app_external.html#external">cross-origin XMLHttpRequests</a>
to communicate with remote servers.
You can also isolate external pages,
so that the rest of your app is secure
(see <a href="app_external.html#webview">Embed external web pages</a>).
</p>
<p>
When saving data locally,
you can use the <a href="storage.html">Chrome Storage API</a>
to save small amounts of string data and
IndexedDB to save structured data.
With IndexedDB, you can persist JavaScript objects
to an object store and use the store's indexes to query data
(to learn more, see HTML5 Rock's
<a href="http://www.html5rocks.com/tutorials/indexeddb/todo/">Simple Todo List Tutorial</a>).
For all other types of data,
like binary data,
use the Filesystem and Sync Filesystem APIs.
</p>
<p>
Chrome's Filesystem and Sync Filesystem APIs extend the
<a href="http://www.html5rocks.com/tutorials/file/filesystem/">HTML5 FileSystem API</a>.
With Chrome's Filesystem API,
apps can create, read, navigate,
and write to a sandboxed section
of the user's local file system.
For example,
a photo-sharing app can use the Filesystem API
to read and write any photos that a user selects.
</p>
<p>
With Chrome's Sync Filesystem API,
apps can save and synchronize data
on a user's Google Drive
so that the same data can be available across different clients.
For example, a
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/syncfs-editor">cloud-backed text editor</a>
app can automatically sync new text files to a user's Google Drive account.
When the user opens the text editor in a new client,
Google Drive pushes new text files to that instance of the text editor.
</p>
<h2 id="filesystem">Using the Chrome Filesystem API</h2>
<h3 id="filesystem-manifest">Adding file system permission</h3>
<p>
To use Chrome's File System API,
you need to add the "fileSystem" permission to the manifest,
so that you can obtain permission from the user
to store persistent data.
<pre>
"permissions": [
"...",
"fileSystem"
]
</pre>
<h3 id="import">User-options for selecting files</h3>
<p>
Users expect to select files
in the same way they always do.
At a minimum,
they expect a 'choose file' button
and standard file-chooser.
If your app makes heavy use of file-handing,
you should also implement drag-and-drop
(see below and also check out
<a href="http://www.html5rocks.com/tutorials/dnd/basics/">Native HTML5 Drag and Drop</a>).
</p>
<h3 id="path">Obtaining the path of a fileEntry</h3>
<p>
To get the full path
of the file the user selected,
<code>fileEntry</code>,
call <code>getDisplayPath()</code>:
</p>
<pre>
function displayPath(fileEntry) {
chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
console.log(path)
});
}
</pre>
<h3 id="drag">Implementing drag-and-drop</h3>
<p>
If you need to implement drag-and-drop selection,
the drag-and-drop file controller
(<code>dnd.js</code>) in the
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/filesystem-access">filesystem-access</a>
sample is a good starting point.
The controller creates a file entry
from a <code>DataTransferItem</code>
via drag-and-drop.
In this example,
the <code>fileEntry</code> is set
to the first dropped item.
</p>
<pre>
var dnd = new DnDFileController('body', function(data) {
var fileEntry = data.items[0].webkitGetAsEntry();
displayPath(fileEntry);
});
</pre>
<h3 id="read">Reading a file</h3>
<p>
The following code opens the file (read-only) and
reads it as text using a <code>FileReader</code> object.
If the file doesn't exist, an error is thrown.
</p>
<pre>
var chosenFileEntry = null;
chooseFileButton.addEventListener('click', function(e) {
chrome.fileSystem.chooseEntry({type: 'openFile'}, function(readOnlyEntry) {
readOnlyEntry.file(function(file) {
var reader = new FileReader();
reader.onerror = errorHandler;
reader.onloadend = function(e) {
console.log(e.target.result);
};
reader.readAsText(file);
});
});
});
</pre>
<h3 id="write">Writing a file</h3>
<p>
The two common use-cases
for writing a file are "Save" and "Save as".
The following code creates a
<code>writableEntry</code>
from the read-only <code>chosenFileEntry</code> and
writes the selected file to it.
</p>
<pre>
chrome.fileSystem.getWritableEntry(chosenFileEntry, function(writableFileEntry) {
writableFileEntry.createWriter(function(writer) {
writer.onerror = errorHandler;
writer.onwriteend = callback;
chosenFileEntry.file(function(file) {
writer.write(file);
});
}, errorHandler);
});
</pre>
<p>
The following code creates a new file
with "Save as" functionality and
writes the new blob to the file
using the <code>writer.write()</code> method.
</p>
<pre>
chrome.fileSystem.chooseEntry({type: 'saveFile'}, function(writableFileEntry) {
writableFileEntry.createWriter(function(writer) {
writer.onerror = errorHandler;
writer.onwriteend = function(e) {
console.log('write complete');
};
writer.write(new Blob(['1234567890'], {type: 'text/plain'}));
}, errorHandler);
});
</pre>
<h2 id="sync-filesystem">Using the Chrome Sync Filesystem API</h2>
<p>
Using syncable file storage,
returned data objects can be operated on in the same way
as local offline file systems in the
<a href="http://www.w3.org/TR/file-system-api/">FileSystem API</a>,
but with the added (and automatic) syncing
of that data to Google Drive.
</p>
<h3 id="sync-manifest">Adding sync file system permission</h3>
<p>
To use Chrome's Sync Filesystem API,
you need to add the "syncFileSystem" permission to the manifest,
so that you can obtain permission from the user
to store and sync persistent data.
<pre>
"permissions": [
"...",
"syncFileSystem"
]
</pre>
<h3 id="initiate">Initiating syncable file storage</h3>
<p>
To initiate syncable file storage in your app,
simply call $ref:[syncFileSystem.requestFileSystem].
This method returns a syncable filesystem backed by Google Drive,
for example:
</p>
<pre>
chrome.syncFileSystem.requestFileSystem(function (fs) {
// FileSystem API should just work on the returned 'fs'.
fs.root.getFile('test.txt', {create:true}, getEntryCallback, errorCallback);
});
</pre>
<h3 id="file-status">About file sync status</h3>
<p>
Use $ref:[syncFileSystem.getFileStatus] to get the sync status
for a current file:
</p>
<pre>
chrome.syncFileSystem.getFileStatus(entry, function(status) {...});
</pre>
<p>
File sync status values can be one of the following:
<code>'synced'</code>, <code>'pending'</code>, or <code>'conflicting'</code>.
'Synced' means the file is fully synchronized;
there're no pending local changes
that haven't been synchronized to Google Drive.
There can, however, be pending changes on the Google Drive side
that haven't been fetched yet.
</p>
<p>
'Pending' means the file has pending changes
not yet synchronized to Google Drive.
If the app is running online,
local changes are (almost) immediately synchronized to Google Drive, and the
$ref:[syncFileSystem.onFileStatusChanged]
event is fired with the <code>'synced'</code> status
(see below for more details).
</p>
<p>
The $ref:[syncFileSystem.onFileStatusChanged]
is fired when a file's status changes to <code>'conflicting'</code>.
'Conflicting' means there are conflicting changes
on both the local storage and Google Drive.
A file can be in this state only if the conflict resolution policy
is set to <code>'manual'</code>. The default policy is
<code>'last_write_win'</code> and conflicts are automatically resolved
by simple last-write-win policy. The system's conflict resolution policy
can be changed by $ref:[syncFileSystem.setConflictResolutionPolicy].
</p>
<p>
If the conflict resolution policy is set to <code>'manual'</code> and
a file results in <code>'conflicting'</code> state, the app can still
read and write the file as a local offline file, but the changes are not
sync'ed and the file will be kept detached from remote changes made on
other clients until the conflict is resolved.
The easiest way to resolve a conflict is to delete or rename the local
version of file. This forces the remote version to be synchronized,
the conflicting state is resolved, and
the <code>onFileStatusChanged</code> event is fired
with the <code>'synced'</code> status.
</p>
<h3 id="file-status">Listening for changes in synced status</h3>
<p>
The
$ref:[syncFileSystem.onFileStatusChanged]
event is fired when the sync status of a file changes.
For example,
assume a file has pending changes and is in the 'pending' state.
The app may have been in offline state so that
the change is about to be synchronized.
When the sync service detects the pending local change
and uploads the change to Google Drive,
the service fires the <code>onFileStatusChanged</code> event
with following values:
<code>{ fileEntry:a fileEntry for the file, status: 'synced', action: 'updated',
direction: 'local_to_remote' }</code>.
</p>
<p>
Similarly, regardless of the local activities,
the sync service may detect remote changes made by another client,
and downloads the changes from Google Drive to the local storage.
If the remote change was for adding a new file,
an event with following values is fired:
<code>{ fileEntry: a fileEntry for the file, status: 'synced', action: 'added',
direction: 'remote_to_local' }</code>.
</p>
<p>
If both the local and remote side have conflicting changes for the same file
and if the conflict resolution policy is set to <code>'manual'</code>,
the file status is changed to <code>conflicting</code> state,
is detached from the sync service,
and won't be synchronized until the conflict is resolved.
In this case an event with following values is fired:
<code>{ fileEntry: a fileEntry for the file, status: 'conflicting', action: null,
direction: null }</code>.
</p>
<p>
You can add a listener for this event
that responds to any changes in status.
For example,
the <a href="https://github.com/agektmr/ChromeMusicPlayer">Chrome Music Player</a> app
listens for any new music synced from Google Drive,
but not yet imported to the user's local storage on a particular client.
Any music found gets synced to that client:
</p>
<pre>
chrome.syncFileSystem.onFileStatusChanged.addListener(function(fileInfo) {
if (fileInfo.status === 'synced') {
if (fileInfo.direction === 'remote_to_local') {
if (fileInfo.action === 'added') {
db.add(fileInfo.fileEntry);
} else if (fileInfo.action === 'deleted') {
db.remove(fileInfo.fileEntry);
}
}
}
});
</pre>
<h3 id="check-usage">Checking API usage</h3>
<p>
To check the amount of data being used by the API,
query the app's local sandboxed directory
or the usage bytes returned by
$ref:[syncFileSystem.getUsageAndQuota]:
</p>
<pre>
chrome.syncFileSystem.getUsageAndQuota(fileSystem, function (storageInfo) {
updateUsageInfo(storageInfo.usageBytes);
updateQuotaInfo(storageInfo.quotaBytes);
});
</pre>
<p>
You can also look at the user's sync backend service storage (in Google Drive).
Synced files are saved in a hidden Google Drive folder,
<strong>Chrome Syncable FileSystem</strong>. The folder won't be shown in
your 'My Drive' list but can be accessed by searching the folder name
in the search box. (Note that the remote folder layout is
<strong>not</strong> guaranteed to remain backwards compatible between
releases.)
</p>
<p class="backtotop"><a href="#top">Back to top</a></p>