chromium/chrome/browser/ash/file_manager/snapshot_manager.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/file_manager/snapshot_manager.h"

#include <utility>

#include "base/containers/circular_deque.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/system/sys_info.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/common/task_util.h"
#include "storage/browser/blob/shareable_file_reference.h"
#include "storage/browser/file_system/file_system_backend.h"
#include "storage/browser/file_system/file_system_context.h"
#include "third_party/cros_system_api/constants/cryptohome.h"

namespace file_manager {
namespace {

typedef base::OnceCallback<void(int64_t)> GetNecessaryFreeSpaceCallback;

// Part of ComputeSpaceNeedToBeFreed.
int64_t ComputeSpaceNeedToBeFreedAfterGetMetadataAsync(
    const base::FilePath& path,
    int64_t snapshot_size) {
  int64_t free_size = base::SysInfo::AmountOfFreeDiskSpace(path);
  if (free_size < 0) {
    return -1;
  }

  // We need to keep cryptohome::kMinFreeSpaceInBytes free space even after
  // |snapshot_size| is occupied.
  free_size -= snapshot_size + cryptohome::kMinFreeSpaceInBytes;
  return (free_size < 0 ? -free_size : 0);
}

// Part of ComputeSpaceNeedToBeFreed.
void ComputeSpaceNeedToBeFreedAfterGetMetadata(
    const base::FilePath& path,
    GetNecessaryFreeSpaceCallback callback,
    base::File::Error result,
    const base::File::Info& file_info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  if (result != base::File::FILE_OK) {
    std::move(callback).Run(-1);
    return;
  }

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
      base::BindOnce(&ComputeSpaceNeedToBeFreedAfterGetMetadataAsync, path,
                     file_info.size),
      std::move(callback));
}

// Part of ComputeSpaceNeedToBeFreed.
void GetMetadataOnIOThread(const base::FilePath& path,
                           scoped_refptr<storage::FileSystemContext> context,
                           const storage::FileSystemURL& url,
                           GetNecessaryFreeSpaceCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  context->operation_runner()->GetMetadata(
      url, {storage::FileSystemOperation::GetMetadataField::kSize},
      base::BindOnce(&ComputeSpaceNeedToBeFreedAfterGetMetadata, path,
                     std::move(callback)));
}

// Computes the size of space that need to be __additionally__ made available
// in the |profile|'s data directory for taking the snapshot of |url|.
// Returns 0 if no additional space is required, or -1 in the case of an error.
void ComputeSpaceNeedToBeFreed(
    Profile* profile,
    scoped_refptr<storage::FileSystemContext> context,
    const storage::FileSystemURL& url,
    GetNecessaryFreeSpaceCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&GetMetadataOnIOThread, profile->GetPath(), context, url,
                     google_apis::CreateRelayCallback(std::move(callback))));
}

}  // namespace

class SnapshotManager::FileRefsHolder
    : public base::RefCountedThreadSafe<
          SnapshotManager::FileRefsHolder,
          content::BrowserThread::DeleteOnIOThread> {
 public:
  FileRefsHolder() = default;

  FileRefsHolder(const FileRefsHolder&) = delete;
  FileRefsHolder& operator=(const FileRefsHolder&) = delete;

  void FreeSpaceAndCreateSnapshotFile(
      scoped_refptr<storage::FileSystemContext> context,
      const storage::FileSystemURL& url,
      int64_t needed_space,
      LocalPathCallback callback);

  void OnCreateSnapshotFile(
      LocalPathCallback callback,
      base::File::Error result,
      const base::File::Info& file_info,
      const base::FilePath& platform_path,
      scoped_refptr<storage::ShareableFileReference> file_ref);

 private:
  // Struct for keeping the snapshot file reference with its file size used for
  // computing the necessity of clean up.
  struct FileReferenceWithSizeInfo {
    FileReferenceWithSizeInfo(
        scoped_refptr<storage::ShareableFileReference> ref,
        int64_t size)
        : file_ref(ref), file_size(size) {}
    FileReferenceWithSizeInfo(const FileReferenceWithSizeInfo& other) = default;
    ~FileReferenceWithSizeInfo() = default;

    scoped_refptr<storage::ShareableFileReference> file_ref;
    int64_t file_size;
  };

  friend struct content::BrowserThread::DeleteOnThread<
      content::BrowserThread::IO>;
  friend class base::DeleteHelper<FileRefsHolder>;

  ~FileRefsHolder() = default;

  base::circular_deque<FileReferenceWithSizeInfo> file_refs_;
};

void SnapshotManager::FileRefsHolder::FreeSpaceAndCreateSnapshotFile(
    scoped_refptr<storage::FileSystemContext> context,
    const storage::FileSystemURL& url,
    int64_t needed_space,
    LocalPathCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (needed_space < 0) {
    std::move(callback).Run(base::FilePath());
    return;
  }

  // Free up to the required size.
  while (needed_space > 0 && !file_refs_.empty()) {
    needed_space -= file_refs_.front().file_size;
    file_refs_.pop_front();
  }

  // If we still could not achieve the space requirement, abort with failure.
  if (needed_space > 0) {
    std::move(callback).Run(base::FilePath());
    return;
  }

  context->operation_runner()->CreateSnapshotFile(
      url, base::BindOnce(&FileRefsHolder::OnCreateSnapshotFile, this,
                          std::move(callback)));
}

void SnapshotManager::FileRefsHolder::OnCreateSnapshotFile(
    LocalPathCallback callback,
    base::File::Error result,
    const base::File::Info& file_info,
    const base::FilePath& platform_path,
    scoped_refptr<storage::ShareableFileReference> file_ref) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (result != base::File::FILE_OK) {
    std::move(callback).Run(base::FilePath());
    return;
  }

  file_refs_.push_back(
      FileReferenceWithSizeInfo(std::move(file_ref), file_info.size));
  std::move(callback).Run(platform_path);
}

SnapshotManager::SnapshotManager(Profile* profile)
    : profile_(profile), holder_(base::MakeRefCounted<FileRefsHolder>()) {}

SnapshotManager::~SnapshotManager() = default;

void SnapshotManager::CreateManagedSnapshot(
    const base::FilePath& absolute_file_path,
    LocalPathCallback callback) {
  scoped_refptr<storage::FileSystemContext> context(
      util::GetFileManagerFileSystemContext(profile_));
  DCHECK(context.get());

  GURL url;
  if (!util::ConvertAbsoluteFilePathToFileSystemUrl(
          profile_, absolute_file_path, util::GetFileManagerURL(), &url)) {
    std::move(callback).Run(base::FilePath());
    return;
  }
  storage::FileSystemURL filesystem_url =
      context->CrackURLInFirstPartyContext(url);

  ComputeSpaceNeedToBeFreed(
      profile_, context, filesystem_url,
      base::BindOnce(&SnapshotManager::CreateManagedSnapshotAfterSpaceComputed,
                     weak_ptr_factory_.GetWeakPtr(), filesystem_url,
                     std::move(callback)));
}

void SnapshotManager::CreateManagedSnapshotAfterSpaceComputed(
    const storage::FileSystemURL& filesystem_url,
    LocalPathCallback callback,
    int64_t needed_space) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  scoped_refptr<storage::FileSystemContext> context(
      util::GetFileManagerFileSystemContext(profile_));
  DCHECK(context.get());

  // Free up space if needed and start creating the snapshot.
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&FileRefsHolder::FreeSpaceAndCreateSnapshotFile, holder_,
                     context, filesystem_url, needed_space,
                     google_apis::CreateRelayCallback(std::move(callback))));
}

}  // namespace file_manager