1// Copyright (c) 2012 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/drive/resource_metadata.h"
6
7#include "base/guid.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/stringprintf.h"
10#include "base/sys_info.h"
11#include "chrome/browser/chromeos/drive/drive.pb.h"
12#include "chrome/browser/chromeos/drive/file_system_util.h"
13#include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
14#include "content/public/browser/browser_thread.h"
15
16using content::BrowserThread;
17
18namespace drive {
19namespace {
20
21// Sets entry's base name from its title and other attributes.
22void SetBaseNameFromTitle(ResourceEntry* entry) {
23  std::string base_name = entry->title();
24  if (entry->has_file_specific_info() &&
25      entry->file_specific_info().is_hosted_document()) {
26    base_name += entry->file_specific_info().document_extension();
27  }
28  entry->set_base_name(util::NormalizeFileName(base_name));
29}
30
31// Returns true if enough disk space is available for DB operation.
32// TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
33bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath& path) {
34  const int64 kRequiredDiskSpaceInMB = 128;  // 128 MB seems to be large enough.
35  return base::SysInfo::AmountOfFreeDiskSpace(path) >=
36      kRequiredDiskSpaceInMB * (1 << 20);
37}
38
39// Runs |callback| with arguments.
40void RunGetResourceEntryCallback(const GetResourceEntryCallback& callback,
41                                 scoped_ptr<ResourceEntry> entry,
42                                 FileError error) {
43  DCHECK(!callback.is_null());
44
45  if (error != FILE_ERROR_OK)
46    entry.reset();
47  callback.Run(error, entry.Pass());
48}
49
50// Runs |callback| with arguments.
51void RunReadDirectoryCallback(const ReadDirectoryCallback& callback,
52                              scoped_ptr<ResourceEntryVector> entries,
53                              FileError error) {
54  DCHECK(!callback.is_null());
55
56  if (error != FILE_ERROR_OK)
57    entries.reset();
58  callback.Run(error, entries.Pass());
59}
60
61}  // namespace
62
63namespace internal {
64
65ResourceMetadata::ResourceMetadata(
66    ResourceMetadataStorage* storage,
67    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
68    : blocking_task_runner_(blocking_task_runner),
69      storage_(storage),
70      weak_ptr_factory_(this) {
71  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
72}
73
74FileError ResourceMetadata::Initialize() {
75  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
76
77  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
78    return FILE_ERROR_NO_LOCAL_SPACE;
79
80  if (!SetUpDefaultEntries())
81    return FILE_ERROR_FAILED;
82
83  return FILE_ERROR_OK;
84}
85
86void ResourceMetadata::Destroy() {
87  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
88
89  weak_ptr_factory_.InvalidateWeakPtrs();
90  blocking_task_runner_->PostTask(
91      FROM_HERE,
92      base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
93                 base::Unretained(this)));
94}
95
96FileError ResourceMetadata::Reset() {
97  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
98
99  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
100    return FILE_ERROR_NO_LOCAL_SPACE;
101
102  if (!storage_->SetLargestChangestamp(0) ||
103      !RemoveEntryRecursively(util::kDriveGrandRootLocalId) ||
104      !SetUpDefaultEntries())
105    return FILE_ERROR_FAILED;
106
107  return FILE_ERROR_OK;
108}
109
110ResourceMetadata::~ResourceMetadata() {
111  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
112}
113
114bool ResourceMetadata::SetUpDefaultEntries() {
115  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
116
117  // Initialize "/drive", "/drive/other" and "drive/trash".
118  ResourceEntry entry;
119  if (!storage_->GetEntry(util::kDriveGrandRootLocalId, &entry)) {
120    ResourceEntry root;
121    root.mutable_file_info()->set_is_directory(true);
122    // TODO(hashimoto): Stop setting dummy resource ID here.
123    root.set_resource_id(util::kDriveGrandRootLocalId);
124    root.set_local_id(util::kDriveGrandRootLocalId);
125    root.set_title(util::kDriveGrandRootDirName);
126    SetBaseNameFromTitle(&root);
127    if (!storage_->PutEntry(root))
128      return false;
129  }
130  if (!storage_->GetEntry(util::kDriveOtherDirLocalId, &entry)) {
131    ResourceEntry other_dir;
132    other_dir.mutable_file_info()->set_is_directory(true);
133    // TODO(hashimoto): Stop setting dummy resource ID here.
134    other_dir.set_resource_id(util::kDriveOtherDirLocalId);
135    other_dir.set_local_id(util::kDriveOtherDirLocalId);
136    other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
137    other_dir.set_title(util::kDriveOtherDirName);
138    if (!PutEntryUnderDirectory(other_dir))
139      return false;
140  }
141  if (!storage_->GetEntry(util::kDriveTrashDirLocalId, &entry)) {
142    ResourceEntry trash_dir;
143    trash_dir.mutable_file_info()->set_is_directory(true);
144    trash_dir.set_local_id(util::kDriveTrashDirLocalId);
145    trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
146    trash_dir.set_title(util::kDriveTrashDirName);
147    if (!PutEntryUnderDirectory(trash_dir))
148      return false;
149  }
150  return true;
151}
152
153void ResourceMetadata::DestroyOnBlockingPool() {
154  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
155  delete this;
156}
157
158int64 ResourceMetadata::GetLargestChangestamp() {
159  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
160  return storage_->GetLargestChangestamp();
161}
162
163FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
164  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
165
166  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
167    return FILE_ERROR_NO_LOCAL_SPACE;
168
169  return storage_->SetLargestChangestamp(value) ?
170      FILE_ERROR_OK : FILE_ERROR_FAILED;
171}
172
173FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
174                                     std::string* out_id) {
175  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
176  DCHECK(entry.local_id().empty());
177
178  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
179    return FILE_ERROR_NO_LOCAL_SPACE;
180
181  ResourceEntry parent;
182  if (!storage_->GetEntry(entry.parent_local_id(), &parent) ||
183      !parent.file_info().is_directory())
184    return FILE_ERROR_NOT_FOUND;
185
186  // Multiple entries with the same resource ID should not be present.
187  std::string local_id;
188  ResourceEntry existing_entry;
189  if (!entry.resource_id().empty() &&
190      storage_->GetIdByResourceId(entry.resource_id(), &local_id) &&
191      storage_->GetEntry(local_id, &existing_entry))
192    return FILE_ERROR_EXISTS;
193
194  // Generate unique local ID when needed.
195  while (local_id.empty() || storage_->GetEntry(local_id, &existing_entry))
196    local_id = base::GenerateGUID();
197
198  ResourceEntry new_entry(entry);
199  new_entry.set_local_id(local_id);
200
201  if (!PutEntryUnderDirectory(new_entry))
202    return FILE_ERROR_FAILED;
203
204  *out_id = local_id;
205  return FILE_ERROR_OK;
206}
207
208FileError ResourceMetadata::RemoveEntry(const std::string& id) {
209  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
210
211  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
212    return FILE_ERROR_NO_LOCAL_SPACE;
213
214  // Disallow deletion of default entries.
215  if (id == util::kDriveGrandRootLocalId ||
216      id == util::kDriveOtherDirLocalId ||
217      id == util::kDriveTrashDirLocalId)
218    return FILE_ERROR_ACCESS_DENIED;
219
220  ResourceEntry entry;
221  if (!storage_->GetEntry(id, &entry))
222    return FILE_ERROR_NOT_FOUND;
223
224  if (!RemoveEntryRecursively(id))
225    return FILE_ERROR_FAILED;
226  return FILE_ERROR_OK;
227}
228
229FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
230                                                 ResourceEntry* out_entry) {
231  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
232  DCHECK(!id.empty());
233  DCHECK(out_entry);
234
235  return storage_->GetEntry(id, out_entry) ?
236      FILE_ERROR_OK : FILE_ERROR_NOT_FOUND;
237}
238
239void ResourceMetadata::GetResourceEntryByPathOnUIThread(
240    const base::FilePath& file_path,
241    const GetResourceEntryCallback& callback) {
242  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
243  DCHECK(!callback.is_null());
244
245  scoped_ptr<ResourceEntry> entry(new ResourceEntry);
246  ResourceEntry* entry_ptr = entry.get();
247  base::PostTaskAndReplyWithResult(
248      blocking_task_runner_.get(),
249      FROM_HERE,
250      base::Bind(&ResourceMetadata::GetResourceEntryByPath,
251                 base::Unretained(this),
252                 file_path,
253                 entry_ptr),
254      base::Bind(&RunGetResourceEntryCallback, callback, base::Passed(&entry)));
255}
256
257FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
258                                                   ResourceEntry* out_entry) {
259  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
260  DCHECK(out_entry);
261
262  std::string id;
263  FileError error = GetIdByPath(path, &id);
264  if (error != FILE_ERROR_OK)
265    return error;
266
267  return GetResourceEntryById(id, out_entry);
268}
269
270void ResourceMetadata::ReadDirectoryByPathOnUIThread(
271    const base::FilePath& file_path,
272    const ReadDirectoryCallback& callback) {
273  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
274  DCHECK(!callback.is_null());
275
276  scoped_ptr<ResourceEntryVector> entries(new ResourceEntryVector);
277  ResourceEntryVector* entries_ptr = entries.get();
278  base::PostTaskAndReplyWithResult(
279      blocking_task_runner_.get(),
280      FROM_HERE,
281      base::Bind(&ResourceMetadata::ReadDirectoryByPath,
282                 base::Unretained(this),
283                 file_path,
284                 entries_ptr),
285      base::Bind(&RunReadDirectoryCallback, callback, base::Passed(&entries)));
286}
287
288FileError ResourceMetadata::ReadDirectoryByPath(
289    const base::FilePath& path,
290    ResourceEntryVector* out_entries) {
291  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
292  DCHECK(out_entries);
293
294  std::string id;
295  FileError error = GetIdByPath(path, &id);
296  if (error != FILE_ERROR_OK)
297    return error;
298
299  ResourceEntry entry;
300  error = GetResourceEntryById(id, &entry);
301  if (error != FILE_ERROR_OK)
302    return error;
303
304  if (!entry.file_info().is_directory())
305    return FILE_ERROR_NOT_A_DIRECTORY;
306
307  std::vector<std::string> children;
308  storage_->GetChildren(id, &children);
309
310  ResourceEntryVector entries(children.size());
311  for (size_t i = 0; i < children.size(); ++i) {
312    if (!storage_->GetEntry(children[i], &entries[i]))
313      return FILE_ERROR_FAILED;
314  }
315  out_entries->swap(entries);
316  return FILE_ERROR_OK;
317}
318
319FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
320  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
321  // TODO(hashimoto): Return an error if the operation will result in having
322  // multiple entries with the same resource ID.
323
324  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
325    return FILE_ERROR_NO_LOCAL_SPACE;
326
327  ResourceEntry old_entry;
328  if (!storage_->GetEntry(entry.local_id(), &old_entry))
329    return FILE_ERROR_NOT_FOUND;
330
331  if (old_entry.parent_local_id().empty() ||  // Reject root.
332      old_entry.file_info().is_directory() !=  // Reject incompatible input.
333      entry.file_info().is_directory())
334    return FILE_ERROR_INVALID_OPERATION;
335
336  // Make sure that the new parent exists and it is a directory.
337  ResourceEntry new_parent;
338  if (!storage_->GetEntry(entry.parent_local_id(), &new_parent))
339    return FILE_ERROR_NOT_FOUND;
340
341  if (!new_parent.file_info().is_directory())
342    return FILE_ERROR_NOT_A_DIRECTORY;
343
344  // Remove from the old parent and add it to the new parent with the new data.
345  if (!PutEntryUnderDirectory(entry))
346    return FILE_ERROR_FAILED;
347  return FILE_ERROR_OK;
348}
349
350void ResourceMetadata::GetSubDirectoriesRecursively(
351    const std::string& id,
352    std::set<base::FilePath>* sub_directories) {
353  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
354
355  std::vector<std::string> children;
356  storage_->GetChildren(id, &children);
357  for (size_t i = 0; i < children.size(); ++i) {
358    ResourceEntry entry;
359    if (storage_->GetEntry(children[i], &entry) &&
360        entry.file_info().is_directory()) {
361      sub_directories->insert(GetFilePath(children[i]));
362      GetSubDirectoriesRecursively(children[i], sub_directories);
363    }
364  }
365}
366
367std::string ResourceMetadata::GetChildId(const std::string& parent_local_id,
368                                         const std::string& base_name) {
369  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
370  return storage_->GetChild(parent_local_id, base_name);
371}
372
373scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
374  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
375
376  return storage_->GetIterator();
377}
378
379base::FilePath ResourceMetadata::GetFilePath(const std::string& id) {
380  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
381
382  base::FilePath path;
383  ResourceEntry entry;
384  if (storage_->GetEntry(id, &entry)) {
385    if (!entry.parent_local_id().empty())
386      path = GetFilePath(entry.parent_local_id());
387    path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
388  }
389  return path;
390}
391
392FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
393                                        std::string* out_id) {
394  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
395
396  // Start from the root.
397  std::vector<base::FilePath::StringType> components;
398  file_path.GetComponents(&components);
399  if (components.empty() || components[0] != util::kDriveGrandRootDirName)
400    return FILE_ERROR_NOT_FOUND;
401
402  // Iterate over the remaining components.
403  std::string id = util::kDriveGrandRootLocalId;
404  for (size_t i = 1; i < components.size(); ++i) {
405    const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
406    id = storage_->GetChild(id, component);
407    if (id.empty())
408      return FILE_ERROR_NOT_FOUND;
409  }
410  *out_id = id;
411  return FILE_ERROR_OK;
412}
413
414FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id,
415                                              std::string* out_local_id) {
416  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
417
418  return storage_->GetIdByResourceId(resource_id, out_local_id) ?
419      FILE_ERROR_OK : FILE_ERROR_NOT_FOUND;
420}
421
422bool ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) {
423  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
424  DCHECK(!entry.local_id().empty());
425  DCHECK(!entry.parent_local_id().empty());
426
427  ResourceEntry updated_entry(entry);
428
429  // The entry name may have been changed due to prior name de-duplication.
430  // We need to first restore the file name based on the title before going
431  // through name de-duplication again when it is added to another directory.
432  SetBaseNameFromTitle(&updated_entry);
433
434  // Do file name de-duplication - Keep changing |entry|'s name until there is
435  // no other entry with the same name under the parent.
436  int modifier = 0;
437  std::string new_base_name = updated_entry.base_name();
438  while (true) {
439    const std::string existing_entry_id =
440        storage_->GetChild(entry.parent_local_id(), new_base_name);
441    if (existing_entry_id.empty() || existing_entry_id == entry.local_id())
442      break;
443
444    base::FilePath new_path =
445        base::FilePath::FromUTF8Unsafe(updated_entry.base_name());
446    new_path =
447        new_path.InsertBeforeExtension(base::StringPrintf(" (%d)", ++modifier));
448    // The new filename must be different from the previous one.
449    DCHECK_NE(new_base_name, new_path.AsUTF8Unsafe());
450    new_base_name = new_path.AsUTF8Unsafe();
451  }
452  updated_entry.set_base_name(new_base_name);
453
454  // Add the entry to resource map.
455  return storage_->PutEntry(updated_entry);
456}
457
458bool ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
459  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
460
461  ResourceEntry entry;
462  if (!storage_->GetEntry(id, &entry))
463    return false;
464
465  if (entry.file_info().is_directory()) {
466    std::vector<std::string> children;
467    storage_->GetChildren(id, &children);
468    for (size_t i = 0; i < children.size(); ++i) {
469      if (!RemoveEntryRecursively(children[i]))
470        return false;
471    }
472  }
473  return storage_->RemoveEntry(id);
474}
475
476}  // namespace internal
477}  // namespace drive
478