blob: b940d8d91b2851f953a41fc179abc88d5823c00b [file] [log] [blame]
// Copyright 2021 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/ui/ash/projector/pending_screencast_manager.h"
#include <map>
#include <vector>
#include "ash/components/drivefs/mojom/drivefs.mojom.h"
#include "ash/public/cpp/projector/projector_controller.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/projector/projector_utils.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
// Returned by GetScreencastContainerSize(const base::FilePath& relative_path)
// when the given `relative_path` is an invalid screencast container.
constexpr int64_t kScreencastSizeUnavailable = -1;
constexpr base::FilePath::CharType kMediaExtension[] =
FILE_PATH_LITERAL(".webm");
const std::string GetMetadataFileExtension() {
return base::StrCat({".", ash::kProjectorMetadataFileExtension});
}
bool IsWebmOrProjectorFile(const base::FilePath& path) {
return path.MatchesExtension(kMediaExtension) ||
path.MatchesExtension(GetMetadataFileExtension());
}
drive::DriveIntegrationService* GetDriveIntegrationServiceForActiveProfile() {
return drive::DriveIntegrationServiceFactory::FindForProfile(
ProfileManager::GetActiveUserProfile());
}
drivefs::DriveFsHost* GetDriveFsHostForActiveProfile() {
auto* drivefs_integration = GetDriveIntegrationServiceForActiveProfile();
return drivefs_integration ? drivefs_integration->GetDriveFsHost() : nullptr;
}
// Return the size in bytes of screencast if `absolute_path` is a valid
// screencast container, otherwise return kScreencastSizeUnavailable. A valid
// screencast should have at least 1 media file and 1 metadata file. The
// 'absolute_path' looks like
// "/{drivefs mounted point}/root/{folder path in drive}".
int64_t GetScreencastContainerSize(const base::FilePath& absolute_path) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (!base::PathExists(absolute_path))
return kScreencastSizeUnavailable;
int size_of_metadata_files = 0;
int size_of_media_files = 0;
base::FileEnumerator files(absolute_path, /*recursive=*/false,
base::FileEnumerator::FILES);
const std::string metadata_extension = GetMetadataFileExtension();
for (base::FilePath path = files.Next(); !path.empty(); path = files.Next()) {
if (path.MatchesExtension(GetMetadataFileExtension()))
size_of_metadata_files += files.GetInfo().GetSize();
else if (path.MatchesExtension(kMediaExtension))
size_of_media_files += files.GetInfo().GetSize();
}
return size_of_media_files > 0 && size_of_metadata_files > 0
? size_of_media_files + size_of_metadata_files
: kScreencastSizeUnavailable;
}
// The `pending_webm_or_projector_events` are new pending ".webm" or
// ".projector" files' events. Checks whether these files are valid screencast
// files, calculate the upload progress, and returns valid pending screencasts.
ash::PendingScreencastSet ProcessAndGenerateNewScreencasts(
const std::vector<drivefs::mojom::ItemEvent>&
pending_webm_or_projector_events,
drive::DriveIntegrationService* drivefs_integration) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
const base::FilePath drivefs_mounted_point =
drivefs_integration->GetMountPointPath();
// The valid screencasts set.
ash::PendingScreencastSet screencasts;
if (!drivefs_integration->IsMounted() ||
!base::PathExists(drivefs_mounted_point)) {
return screencasts;
}
// A map of container directory path to pending screencast. Each screencast
// has a unique container directory path in DriveFS.
std::map<base::FilePath, ash::PendingScreencast> container_to_screencasts;
// The `pending_event.path` is the file path in drive. It looks like
// "/root/{folder path in drive}/{file name}".
for (const auto& pending_event : pending_webm_or_projector_events) {
// `container_dir` is the parent folder of `pending_event.path` in drive. It
// looks like "/root/{folder path in drive}".
const base::FilePath container_dir =
base::FilePath(pending_event.path).DirName();
ash::PendingScreencast screencast;
screencast.container_dir = container_dir;
// The display name of the a pending screencast is the name of the container
// folder name of this screencast.
screencast.name = container_dir.BaseName().value();
// During this loop, items of multiple events might be under the same
// folder.
auto iter = container_to_screencasts.find(container_dir);
if (iter != container_to_screencasts.end()) {
// Calculates remaining untranferred bytes of a screencast by adding up
// its transferred bytes of its files. `pending_event.bytes_to_transfer`
// is the total bytes of current file.
// TODO(b/209854146) Not all files appear in
// `pending_webm_or_projector_events.bytes_transferred`. The missing files
// might be uploaded or not uploaded. To get an accurate
// `bytes_transferred`, use DriveIntegrationService::GetMetadata().
iter->second.bytes_transferred += pending_event.bytes_transferred;
// Skips getting the size of a folder if it has been validated before.
continue;
}
base::FilePath root("/");
base::FilePath container_absolute_dir(drivefs_mounted_point);
root.AppendRelativePath(container_dir, &container_absolute_dir);
const int64_t total_size_in_bytes =
GetScreencastContainerSize(container_absolute_dir);
if (total_size_in_bytes != -1) {
screencast.total_size_in_bytes = total_size_in_bytes;
screencast.bytes_transferred = pending_event.bytes_transferred;
container_to_screencasts[container_dir] = screencast;
}
}
for (const auto& pair : container_to_screencasts)
screencasts.insert(pair.second);
return screencasts;
}
} // namespace
PendingSreencastManager::PendingSreencastManager(
PendingScreencastChangeCallback pending_screencast_change_callback)
: pending_screencast_change_callback_(pending_screencast_change_callback),
blocking_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
session_manager::SessionManager* session_manager =
session_manager::SessionManager::Get();
if (session_manager)
session_manager->AddObserver(this);
}
PendingSreencastManager::~PendingSreencastManager() {
session_manager::SessionManager* session_manager =
session_manager::SessionManager::Get();
if (session_manager) {
session_manager->RemoveObserver(this);
auto* drivefs_host = GetDriveFsHostForActiveProfile();
if (drivefs_host)
drivefs_host->RemoveObserver(this);
}
}
void PendingSreencastManager::OnUnmounted() {
if (!pending_screencast_cache_.empty()) {
pending_screencast_cache_.clear();
// Since DriveFS is unmounted, screencasts stop uploading. Notifies pending
// screencast status has changed.
pending_screencast_change_callback_.Run(pending_screencast_cache_);
}
}
// TODO(b/200343894): OnSyncingStatusUpdate() gets called for both upload and
// download event. Find a way to filter out the upload event.
void PendingSreencastManager::OnSyncingStatusUpdate(
const drivefs::mojom::SyncingStatus& status) {
std::vector<drivefs::mojom::ItemEvent> pending_webm_or_projector_events;
for (const auto& event : status.item_events) {
base::FilePath pending_file = base::FilePath(event->path);
bool pending =
event->state == drivefs::mojom::ItemEvent::State::kQueued ||
event->state == drivefs::mojom::ItemEvent::State::kInProgress;
// Filters pending ".webm" or ".projector".
if (!pending || !IsWebmOrProjectorFile(pending_file))
continue;
pending_webm_or_projector_events.push_back(
drivefs::mojom::ItemEvent(*event.get()));
}
// The `task` is a blocking I/O operation while `reply` runs on current
// thread.
blocking_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(ProcessAndGenerateNewScreencasts,
std::move(pending_webm_or_projector_events),
GetDriveIntegrationServiceForActiveProfile()),
base::BindOnce(
&PendingSreencastManager::OnProcessAndGenerateNewScreencastsFinished,
weak_ptr_factory_.GetWeakPtr()));
}
// TODO(b/200179137): Handle drive full cannot upload error. Maybe send
// Notification and show failed file on gallery?
void PendingSreencastManager::OnError(const drivefs::mojom::DriveError& error) {
}
void PendingSreencastManager::OnUserSessionStarted(bool is_primary_user) {
auto* profile = ProfileManager::GetActiveUserProfile();
if (!IsProjectorAllowedForProfile(profile))
return;
auto* drivefs_host = GetDriveFsHostForActiveProfile();
if (drivefs_host)
GetDriveFsHostForActiveProfile()->AddObserver(this);
}
const ash::PendingScreencastSet&
PendingSreencastManager::GetPendingScreencasts() const {
return pending_screencast_cache_;
}
void PendingSreencastManager::OnProcessAndGenerateNewScreencastsFinished(
const ash::PendingScreencastSet& screencasts) {
// Return if pending screencasts didn't change.
if (screencasts == pending_screencast_cache_)
return;
pending_screencast_cache_ = screencasts;
// Notifies pending screencast status changed.
pending_screencast_change_callback_.Run(pending_screencast_cache_);
}