text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/chromeos/file_manager/snapshot_manager.h"
  
  #include <utility>
  
  #include "base/bind.h"
  #include "base/containers/circular_deque.h"
  #include "base/files/file.h"
  #include "base/memory/ref_counted.h"
  #include "base/system/sys_info.h"
  #include "base/task/post_task.h"
  #include "base/task/thread_pool.h"
  #include "chrome/browser/chromeos/file_manager/app_id.h"
  #include "chrome/browser/chromeos/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/drive/task_util.h"
  #include "storage/browser/blob/shareable_file_reference.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::GET_METADATA_FIELD_SIZE,
        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);
    base::PostTask(
        FROM_HERE, {content::BrowserThread::IO},
        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;
  
    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_;
  
    DISALLOW_COPY_AND_ASSIGN(FileRefsHolder);
  };
  
  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::GetFileSystemContextForExtensionId(profile_, kFileManagerAppId));
    DCHECK(context.get());
  
    GURL url;
    if (!util::ConvertAbsoluteFilePathToFileSystemUrl(
            profile_, absolute_file_path, kFileManagerAppId, &url)) {
      std::move(callback).Run(base::FilePath());
      return;
    }
    storage::FileSystemURL filesystem_url = context->CrackURL(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::GetFileSystemContextForExtensionId(profile_, kFileManagerAppId));
    DCHECK(context.get());
  
    // Free up space if needed and start creating the snapshot.
    base::PostTask(
        FROM_HERE, {content::BrowserThread::IO},
        base::BindOnce(&FileRefsHolder::FreeSpaceAndCreateSnapshotFile, holder_,
                       context, filesystem_url, needed_space,
                       google_apis::CreateRelayCallback(std::move(callback))));
  }
  
  }  // namespace file_manager
  