Commit 0de93615 authored by hirono's avatar hirono Committed by Commit bot

Files.app: Add JobEventRouter class that handles Job related events of Files.app.

The following CL replaces the existing code with the new class.

BUG=469039
TEST=JobEventRouterTest

Review URL: https://codereview.chromium.org/1023923002

Cr-Commit-Position: refs/heads/master@{#321532}
parent 1535e6a9
// Copyright 2015 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/extensions/file_manager/job_event_router.h"
#include <cmath>
#include "base/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace file_manager_private = extensions::api::file_manager_private;
namespace file_manager {
namespace {
// Utility function to check if |job_info| is a file uploading job.
bool IsUploadJob(drive::JobType type) {
return (type == drive::TYPE_UPLOAD_NEW_FILE ||
type == drive::TYPE_UPLOAD_EXISTING_FILE);
}
} // namespace
JobEventRouter::JobEventRouter(const base::TimeDelta& event_delay)
: event_delay_(event_delay),
num_completed_bytes_(0),
num_total_bytes_(0),
weak_factory_(this) {
}
JobEventRouter::~JobEventRouter() {
}
void JobEventRouter::OnJobAdded(const drive::JobInfo& job_info) {
OnJobUpdated(job_info);
}
void JobEventRouter::OnJobUpdated(const drive::JobInfo& job_info) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!drive::IsActiveFileTransferJobInfo(job_info))
return;
// Add new job info.
UpdateBytes(job_info);
drive_jobs_[job_info.job_id] = make_linked_ptr(new drive::JobInfo(job_info));
ScheduleDriveFileTransferEvent(
job_info, file_manager_private::TRANSFER_STATE_IN_PROGRESS,
false /* immediate */);
}
void JobEventRouter::OnJobDone(const drive::JobInfo& job_info,
drive::FileError error) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!drive::IsActiveFileTransferJobInfo(job_info))
return;
const file_manager_private::TransferState state =
error == drive::FILE_ERROR_OK
? file_manager_private::TRANSFER_STATE_COMPLETED
: file_manager_private::TRANSFER_STATE_FAILED;
drive::JobInfo completed_job = job_info;
completed_job.num_completed_bytes = completed_job.num_total_bytes;
UpdateBytes(completed_job);
ScheduleDriveFileTransferEvent(job_info, state, true /* immediate */);
// Forget about the job.
drive_jobs_.erase(job_info.job_id);
if (!drive_jobs_.size()) {
num_completed_bytes_ = 0L;
num_total_bytes_ = 0L;
}
}
void JobEventRouter::UpdateBytes(const drive::JobInfo& job_info) {
int64 last_completed_bytes = 0;
int64 last_total_bytes = 0;
if (drive_jobs_.count(job_info.job_id)) {
last_completed_bytes = drive_jobs_[job_info.job_id]->num_completed_bytes;
last_total_bytes = drive_jobs_[job_info.job_id]->num_total_bytes;
}
num_completed_bytes_ += job_info.num_completed_bytes - last_completed_bytes;
num_total_bytes_ += job_info.num_total_bytes - last_total_bytes;
}
void JobEventRouter::ScheduleDriveFileTransferEvent(
const drive::JobInfo& job_info,
file_manager_private::TransferState state,
bool immediate) {
const bool no_pending_task = !pending_event_;
// Update the latest event.
pending_event_.reset(new file_manager_private::FileTransferStatus);
const GURL url =
ConvertDrivePathToFileSystemUrl(job_info.file_path, kFileManagerAppId);
pending_event_->file_url = url.spec();
pending_event_->transfer_state = state;
pending_event_->transfer_type =
IsUploadJob(job_info.job_type)
? file_manager_private::TRANSFER_TYPE_UPLOAD
: file_manager_private::TRANSFER_TYPE_DOWNLOAD;
if (immediate) {
SendDriveFileTransferEvent();
} else if (no_pending_task) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&JobEventRouter::SendDriveFileTransferEvent,
weak_factory_.GetWeakPtr()),
event_delay_);
}
}
void JobEventRouter::SendDriveFileTransferEvent() {
if (!pending_event_)
return;
// JavaScript does not have 64-bit integers. Instead we use double, which
// is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice
// in C++. Larger values are rounded.
pending_event_->num_total_jobs = drive_jobs_.size();
pending_event_->processed.reset(new double(num_completed_bytes_));
pending_event_->total.reset(new double(num_total_bytes_));
BroadcastEvent(
file_manager_private::OnFileTransfersUpdated::kEventName,
file_manager_private::OnFileTransfersUpdated::Create(*pending_event_));
pending_event_.reset();
}
} // namespace file_manager
// Copyright 2015 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.
#ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_JOB_EVENT_ROUTER_H_
#define CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_JOB_EVENT_ROUTER_H_
#include <map>
#include <string>
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/chromeos/drive/job_list.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "url/gurl.h"
namespace file_manager {
// Files.app's event router handling job related events.
class JobEventRouter : public drive::JobListObserver {
public:
explicit JobEventRouter(const base::TimeDelta& event_delay);
~JobEventRouter() override;
// drive::JobListObserver overrides.
void OnJobAdded(const drive::JobInfo& job_info) override;
void OnJobUpdated(const drive::JobInfo& job_info) override;
void OnJobDone(const drive::JobInfo& job_info,
drive::FileError error) override;
protected:
// Helper method to convert drive path to file system URL.
virtual GURL ConvertDrivePathToFileSystemUrl(const base::FilePath& path,
const std::string& id) const = 0;
// Helper method to dispatch events.
virtual void BroadcastEvent(const std::string& event_name,
scoped_ptr<base::ListValue> event_args) = 0;
private:
// Request sending transfer event with |job_info| and |state|.
// If |immediate| is true, the event will be dispatched synchronously.
// Otherwise, the event is throttled, or may be skipped.
void ScheduleDriveFileTransferEvent(
const drive::JobInfo& job_info,
extensions::api::file_manager_private::TransferState state,
bool immediate);
// Send transfer event requested by ScheduleDriveFileTransferEvent at last.
void SendDriveFileTransferEvent();
// Update |num_completed_bytes_| and |num_total_bytes_| depends on |job|.
void UpdateBytes(const drive::JobInfo& job_info);
// Delay time before sending progress events.
base::TimeDelta event_delay_;
// Set of job that are in the job schedular.
std::map<drive::JobID, linked_ptr<drive::JobInfo>> drive_jobs_;
// Pending transfer event. |ScheduleDriveFileTransferEvent| registers timeout
// callback to dispatch this.
scoped_ptr<extensions::api::file_manager_private::FileTransferStatus>
pending_event_;
// Computed bytes of tasks that have been processed. Once it completes all
// tasks, it clears the variable.
int64 num_completed_bytes_;
// Total bytes of tasks that have been processed. Once it completes all tasks,
// it clears the variable.
int64 num_total_bytes_;
// Thread checker.
base::ThreadChecker thread_checker_;
// Note: This should remain the last member so it'll be destroyed and
// invalidate the weak pointers before any other members are destroyed.
base::WeakPtrFactory<JobEventRouter> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(JobEventRouter);
};
} // namespace file_manager
#endif // CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_JOB_EVENT_ROUTER_H_
// Copyright 2015 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/extensions/file_manager/job_event_router.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace file_manager {
namespace {
class JobEventRouterImpl : public JobEventRouter {
public:
JobEventRouterImpl() : JobEventRouter(base::TimeDelta::FromMilliseconds(0)) {}
std::vector<linked_ptr<base::DictionaryValue>> events;
protected:
GURL ConvertDrivePathToFileSystemUrl(const base::FilePath& path,
const std::string& id) const override {
return GURL();
}
void BroadcastEvent(const std::string& event_name,
scoped_ptr<base::ListValue> event_args) override {
ASSERT_EQ(1u, event_args->GetSize());
const base::DictionaryValue* event;
event_args->GetDictionary(0, &event);
events.push_back(make_linked_ptr(event->DeepCopy()));
}
private:
DISALLOW_COPY_AND_ASSIGN(JobEventRouterImpl);
};
class JobEventRouterTest : public testing::Test {
protected:
void SetUp() override { job_event_router.reset(new JobEventRouterImpl()); }
drive::JobInfo CreateJobInfo(drive::JobID id,
int64 num_completed_bytes,
int64 num_total_bytes) {
drive::JobInfo job(drive::TYPE_DOWNLOAD_FILE);
job.job_id = id;
job.num_total_bytes = num_total_bytes;
job.num_completed_bytes = num_completed_bytes;
return job;
}
std::string GetEventString(size_t index, const std::string& name) {
std::string value;
job_event_router->events[index]->GetString(name, &value);
return value;
}
double GetEventDouble(size_t index, const std::string& name) {
double value = NAN;
job_event_router->events[index]->GetDouble(name, &value);
return value;
}
scoped_ptr<JobEventRouterImpl> job_event_router;
private:
base::MessageLoop message_loop_;
};
TEST_F(JobEventRouterTest, Basic) {
// Add a job.
job_event_router->OnJobAdded(CreateJobInfo(0, 0, 100));
// Event should be throttled.
ASSERT_EQ(0u, job_event_router->events.size());
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, job_event_router->events.size());
EXPECT_EQ("in_progress", GetEventString(0, "transferState"));
EXPECT_EQ(0.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(100.0f, GetEventDouble(0, "total"));
job_event_router->events.clear();
// Job is updated.
job_event_router->OnJobUpdated(CreateJobInfo(0, 50, 100));
job_event_router->OnJobUpdated(CreateJobInfo(0, 100, 100));
// Event should be throttled.
ASSERT_EQ(0u, job_event_router->events.size());
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, job_event_router->events.size());
EXPECT_EQ("in_progress", GetEventString(0, "transferState"));
EXPECT_EQ(100.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(100.0f, GetEventDouble(0, "total"));
job_event_router->events.clear();
// Complete first job.
job_event_router->OnJobDone(CreateJobInfo(0, 100, 100), drive::FILE_ERROR_OK);
// Complete event should not be throttled.
ASSERT_EQ(1u, job_event_router->events.size());
EXPECT_EQ("completed", GetEventString(0, "transferState"));
EXPECT_EQ(100.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(100.0f, GetEventDouble(0, "total"));
job_event_router->events.clear();
}
TEST_F(JobEventRouterTest, CompleteWithInvalidCompletedBytes) {
job_event_router->OnJobDone(CreateJobInfo(0, 50, 100), drive::FILE_ERROR_OK);
ASSERT_EQ(1u, job_event_router->events.size());
EXPECT_EQ("completed", GetEventString(0, "transferState"));
EXPECT_EQ(100.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(100.0f, GetEventDouble(0, "total"));
}
TEST_F(JobEventRouterTest, AnotherJobAddedBeforeComplete) {
job_event_router->OnJobAdded(CreateJobInfo(0, 0, 100));
job_event_router->OnJobUpdated(CreateJobInfo(0, 50, 100));
job_event_router->OnJobAdded(CreateJobInfo(1, 0, 100));
// Event should be throttled.
ASSERT_EQ(0u, job_event_router->events.size());
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, job_event_router->events.size());
EXPECT_EQ("in_progress", GetEventString(0, "transferState"));
EXPECT_EQ(50.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(200.0f, GetEventDouble(0, "total"));
job_event_router->events.clear();
job_event_router->OnJobDone(CreateJobInfo(0, 100, 100), drive::FILE_ERROR_OK);
job_event_router->OnJobDone(CreateJobInfo(1, 100, 100), drive::FILE_ERROR_OK);
// Complete event should not be throttled.
ASSERT_EQ(2u, job_event_router->events.size());
EXPECT_EQ("completed", GetEventString(0, "transferState"));
EXPECT_EQ(100.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(200.0f, GetEventDouble(0, "total"));
EXPECT_EQ("completed", GetEventString(1, "transferState"));
EXPECT_EQ(200.0f, GetEventDouble(1, "processed"));
EXPECT_EQ(200.0f, GetEventDouble(1, "total"));
}
TEST_F(JobEventRouterTest, AnotherJobAddedAfterComplete) {
job_event_router->OnJobAdded(CreateJobInfo(0, 0, 100));
job_event_router->OnJobUpdated(CreateJobInfo(0, 50, 100));
job_event_router->OnJobDone(CreateJobInfo(0, 100, 100), drive::FILE_ERROR_OK);
job_event_router->OnJobAdded(CreateJobInfo(1, 0, 100));
job_event_router->OnJobDone(CreateJobInfo(1, 100, 100), drive::FILE_ERROR_OK);
// Complete event should not be throttled.
ASSERT_EQ(2u, job_event_router->events.size());
EXPECT_EQ("completed", GetEventString(0, "transferState"));
EXPECT_EQ(100.0f, GetEventDouble(0, "processed"));
// Total byte shold be reset when all tasks complete.
EXPECT_EQ(100.0f, GetEventDouble(0, "total"));
EXPECT_EQ("completed", GetEventString(1, "transferState"));
EXPECT_EQ(100.0f, GetEventDouble(1, "processed"));
EXPECT_EQ(100.0f, GetEventDouble(1, "total"));
}
TEST_F(JobEventRouterTest, UpdateTotalSizeAfterAdded) {
job_event_router->OnJobAdded(CreateJobInfo(0, 0, 0));
base::RunLoop().RunUntilIdle();
job_event_router->OnJobUpdated(CreateJobInfo(0, 0, 100));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(2u, job_event_router->events.size());
EXPECT_EQ("in_progress", GetEventString(0, "transferState"));
EXPECT_EQ(0.0f, GetEventDouble(0, "processed"));
EXPECT_EQ(0.0f, GetEventDouble(0, "total"));
EXPECT_EQ("in_progress", GetEventString(1, "transferState"));
EXPECT_EQ(0.0f, GetEventDouble(1, "processed"));
EXPECT_EQ(100.0f, GetEventDouble(1, "total"));
}
} // namespace
} // namespace file_manager
......@@ -1027,6 +1027,8 @@
'browser/chromeos/extensions/file_manager/file_browser_handler_api.cc',
'browser/chromeos/extensions/file_manager/file_browser_handler_api.h',
'browser/chromeos/extensions/file_manager/file_manager_private_api_functions.h',
'browser/chromeos/extensions/file_manager/job_event_router.cc',
'browser/chromeos/extensions/file_manager/job_event_router.h',
'browser/chromeos/extensions/file_manager/private_api_base.cc',
'browser/chromeos/extensions/file_manager/private_api_base.h',
'browser/chromeos/extensions/file_manager/private_api_dialog.cc',
......
......@@ -1180,6 +1180,7 @@
'browser/chromeos/extensions/device_local_account_management_policy_provider_unittest.cc',
'browser/chromeos/extensions/external_cache_unittest.cc',
'browser/chromeos/extensions/file_manager/device_event_router_unittest.cc',
'browser/chromeos/extensions/file_manager/job_event_router_unittest.cc',
'browser/chromeos/extensions/wallpaper_private_api_unittest.cc',
'browser/chromeos/external_metrics_unittest.cc',
'browser/chromeos/file_manager/file_tasks_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment