1// Copyright 2013 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#include "content/child/fileapi/webfilesystem_impl.h"
6
7#include "base/bind.h"
8#include "base/id_map.h"
9#include "base/lazy_instance.h"
10#include "base/logging.h"
11#include "base/message_loop/message_loop_proxy.h"
12#include "base/synchronization/waitable_event.h"
13#include "base/threading/thread_local.h"
14#include "content/child/child_thread.h"
15#include "content/child/fileapi/file_system_dispatcher.h"
16#include "content/child/fileapi/webfilesystem_callback_adapters.h"
17#include "content/child/fileapi/webfilewriter_impl.h"
18#include "content/common/fileapi/file_system_messages.h"
19#include "third_party/WebKit/public/platform/WebFileInfo.h"
20#include "third_party/WebKit/public/platform/WebString.h"
21#include "third_party/WebKit/public/platform/WebURL.h"
22#include "third_party/WebKit/public/web/WebFileSystemCallbacks.h"
23#include "url/gurl.h"
24#include "webkit/child/worker_task_runner.h"
25#include "webkit/common/fileapi/directory_entry.h"
26#include "webkit/common/fileapi/file_system_util.h"
27#include "webkit/glue/webkit_glue.h"
28
29using WebKit::WebFileInfo;
30using WebKit::WebFileSystemCallbacks;
31using WebKit::WebFileSystemEntry;
32using WebKit::WebString;
33using WebKit::WebURL;
34using WebKit::WebVector;
35using webkit_glue::WorkerTaskRunner;
36
37namespace content {
38
39namespace {
40
41class CallbacksMap;
42
43base::LazyInstance<base::ThreadLocalPointer<CallbacksMap> >::Leaky
44    g_callbacks_map_tls = LAZY_INSTANCE_INITIALIZER;
45
46// TODO(kinuko): Integrate this into WebFileSystemImpl when blink side
47// becomes ready to make WebFileSystemImpl thread-local.
48class CallbacksMap : public WorkerTaskRunner::Observer {
49 public:
50  static CallbacksMap* Get() {
51    return g_callbacks_map_tls.Pointer()->Get();
52  }
53
54  static CallbacksMap* GetOrCreate() {
55    if (g_callbacks_map_tls.Pointer()->Get())
56      return g_callbacks_map_tls.Pointer()->Get();
57    CallbacksMap* map = new CallbacksMap;
58    if (WorkerTaskRunner::Instance()->CurrentWorkerId())
59      WorkerTaskRunner::Instance()->AddStopObserver(map);
60    return map;
61  }
62
63  virtual ~CallbacksMap() {
64    IDMap<WebFileSystemCallbacks>::iterator iter(&callbacks_);
65    while (!iter.IsAtEnd()) {
66      iter.GetCurrentValue()->didFail(WebKit::WebFileErrorAbort);
67      iter.Advance();
68    }
69    g_callbacks_map_tls.Pointer()->Set(NULL);
70  }
71
72  // webkit_glue::WorkerTaskRunner::Observer implementation.
73  virtual void OnWorkerRunLoopStopped() OVERRIDE {
74    delete this;
75  }
76
77  int RegisterCallbacks(WebFileSystemCallbacks* callbacks) {
78    return callbacks_.Add(callbacks);
79  }
80
81  WebFileSystemCallbacks* GetAndUnregisterCallbacks(
82      int callbacks_id) {
83    WebFileSystemCallbacks* callbacks = callbacks_.Lookup(callbacks_id);
84    callbacks_.Remove(callbacks_id);
85    return callbacks;
86  }
87
88 private:
89  CallbacksMap() {
90    g_callbacks_map_tls.Pointer()->Set(this);
91  }
92
93  IDMap<WebFileSystemCallbacks> callbacks_;
94
95  DISALLOW_COPY_AND_ASSIGN(CallbacksMap);
96};
97
98class WaitableCallbackResults {
99 public:
100  static WaitableCallbackResults* MaybeCreate(
101      WebKit::WebFileSystemCallbacks* callbacks) {
102    if (callbacks->shouldBlockUntilCompletion())
103      return new WaitableCallbackResults;
104    return NULL;
105  }
106  ~WaitableCallbackResults() {}
107
108  void SetResultsAndSignal(const base::Closure& results_closure) {
109    results_closure_ = results_closure;
110    event_->Signal();
111  }
112
113  void WaitAndRun() {
114    event_->Wait();
115    DCHECK(!results_closure_.is_null());
116    results_closure_.Run();
117  }
118
119 private:
120  WaitableCallbackResults() : event_(new base::WaitableEvent(true, false)) {}
121
122  base::WaitableEvent* event_;
123  base::Closure results_closure_;
124  DISALLOW_COPY_AND_ASSIGN(WaitableCallbackResults);
125};
126
127void DidReceiveSnapshotFile(int request_id) {
128  if (ChildThread::current())
129    ChildThread::current()->Send(
130        new FileSystemHostMsg_DidReceiveSnapshotFile(request_id));
131}
132
133int CurrentWorkerId() {
134  return WorkerTaskRunner::Instance()->CurrentWorkerId();
135}
136
137template <typename Method, typename Params>
138void CallDispatcherOnMainThread(
139    base::MessageLoopProxy* loop,
140    Method method, const Params& params,
141    scoped_ptr<WaitableCallbackResults> waitable_results) {
142  scoped_ptr<WaitableCallbackResults> null_waitable;
143  if (!loop->RunsTasksOnCurrentThread()) {
144    loop->PostTask(FROM_HERE,
145                   base::Bind(&CallDispatcherOnMainThread<Method, Params>,
146                              make_scoped_refptr(loop), method, params,
147                              base::Passed(&null_waitable)));
148    if (!waitable_results)
149      return;
150    waitable_results->WaitAndRun();
151  }
152  if (!ChildThread::current() ||
153      !ChildThread::current()->file_system_dispatcher())
154    return;
155
156  DCHECK(!waitable_results);
157  DispatchToMethod(ChildThread::current()->file_system_dispatcher(),
158                   method, params);
159}
160
161// Run WebFileSystemCallbacks's |method| with |params|.
162template <typename Method, typename Params>
163void RunCallbacks(int callbacks_id, Method method, const Params& params) {
164  if (!CallbacksMap::Get())
165    return;
166  WebFileSystemCallbacks* callbacks =
167      CallbacksMap::Get()->GetAndUnregisterCallbacks(callbacks_id);
168  DCHECK(callbacks);
169  DispatchToMethod(callbacks, method, params);
170}
171
172void DispatchResultsClosure(int thread_id, int callbacks_id,
173                            WaitableCallbackResults* waitable_results,
174                            const base::Closure& results_closure) {
175  if (thread_id != CurrentWorkerId()) {
176    if (waitable_results) {
177      waitable_results->SetResultsAndSignal(results_closure);
178      return;
179    }
180    WorkerTaskRunner::Instance()->PostTask(thread_id, results_closure);
181    return;
182  }
183  results_closure.Run();
184}
185
186template <typename Method, typename Params>
187void CallbackFileSystemCallbacks(
188    int thread_id, int callbacks_id,
189    WaitableCallbackResults* waitable_results,
190    Method method, const Params& params) {
191  DispatchResultsClosure(
192      thread_id, callbacks_id, waitable_results,
193      base::Bind(&RunCallbacks<Method, Params>, callbacks_id, method, params));
194}
195
196void StatusCallbackAdapter(int thread_id, int callbacks_id,
197                           WaitableCallbackResults* waitable_results,
198                           base::PlatformFileError error) {
199  if (error == base::PLATFORM_FILE_OK) {
200    CallbackFileSystemCallbacks(
201        thread_id, callbacks_id, waitable_results,
202        &WebFileSystemCallbacks::didSucceed, MakeTuple());
203  } else {
204    CallbackFileSystemCallbacks(
205        thread_id, callbacks_id, waitable_results,
206        &WebFileSystemCallbacks::didFail,
207        MakeTuple(fileapi::PlatformFileErrorToWebFileError(error)));
208  }
209}
210
211void ReadMetadataCallbackAdapter(int thread_id, int callbacks_id,
212                                 WaitableCallbackResults* waitable_results,
213                                 const base::PlatformFileInfo& file_info) {
214  WebFileInfo web_file_info;
215  webkit_glue::PlatformFileInfoToWebFileInfo(file_info, &web_file_info);
216  CallbackFileSystemCallbacks(
217      thread_id, callbacks_id, waitable_results,
218      &WebFileSystemCallbacks::didReadMetadata,
219      MakeTuple(web_file_info));
220}
221
222void ReadDirectoryCallbackAdapater(
223    int thread_id, int callbacks_id, WaitableCallbackResults* waitable_results,
224    const std::vector<fileapi::DirectoryEntry>& entries,
225    bool has_more) {
226  WebVector<WebFileSystemEntry> file_system_entries(entries.size());
227  for (size_t i = 0; i < entries.size(); i++) {
228    file_system_entries[i].name =
229        base::FilePath(entries[i].name).AsUTF16Unsafe();
230    file_system_entries[i].isDirectory = entries[i].is_directory;
231  }
232  CallbackFileSystemCallbacks(
233      thread_id, callbacks_id, waitable_results,
234      &WebFileSystemCallbacks::didReadDirectory,
235      MakeTuple(file_system_entries, has_more));
236}
237
238void DidCreateFileWriter(
239    int callbacks_id,
240    const GURL& path,
241    WebKit::WebFileWriterClient* client,
242    base::MessageLoopProxy* main_thread_loop,
243    const base::PlatformFileInfo& file_info) {
244  if (!CallbacksMap::Get())
245    return;
246
247  WebFileSystemCallbacks* callbacks =
248      CallbacksMap::Get()->GetAndUnregisterCallbacks(callbacks_id);
249  DCHECK(callbacks);
250
251  if (file_info.is_directory || file_info.size < 0) {
252    callbacks->didFail(WebKit::WebFileErrorInvalidState);
253    return;
254  }
255  WebFileWriterImpl::Type type = callbacks->shouldBlockUntilCompletion() ?
256      WebFileWriterImpl::TYPE_SYNC : WebFileWriterImpl::TYPE_ASYNC;
257  callbacks->didCreateFileWriter(
258      new WebFileWriterImpl(path, client, type, main_thread_loop),
259      file_info.size);
260}
261
262void CreateFileWriterCallbackAdapter(
263    int thread_id, int callbacks_id,
264    WaitableCallbackResults* waitable_results,
265    base::MessageLoopProxy* main_thread_loop,
266    const GURL& path,
267    WebKit::WebFileWriterClient* client,
268    const base::PlatformFileInfo& file_info) {
269  DispatchResultsClosure(
270      thread_id, callbacks_id, waitable_results,
271      base::Bind(&DidCreateFileWriter, callbacks_id, path, client,
272                 make_scoped_refptr(main_thread_loop), file_info));
273}
274
275void DidCreateSnapshotFile(
276    int callbacks_id,
277    base::MessageLoopProxy* main_thread_loop,
278    const base::PlatformFileInfo& file_info,
279    const base::FilePath& platform_path,
280    int request_id) {
281  if (!CallbacksMap::Get())
282    return;
283
284  WebFileSystemCallbacks* callbacks =
285      CallbacksMap::Get()->GetAndUnregisterCallbacks(callbacks_id);
286  DCHECK(callbacks);
287
288  WebFileInfo web_file_info;
289  webkit_glue::PlatformFileInfoToWebFileInfo(file_info, &web_file_info);
290  web_file_info.platformPath = platform_path.AsUTF16Unsafe();
291  callbacks->didCreateSnapshotFile(web_file_info);
292
293  // TODO(michaeln,kinuko): Use ThreadSafeSender when Blob becomes
294  // non-bridge model.
295  main_thread_loop->PostTask(
296      FROM_HERE, base::Bind(&DidReceiveSnapshotFile, request_id));
297}
298
299void CreateSnapshotFileCallbackAdapter(
300    int thread_id, int callbacks_id,
301    WaitableCallbackResults* waitable_results,
302    base::MessageLoopProxy* main_thread_loop,
303    const base::PlatformFileInfo& file_info,
304    const base::FilePath& platform_path,
305    int request_id) {
306  DispatchResultsClosure(
307      thread_id, callbacks_id, waitable_results,
308      base::Bind(&DidCreateSnapshotFile, callbacks_id,
309                 make_scoped_refptr(main_thread_loop),
310                 file_info, platform_path, request_id));
311}
312
313}  // namespace
314
315WebFileSystemImpl::~WebFileSystemImpl() {
316}
317
318WebFileSystemImpl::WebFileSystemImpl(base::MessageLoopProxy* main_thread_loop)
319    : main_thread_loop_(main_thread_loop) {
320}
321
322void WebFileSystemImpl::move(
323    const WebKit::WebURL& src_path,
324    const WebKit::WebURL& dest_path,
325    WebKit::WebFileSystemCallbacks* callbacks) {
326  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
327  WaitableCallbackResults* waitable_results =
328      WaitableCallbackResults::MaybeCreate(callbacks);
329  CallDispatcherOnMainThread(
330      main_thread_loop_.get(),
331      &FileSystemDispatcher::Move,
332      MakeTuple(GURL(src_path), GURL(dest_path),
333                base::Bind(&StatusCallbackAdapter,
334                           CurrentWorkerId(), callbacks_id,
335                           base::Unretained(waitable_results))),
336      make_scoped_ptr(waitable_results));
337}
338
339void WebFileSystemImpl::copy(
340    const WebKit::WebURL& src_path,
341    const WebKit::WebURL& dest_path,
342    WebKit::WebFileSystemCallbacks* callbacks) {
343  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
344  WaitableCallbackResults* waitable_results =
345      WaitableCallbackResults::MaybeCreate(callbacks);
346  CallDispatcherOnMainThread(
347      main_thread_loop_.get(),
348      &FileSystemDispatcher::Copy,
349      MakeTuple(GURL(src_path), GURL(dest_path),
350                base::Bind(&StatusCallbackAdapter,
351                           CurrentWorkerId(), callbacks_id,
352                           base::Unretained(waitable_results))),
353      make_scoped_ptr(waitable_results));
354}
355
356void WebFileSystemImpl::remove(
357    const WebKit::WebURL& path,
358    WebKit::WebFileSystemCallbacks* callbacks) {
359  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
360  WaitableCallbackResults* waitable_results =
361      WaitableCallbackResults::MaybeCreate(callbacks);
362  CallDispatcherOnMainThread(
363      main_thread_loop_.get(),
364      &FileSystemDispatcher::Remove,
365      MakeTuple(GURL(path), false /* recursive */,
366                base::Bind(&StatusCallbackAdapter,
367                           CurrentWorkerId(), callbacks_id,
368                           base::Unretained(waitable_results))),
369      make_scoped_ptr(waitable_results));
370}
371
372void WebFileSystemImpl::removeRecursively(
373    const WebKit::WebURL& path,
374    WebKit::WebFileSystemCallbacks* callbacks) {
375  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
376  WaitableCallbackResults* waitable_results =
377      WaitableCallbackResults::MaybeCreate(callbacks);
378  CallDispatcherOnMainThread(
379      main_thread_loop_.get(),
380      &FileSystemDispatcher::Remove,
381      MakeTuple(GURL(path), true /* recursive */,
382                base::Bind(&StatusCallbackAdapter,
383                           CurrentWorkerId(), callbacks_id,
384                           base::Unretained(waitable_results))),
385      make_scoped_ptr(waitable_results));
386}
387
388void WebFileSystemImpl::readMetadata(
389    const WebKit::WebURL& path,
390    WebKit::WebFileSystemCallbacks* callbacks) {
391  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
392  WaitableCallbackResults* waitable_results =
393      WaitableCallbackResults::MaybeCreate(callbacks);
394  CallDispatcherOnMainThread(
395      main_thread_loop_.get(),
396      &FileSystemDispatcher::ReadMetadata,
397      MakeTuple(GURL(path),
398                base::Bind(&ReadMetadataCallbackAdapter,
399                           CurrentWorkerId(), callbacks_id,
400                           base::Unretained(waitable_results)),
401                base::Bind(&StatusCallbackAdapter,
402                           CurrentWorkerId(), callbacks_id,
403                           base::Unretained(waitable_results))),
404      make_scoped_ptr(waitable_results));
405}
406
407void WebFileSystemImpl::createFile(
408    const WebKit::WebURL& path,
409    bool exclusive,
410    WebKit::WebFileSystemCallbacks* callbacks) {
411  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
412  WaitableCallbackResults* waitable_results =
413      WaitableCallbackResults::MaybeCreate(callbacks);
414  CallDispatcherOnMainThread(
415      main_thread_loop_.get(),
416      &FileSystemDispatcher::CreateFile,
417      MakeTuple(GURL(path), exclusive,
418                base::Bind(&StatusCallbackAdapter,
419                           CurrentWorkerId(), callbacks_id,
420                           base::Unretained(waitable_results))),
421      make_scoped_ptr(waitable_results));
422}
423
424void WebFileSystemImpl::createDirectory(
425    const WebKit::WebURL& path,
426    bool exclusive,
427    WebKit::WebFileSystemCallbacks* callbacks) {
428  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
429  WaitableCallbackResults* waitable_results =
430      WaitableCallbackResults::MaybeCreate(callbacks);
431  CallDispatcherOnMainThread(
432      main_thread_loop_.get(),
433      &FileSystemDispatcher::CreateDirectory,
434      MakeTuple(GURL(path), exclusive, false /* recursive */,
435                base::Bind(&StatusCallbackAdapter,
436                           CurrentWorkerId(), callbacks_id,
437                           base::Unretained(waitable_results))),
438      make_scoped_ptr(waitable_results));
439}
440
441void WebFileSystemImpl::fileExists(
442    const WebKit::WebURL& path,
443    WebKit::WebFileSystemCallbacks* callbacks) {
444  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
445  WaitableCallbackResults* waitable_results =
446      WaitableCallbackResults::MaybeCreate(callbacks);
447  CallDispatcherOnMainThread(
448      main_thread_loop_.get(),
449      &FileSystemDispatcher::Exists,
450      MakeTuple(GURL(path), false /* directory */,
451                base::Bind(&StatusCallbackAdapter,
452                           CurrentWorkerId(), callbacks_id,
453base::Unretained(waitable_results))),
454      make_scoped_ptr(waitable_results));
455}
456
457void WebFileSystemImpl::directoryExists(
458    const WebKit::WebURL& path,
459    WebKit::WebFileSystemCallbacks* callbacks) {
460  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
461  WaitableCallbackResults* waitable_results =
462      WaitableCallbackResults::MaybeCreate(callbacks);
463  CallDispatcherOnMainThread(
464      main_thread_loop_.get(),
465      &FileSystemDispatcher::Exists,
466      MakeTuple(GURL(path), true /* directory */,
467                base::Bind(&StatusCallbackAdapter,
468                           CurrentWorkerId(), callbacks_id,
469                           base::Unretained(waitable_results))),
470      make_scoped_ptr(waitable_results));
471}
472
473void WebFileSystemImpl::readDirectory(
474    const WebKit::WebURL& path,
475    WebKit::WebFileSystemCallbacks* callbacks) {
476  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
477  WaitableCallbackResults* waitable_results =
478      WaitableCallbackResults::MaybeCreate(callbacks);
479  CallDispatcherOnMainThread(
480      main_thread_loop_.get(),
481      &FileSystemDispatcher::ReadDirectory,
482      MakeTuple(GURL(path),
483                base::Bind(&ReadDirectoryCallbackAdapater,
484                           CurrentWorkerId(), callbacks_id,
485                           base::Unretained(waitable_results)),
486                base::Bind(&StatusCallbackAdapter,
487                           CurrentWorkerId(), callbacks_id,
488base::Unretained(waitable_results))),
489      make_scoped_ptr(waitable_results));
490}
491
492WebKit::WebFileWriter* WebFileSystemImpl::createFileWriter(
493    const WebURL& path, WebKit::WebFileWriterClient* client) {
494  return new WebFileWriterImpl(GURL(path), client,
495                               WebFileWriterImpl::TYPE_ASYNC,
496                               main_thread_loop_.get());
497}
498
499void WebFileSystemImpl::createFileWriter(
500    const WebURL& path,
501    WebKit::WebFileWriterClient* client,
502    WebKit::WebFileSystemCallbacks* callbacks) {
503  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
504  WaitableCallbackResults* waitable_results =
505      WaitableCallbackResults::MaybeCreate(callbacks);
506  CallDispatcherOnMainThread(
507      main_thread_loop_.get(),
508      &FileSystemDispatcher::ReadMetadata,
509      MakeTuple(GURL(path),
510                base::Bind(&CreateFileWriterCallbackAdapter,
511                           CurrentWorkerId(), callbacks_id,
512                           base::Unretained(waitable_results),
513                           main_thread_loop_, GURL(path), client),
514                base::Bind(&StatusCallbackAdapter,
515                           CurrentWorkerId(), callbacks_id,
516                           base::Unretained(waitable_results))),
517      make_scoped_ptr(waitable_results));
518}
519
520void WebFileSystemImpl::createSnapshotFileAndReadMetadata(
521    const WebKit::WebURL& path,
522    WebKit::WebFileSystemCallbacks* callbacks) {
523  int callbacks_id = CallbacksMap::GetOrCreate()->RegisterCallbacks(callbacks);
524  WaitableCallbackResults* waitable_results =
525      WaitableCallbackResults::MaybeCreate(callbacks);
526  CallDispatcherOnMainThread(
527      main_thread_loop_.get(),
528      &FileSystemDispatcher::CreateSnapshotFile,
529      MakeTuple(GURL(path),
530                base::Bind(&CreateSnapshotFileCallbackAdapter,
531                           CurrentWorkerId(), callbacks_id,
532                           base::Unretained(waitable_results),
533                           main_thread_loop_),
534                base::Bind(&StatusCallbackAdapter,
535                           CurrentWorkerId(), callbacks_id,
536                           base::Unretained(waitable_results))),
537      make_scoped_ptr(waitable_results));
538}
539
540}  // namespace content
541