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
5var fileSystemNatives = requireNative('file_system_natives');
6var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem;
7var sendRequest = require('sendRequest');
8var lastError = require('lastError');
9var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
10// TODO(sammc): Don't require extension. See http://crbug.com/235689.
11var GetExtensionViews = requireNative('runtime').GetExtensionViews;
12
13// For a given |apiName|, generates object with two elements that are used
14// in file system relayed APIs:
15// * 'bindFileEntryCallback' function that provides mapping between JS objects
16//   into actual FileEntry|DirectoryEntry objects.
17// * 'entryIdManager' object that implements methods for keeping the tracks of
18//   previously saved file entries.
19function getFileBindingsForApi(apiName) {
20  // Fallback to using the current window if no background page is running.
21  var backgroundPage = GetExtensionViews(-1, 'BACKGROUND')[0] || window;
22  var backgroundPageModuleSystem = GetModuleSystem(backgroundPage);
23
24  // All windows use the bindFileEntryCallback from the background page so their
25  // FileEntry objects have the background page's context as their own. This
26  // allows them to be used from other windows (including the background page)
27  // after the original window is closed.
28  if (window == backgroundPage) {
29    var bindFileEntryCallback = function(functionName, apiFunctions) {
30      apiFunctions.setCustomCallback(functionName,
31          function(name, request, response) {
32        if (request.callback && response) {
33          var callback = request.callback;
34          request.callback = null;
35
36          var entries = [];
37          var hasError = false;
38
39          var getEntryError = function(fileError) {
40            if (!hasError) {
41              hasError = true;
42              lastError.run(
43                  apiName + '.' + functionName,
44                  'Error getting fileEntry, code: ' + fileError.code,
45                  request.stack,
46                  callback);
47            }
48          }
49
50          // Loop through the response entries and asynchronously get the
51          // FileEntry for each. We use hasError to ensure that only the first
52          // error is reported. Note that an error can occur either during the
53          // loop or in the asynchronous error callback to getFile.
54          $Array.forEach(response.entries, function(entry) {
55            if (hasError)
56              return;
57            var fileSystemId = entry.fileSystemId;
58            var baseName = entry.baseName;
59            var id = entry.id;
60            var fs = GetIsolatedFileSystem(fileSystemId);
61
62            try {
63              var getEntryCallback = function(fileEntry) {
64                if (hasError)
65                  return;
66                entryIdManager.registerEntry(id, fileEntry);
67                entries.push(fileEntry);
68                // Once all entries are ready, pass them to the callback. In the
69                // event of an error, this condition will never be satisfied so
70                // the callback will not be called with any entries.
71                if (entries.length == response.entries.length) {
72                  if (response.multiple) {
73                    sendRequest.safeCallbackApply(
74                        apiName + '.' + functionName, request, callback,
75                        [entries]);
76                  } else {
77                    sendRequest.safeCallbackApply(
78                        apiName + '.' + functionName, request, callback,
79                        [entries[0]]);
80                  }
81                }
82              }
83              // TODO(koz): fs.root.getFile() makes a trip to the browser
84              // process, but it might be possible avoid that by calling
85              // WebDOMFileSystem::createV8Entry().
86              if (entry.isDirectory) {
87                fs.root.getDirectory(baseName, {}, getEntryCallback,
88                                     getEntryError);
89              } else {
90                fs.root.getFile(baseName, {}, getEntryCallback, getEntryError);
91              }
92            } catch (e) {
93              if (!hasError) {
94                hasError = true;
95                lastError.run(apiName + '.' + functionName,
96                              'Error getting fileEntry: ' + e.stack,
97                              request.stack,
98                              callback);
99              }
100            }
101          });
102        }
103      });
104    };
105    var entryIdManager = require('entryIdManager');
106  } else {
107    // Force the fileSystem API to be loaded in the background page. Using
108    // backgroundPageModuleSystem.require('fileSystem') is insufficient as
109    // requireNative is only allowed while lazily loading an API.
110    backgroundPage.chrome.fileSystem;
111    var bindFileEntryCallback = backgroundPageModuleSystem.require(
112        apiName).bindFileEntryCallback;
113    var entryIdManager = backgroundPageModuleSystem.require('entryIdManager');
114  }
115  return {bindFileEntryCallback: bindFileEntryCallback,
116          entryIdManager: entryIdManager};
117}
118
119exports.getFileBindingsForApi = getFileBindingsForApi;
120