| <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> |