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
5#include "chrome/browser/chromeos/file_manager/snapshot_manager.h"
6
7#include "base/bind.h"
8#include "base/sys_info.h"
9#include "chrome/browser/chromeos/file_manager/app_id.h"
10#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
11#include "chrome/browser/profiles/profile.h"
12#include "content/public/browser/browser_thread.h"
13#include "google_apis/drive/task_util.h"
14#include "storage/browser/fileapi/file_system_context.h"
15#include "storage/common/blob/shareable_file_reference.h"
16#include "third_party/cros_system_api/constants/cryptohome.h"
17
18namespace file_manager {
19namespace {
20
21typedef base::Callback<void(int64)> GetNecessaryFreeSpaceCallback;
22
23// Part of ComputeSpaceNeedToBeFreed.
24int64 ComputeSpaceNeedToBeFreedAfterGetMetadataOnBlockingPool(
25    const base::FilePath& path,
26    int64 snapshot_size) {
27  int64 free_size = base::SysInfo::AmountOfFreeDiskSpace(path);
28  if (free_size < 0)
29    return -1;
30
31  // We need to keep cryptohome::kMinFreeSpaceInBytes free space even after
32  // |snapshot_size| is occupied.
33  free_size -= snapshot_size + cryptohome::kMinFreeSpaceInBytes;
34  return (free_size < 0 ? -free_size : 0);
35}
36
37// Part of ComputeSpaceNeedToBeFreed.
38void ComputeSpaceNeedToBeFreedAfterGetMetadata(
39    const base::FilePath& path,
40    const GetNecessaryFreeSpaceCallback& callback,
41    base::File::Error result,
42    const base::File::Info& file_info) {
43  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
44  if (result != base::File::FILE_OK) {
45    callback.Run(-1);
46    return;
47  }
48
49  base::PostTaskAndReplyWithResult(
50      content::BrowserThread::GetBlockingPool(),
51      FROM_HERE,
52      base::Bind(&ComputeSpaceNeedToBeFreedAfterGetMetadataOnBlockingPool,
53                 path, file_info.size),
54      callback);
55}
56
57// Part of ComputeSpaceNeedToBeFreed.
58void GetMetadataOnIOThread(const base::FilePath& path,
59                           scoped_refptr<storage::FileSystemContext> context,
60                           const storage::FileSystemURL& url,
61                           const GetNecessaryFreeSpaceCallback& callback) {
62  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
63  context->operation_runner()->GetMetadata(
64      url,
65      base::Bind(&ComputeSpaceNeedToBeFreedAfterGetMetadata, path, callback));
66}
67
68// Computes the size of space that need to be __additionally__ made available
69// in the |profile|'s data directory for taking the snapshot of |url|.
70// Returns 0 if no additional space is required, or -1 in the case of an error.
71void ComputeSpaceNeedToBeFreed(
72    Profile* profile,
73    scoped_refptr<storage::FileSystemContext> context,
74    const storage::FileSystemURL& url,
75    const GetNecessaryFreeSpaceCallback& callback) {
76  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
77  content::BrowserThread::PostTask(
78      content::BrowserThread::IO,
79      FROM_HERE,
80      base::Bind(&GetMetadataOnIOThread, profile->GetPath(), context, url,
81                 google_apis::CreateRelayCallback(callback)));
82}
83
84// Part of CreateManagedSnapshot. Runs CreateSnapshotFile method of fileapi.
85void CreateSnapshotFileOnIOThread(
86    scoped_refptr<storage::FileSystemContext> context,
87    const storage::FileSystemURL& url,
88    const storage::FileSystemOperation::SnapshotFileCallback& callback) {
89  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
90  context->operation_runner()->CreateSnapshotFile(url, callback);
91}
92
93// Utility for destructing the bound |file_refs| on IO thread. This is meant
94// to be used together with base::Bind. After this function finishes, the
95// Bind callback should destruct the bound argument.
96void FreeReferenceOnIOThread(
97    const std::deque<SnapshotManager::FileReferenceWithSizeInfo>& file_refs) {
98  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
99}
100
101}  // namespace
102
103SnapshotManager::FileReferenceWithSizeInfo::FileReferenceWithSizeInfo(
104    scoped_refptr<storage::ShareableFileReference> ref,
105    int64 size)
106    : file_ref(ref), file_size(size) {
107}
108
109SnapshotManager::FileReferenceWithSizeInfo::~FileReferenceWithSizeInfo() {
110}
111
112SnapshotManager::SnapshotManager(Profile* profile)
113    : profile_(profile), weak_ptr_factory_(this) {
114}
115
116SnapshotManager::~SnapshotManager() {
117  if (!file_refs_.empty()) {
118    bool posted = content::BrowserThread::PostTask(
119        content::BrowserThread::IO,
120        FROM_HERE,
121        base::Bind(&FreeReferenceOnIOThread, file_refs_));
122    DCHECK(posted);
123  }
124}
125
126void SnapshotManager::CreateManagedSnapshot(
127    const base::FilePath& absolute_file_path,
128    const LocalPathCallback& callback) {
129  scoped_refptr<storage::FileSystemContext> context(
130      util::GetFileSystemContextForExtensionId(profile_, kFileManagerAppId));
131  DCHECK(context.get());
132
133  GURL url;
134  if (!util::ConvertAbsoluteFilePathToFileSystemUrl(
135          profile_, absolute_file_path, kFileManagerAppId, &url)) {
136    callback.Run(base::FilePath());
137    return;
138  }
139  storage::FileSystemURL filesystem_url = context->CrackURL(url);
140
141  ComputeSpaceNeedToBeFreed(profile_, context, filesystem_url,
142      base::Bind(&SnapshotManager::CreateManagedSnapshotAfterSpaceComputed,
143                 weak_ptr_factory_.GetWeakPtr(),
144                 filesystem_url,
145                 callback));
146}
147
148void SnapshotManager::CreateManagedSnapshotAfterSpaceComputed(
149    const storage::FileSystemURL& filesystem_url,
150    const LocalPathCallback& callback,
151    int64 needed_space) {
152  scoped_refptr<storage::FileSystemContext> context(
153      util::GetFileSystemContextForExtensionId(profile_, kFileManagerAppId));
154  DCHECK(context.get());
155
156  if (needed_space < 0) {
157    callback.Run(base::FilePath());
158    return;
159  }
160
161  // Free up to the required size.
162  std::deque<FileReferenceWithSizeInfo> to_free;
163  while (needed_space > 0 && !file_refs_.empty()) {
164    needed_space -= file_refs_.front().file_size;
165    to_free.push_back(file_refs_.front());
166    file_refs_.pop_front();
167  }
168  if (!to_free.empty()) {
169    bool posted = content::BrowserThread::PostTask(
170        content::BrowserThread::IO,
171        FROM_HERE,
172        base::Bind(&FreeReferenceOnIOThread, to_free));
173    DCHECK(posted);
174  }
175
176  // If we still could not achieve the space requirement, abort with failure.
177  if (needed_space > 0) {
178    callback.Run(base::FilePath());
179    return;
180  }
181
182  // Start creating the snapshot.
183  content::BrowserThread::PostTask(
184      content::BrowserThread::IO,
185      FROM_HERE,
186      base::Bind(&CreateSnapshotFileOnIOThread,
187                 context,
188                 filesystem_url,
189                 google_apis::CreateRelayCallback(
190                     base::Bind(&SnapshotManager::OnCreateSnapshotFile,
191                                weak_ptr_factory_.GetWeakPtr(),
192                                callback))));
193}
194
195void SnapshotManager::OnCreateSnapshotFile(
196    const LocalPathCallback& callback,
197    base::File::Error result,
198    const base::File::Info& file_info,
199    const base::FilePath& platform_path,
200    const scoped_refptr<storage::ShareableFileReference>& file_ref) {
201  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
202
203  if (result != base::File::FILE_OK) {
204    callback.Run(base::FilePath());
205    return;
206  }
207
208  file_refs_.push_back(FileReferenceWithSizeInfo(file_ref, file_info.size));
209  callback.Run(platform_path);
210}
211
212}  // namespace file_manager
213