// 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