1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/google_apis/operation_registry.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "content/public/browser/browser_thread.h"
10 using content::BrowserThread
;
14 const int64 kNotificationFrequencyInMilliseconds
= 1000;
18 namespace google_apis
{
20 std::string
OperationTypeToString(OperationType type
) {
22 case OPERATION_UPLOAD
: return "upload";
23 case OPERATION_DOWNLOAD
: return "download";
24 case OPERATION_OTHER
: return "other";
27 return "unknown_transfer_state";
30 std::string
OperationTransferStateToString(OperationTransferState state
) {
32 case OPERATION_NOT_STARTED
: return "not_started";
33 case OPERATION_STARTED
: return "started";
34 case OPERATION_IN_PROGRESS
: return "in_progress";
35 case OPERATION_COMPLETED
: return "completed";
36 case OPERATION_FAILED
: return "failed";
37 // Suspended state is opaque to users and looks as same as "in_progress".
38 case OPERATION_SUSPENDED
: return "in_progress";
41 return "unknown_transfer_state";
44 OperationProgressStatus::OperationProgressStatus(OperationType type
,
45 const base::FilePath
& path
)
49 transfer_state(OPERATION_NOT_STARTED
),
54 std::string
OperationProgressStatus::DebugString() const {
57 str
+= base::IntToString(operation_id
);
59 str
+= OperationTypeToString(operation_type
);
61 str
+= file_path
.AsUTF8Unsafe();
63 str
+= OperationTransferStateToString(transfer_state
);
65 str
+= base::Int64ToString(progress_current
);
67 str
+= base::Int64ToString(progress_total
);
71 OperationRegistry::Operation::Operation(OperationRegistry
* registry
)
72 : registry_(registry
),
73 progress_status_(OPERATION_OTHER
, base::FilePath()) {
76 OperationRegistry::Operation::Operation(OperationRegistry
* registry
,
78 const base::FilePath
& path
)
79 : registry_(registry
),
80 progress_status_(type
, path
) {
83 OperationRegistry::Operation::~Operation() {
84 DCHECK(progress_status_
.transfer_state
== OPERATION_COMPLETED
||
85 progress_status_
.transfer_state
== OPERATION_SUSPENDED
||
86 progress_status_
.transfer_state
== OPERATION_FAILED
);
89 void OperationRegistry::Operation::Cancel() {
91 NotifyFinish(OPERATION_FAILED
);
94 void OperationRegistry::Operation::NotifyStart() {
95 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
96 // Some operations may be restarted. Report only the first "start".
97 if (progress_status_
.transfer_state
== OPERATION_NOT_STARTED
) {
98 progress_status_
.transfer_state
= OPERATION_STARTED
;
99 progress_status_
.start_time
= base::Time::Now();
100 registry_
->OnOperationStart(this, &progress_status_
.operation_id
);
104 void OperationRegistry::Operation::NotifyProgress(
105 int64 current
, int64 total
) {
106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
107 DCHECK(progress_status_
.transfer_state
>= OPERATION_STARTED
);
108 progress_status_
.transfer_state
= OPERATION_IN_PROGRESS
;
109 progress_status_
.progress_current
= current
;
110 progress_status_
.progress_total
= total
;
111 registry_
->OnOperationProgress(progress_status().operation_id
);
114 void OperationRegistry::Operation::NotifyFinish(
115 OperationTransferState status
) {
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
117 DCHECK(progress_status_
.transfer_state
>= OPERATION_STARTED
);
118 DCHECK(status
== OPERATION_COMPLETED
|| status
== OPERATION_FAILED
);
119 progress_status_
.transfer_state
= status
;
120 registry_
->OnOperationFinish(progress_status().operation_id
);
123 void OperationRegistry::Operation::NotifySuspend() {
124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
125 DCHECK(progress_status_
.transfer_state
>= OPERATION_STARTED
);
126 progress_status_
.transfer_state
= OPERATION_SUSPENDED
;
127 registry_
->OnOperationSuspend(progress_status().operation_id
);
130 void OperationRegistry::Operation::NotifyResume() {
131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
132 if (progress_status_
.transfer_state
== OPERATION_NOT_STARTED
) {
133 progress_status_
.transfer_state
= OPERATION_IN_PROGRESS
;
134 registry_
->OnOperationResume(this, &progress_status_
);
138 OperationRegistry::OperationRegistry()
139 : do_notification_frequency_control_(true) {
140 in_flight_operations_
.set_check_on_null_data(true);
143 OperationRegistry::~OperationRegistry() {
144 DCHECK(in_flight_operations_
.IsEmpty());
147 void OperationRegistry::AddObserver(OperationRegistryObserver
* observer
) {
148 observer_list_
.AddObserver(observer
);
151 void OperationRegistry::RemoveObserver(OperationRegistryObserver
* observer
) {
152 observer_list_
.RemoveObserver(observer
);
155 void OperationRegistry::DisableNotificationFrequencyControlForTest() {
156 do_notification_frequency_control_
= false;
159 void OperationRegistry::CancelAll() {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
162 for (OperationIDMap::iterator
iter(&in_flight_operations_
);
165 Operation
* operation
= iter
.GetCurrentValue();
166 CancelOperation(operation
);
167 // CancelOperation may immediately trigger OnOperationFinish and remove the
168 // operation from the map, but IDMap is designed to be safe on such remove
173 bool OperationRegistry::CancelForFilePath(const base::FilePath
& file_path
) {
174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
176 for (OperationIDMap::iterator
iter(&in_flight_operations_
);
179 Operation
* operation
= iter
.GetCurrentValue();
180 if (operation
->progress_status().file_path
== file_path
) {
181 CancelOperation(operation
);
188 void OperationRegistry::CancelOperation(Operation
* operation
) {
189 if (operation
->progress_status().transfer_state
== OPERATION_SUSPENDED
) {
190 // SUSPENDED operation already completed its job (like calling back to
191 // its client code). Invoking operation->Cancel() again on it is a kind of
192 // 'double deletion'. So here we directly call OnOperationFinish and just
193 // unregister the operation from the registry.
194 // TODO(kinaba): http://crbug.com/164098 Get rid of the hack.
195 OnOperationFinish(operation
->progress_status().operation_id
);
201 void OperationRegistry::OnOperationStart(
202 OperationRegistry::Operation
* operation
,
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
206 *id
= in_flight_operations_
.Add(operation
);
207 DVLOG(1) << "GDataOperation[" << *id
<< "] started.";
208 if (IsFileTransferOperation(operation
))
209 NotifyStatusToObservers();
212 void OperationRegistry::OnOperationProgress(OperationID id
) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
215 Operation
* operation
= in_flight_operations_
.Lookup(id
);
218 DVLOG(1) << "GDataOperation[" << id
<< "] "
219 << operation
->progress_status().DebugString();
220 if (IsFileTransferOperation(operation
))
221 NotifyStatusToObservers();
224 void OperationRegistry::OnOperationFinish(OperationID id
) {
225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
227 Operation
* operation
= in_flight_operations_
.Lookup(id
);
230 DVLOG(1) << "GDataOperation[" << id
<< "] finished.";
231 if (IsFileTransferOperation(operation
))
232 NotifyStatusToObservers();
233 in_flight_operations_
.Remove(id
);
236 void OperationRegistry::OnOperationResume(
237 OperationRegistry::Operation
* operation
,
238 OperationProgressStatus
* new_status
) {
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
241 // Find the corresponding suspended task.
242 Operation
* suspended
= NULL
;
243 for (OperationIDMap::iterator
iter(&in_flight_operations_
);
246 Operation
* in_flight_operation
= iter
.GetCurrentValue();
247 const OperationProgressStatus
& status
=
248 in_flight_operation
->progress_status();
249 if (status
.transfer_state
== OPERATION_SUSPENDED
&&
250 status
.file_path
== operation
->progress_status().file_path
) {
251 suspended
= in_flight_operation
;
257 // Preceding suspended operations was not found. Assume it was canceled.
259 // operation->Cancel() needs to be called to properly shut down the
260 // current operation, but operation->Cancel() tries to unregister itself
261 // from the registry. So, as a hack, temporarily assign it an ID.
262 // TODO(kinaba): http://crbug.com/164098 Get rid of it.
263 new_status
->operation_id
= in_flight_operations_
.Add(operation
);
264 CancelOperation(operation
);
268 // Copy the progress status.
269 const OperationProgressStatus
& old_status
= suspended
->progress_status();
270 OperationID old_id
= old_status
.operation_id
;
272 new_status
->progress_current
= old_status
.progress_current
;
273 new_status
->progress_total
= old_status
.progress_total
;
274 new_status
->start_time
= old_status
.start_time
;
276 // Remove the old one and initiate the new operation.
277 in_flight_operations_
.Remove(old_id
);
278 new_status
->operation_id
= in_flight_operations_
.Add(operation
);
279 DVLOG(1) << "GDataOperation[" << old_id
<< " -> " <<
280 new_status
->operation_id
<< "] resumed.";
281 if (IsFileTransferOperation(operation
))
282 NotifyStatusToObservers();
285 void OperationRegistry::OnOperationSuspend(OperationID id
) {
286 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
288 Operation
* operation
= in_flight_operations_
.Lookup(id
);
291 DVLOG(1) << "GDataOperation[" << id
<< "] suspended.";
292 if (IsFileTransferOperation(operation
))
293 NotifyStatusToObservers();
296 bool OperationRegistry::IsFileTransferOperation(
297 const Operation
* operation
) const {
298 OperationType type
= operation
->progress_status().operation_type
;
299 return type
== OPERATION_UPLOAD
|| type
== OPERATION_DOWNLOAD
;
302 OperationProgressStatusList
OperationRegistry::GetProgressStatusList() {
303 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
305 OperationProgressStatusList status_list
;
306 for (OperationIDMap::const_iterator
iter(&in_flight_operations_
);
309 const Operation
* operation
= iter
.GetCurrentValue();
310 if (IsFileTransferOperation(operation
))
311 status_list
.push_back(operation
->progress_status());
316 bool OperationRegistry::ShouldNotifyStatusNow(
317 const OperationProgressStatusList
& list
) {
318 if (!do_notification_frequency_control_
)
321 base::Time now
= base::Time::Now();
323 // If it is a first event, or some time abnormality is detected, we should
324 // not skip this notification.
325 if (last_notification_
.is_null() || now
< last_notification_
) {
326 last_notification_
= now
;
330 // If sufficiently long time has elapsed since the previous event, we should
331 // not skip this notification.
332 if ((now
- last_notification_
).InMilliseconds() >=
333 kNotificationFrequencyInMilliseconds
) {
334 last_notification_
= now
;
338 // If important events (OPERATION_STARTED, COMPLETED, or FAILED) are there,
339 // we should not skip this notification.
340 for (size_t i
= 0; i
< list
.size(); ++i
) {
341 if (list
[i
].transfer_state
!= OPERATION_IN_PROGRESS
) {
342 last_notification_
= now
;
347 // Otherwise we can skip it.
351 void OperationRegistry::NotifyStatusToObservers() {
352 OperationProgressStatusList
list(GetProgressStatusList());
353 if (ShouldNotifyStatusNow(list
))
354 FOR_EACH_OBSERVER(OperationRegistryObserver
,
356 OnProgressUpdate(list
));
359 } // namespace google_apis