Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/drive/drive_sync_client.h" | 5 #include "chrome/browser/chromeos/drive/drive_sync_client.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 97 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | 97 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { |
| 98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 99 } | 99 } |
| 100 | 100 |
| 101 DriveSyncClient::~DriveSyncClient() { | 101 DriveSyncClient::~DriveSyncClient() { |
| 102 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 102 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 103 if (file_system_) | 103 if (file_system_) |
| 104 file_system_->RemoveObserver(this); | 104 file_system_->RemoveObserver(this); |
| 105 if (cache_) | 105 if (cache_) |
| 106 cache_->RemoveObserver(this); | 106 cache_->RemoveObserver(this); |
| 107 net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); | 107 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); |
| 108 } | 108 } |
| 109 | 109 |
| 110 void DriveSyncClient::Initialize() { | 110 void DriveSyncClient::Initialize() { |
| 111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 112 | 112 |
| 113 file_system_->AddObserver(this); | 113 file_system_->AddObserver(this); |
| 114 cache_->AddObserver(this); | 114 cache_->AddObserver(this); |
| 115 | 115 |
| 116 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); | 116 net::NetworkChangeNotifier::AddNetworkChangeObserver(this); |
| 117 | 117 |
| 118 registrar_->Init(profile_->GetPrefs()); | 118 registrar_->Init(profile_->GetPrefs()); |
| 119 base::Closure callback = base::Bind( | 119 base::Closure callback = base::Bind( |
| 120 &DriveSyncClient::OnDriveSyncPreferenceChanged, base::Unretained(this)); | 120 &DriveSyncClient::OnDriveSyncPreferenceChanged, base::Unretained(this)); |
| 121 registrar_->Add(prefs::kDisableDrive, callback); | 121 registrar_->Add(prefs::kDisableDrive, callback); |
| 122 registrar_->Add(prefs::kDisableDriveOverCellular, callback); | 122 registrar_->Add(prefs::kDisableDriveOverCellular, callback); |
| 123 } | 123 } |
| 124 | 124 |
| 125 void DriveSyncClient::StartProcessingBacklog() { | 125 void DriveSyncClient::StartProcessingBacklog() { |
| 126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 147 SyncType sync_type) const { | 147 SyncType sync_type) const { |
| 148 std::vector<std::string> resource_ids; | 148 std::vector<std::string> resource_ids; |
| 149 for (size_t i = 0; i < queue_.size(); ++i) { | 149 for (size_t i = 0; i < queue_.size(); ++i) { |
| 150 const SyncTask& sync_task = queue_[i]; | 150 const SyncTask& sync_task = queue_[i]; |
| 151 if (sync_task.sync_type == sync_type) | 151 if (sync_task.sync_type == sync_type) |
| 152 resource_ids.push_back(sync_task.resource_id); | 152 resource_ids.push_back(sync_task.resource_id); |
| 153 } | 153 } |
| 154 return resource_ids; | 154 return resource_ids; |
| 155 } | 155 } |
| 156 | 156 |
| 157 void DriveSyncClient::StartSyncLoop() { | 157 void DriveSyncClient::StartSyncLoop(bool force_offline) { |
| 158 if (!sync_loop_is_running_) | 158 if (!sync_loop_is_running_) |
| 159 DoSyncLoop(); | 159 DoSyncLoop(force_offline); |
| 160 } | 160 } |
| 161 | 161 |
| 162 void DriveSyncClient::DoSyncLoop() { | 162 void DriveSyncClient::DoSyncLoop(bool force_offline) { |
| 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 164 | 164 |
| 165 if (ShouldStopSyncLoop()) { | 165 if (ShouldStopSyncLoop(force_offline)) { |
| 166 // Note that |queue_| is not cleared so the sync loop can resume. | 166 // Note that |queue_| is not cleared so the sync loop can resume. |
| 167 sync_loop_is_running_ = false; | 167 sync_loop_is_running_ = false; |
| 168 FOR_EACH_OBSERVER(DriveSyncClientObserver, observers_, | 168 FOR_EACH_OBSERVER(DriveSyncClientObserver, observers_, |
| 169 OnSyncClientStopped()); | 169 OnSyncClientStopped()); |
| 170 return; | 170 return; |
| 171 } | 171 } |
| 172 if (queue_.empty()) { | 172 if (queue_.empty()) { |
| 173 sync_loop_is_running_ = false; | 173 sync_loop_is_running_ = false; |
| 174 FOR_EACH_OBSERVER(DriveSyncClientObserver, observers_, OnSyncClientIdle()); | 174 FOR_EACH_OBSERVER(DriveSyncClientObserver, observers_, OnSyncClientIdle()); |
| 175 return; | 175 return; |
| 176 } | 176 } |
| 177 sync_loop_is_running_ = true; | 177 sync_loop_is_running_ = true; |
| 178 | 178 |
| 179 // Should copy before calling queue_.pop_front(). | 179 // Should copy before calling queue_.pop_front(). |
| 180 const SyncTask sync_task = queue_.front(); | 180 const SyncTask sync_task = queue_.front(); |
| 181 | 181 |
| 182 // Check if we are ready to process the task. | 182 // Check if we are ready to process the task. |
| 183 const base::TimeDelta elapsed = base::Time::Now() - sync_task.timestamp; | 183 const base::TimeDelta elapsed = base::Time::Now() - sync_task.timestamp; |
| 184 if (elapsed < delay_) { | 184 if (elapsed < delay_) { |
| 185 // Not yet ready. Revisit at a later time. | 185 // Not yet ready. Revisit at a later time. |
| 186 const bool posted = base::MessageLoopProxy::current()->PostDelayedTask( | 186 const bool posted = base::MessageLoopProxy::current()->PostDelayedTask( |
| 187 FROM_HERE, | 187 FROM_HERE, |
| 188 base::Bind(&DriveSyncClient::DoSyncLoop, | 188 base::Bind(&DriveSyncClient::DoSyncLoop, |
| 189 weak_ptr_factory_.GetWeakPtr()), | 189 weak_ptr_factory_.GetWeakPtr(), |
| 190 force_offline), | |
|
szym
2013/01/20 06:52:08
I was concerned that, because it's using PostDelay
| |
| 190 delay_); | 191 delay_); |
| 191 DCHECK(posted); | 192 DCHECK(posted); |
| 192 return; | 193 return; |
| 193 } | 194 } |
| 194 | 195 |
| 195 FOR_EACH_OBSERVER(DriveSyncClientObserver, observers_, OnSyncTaskStarted()); | 196 FOR_EACH_OBSERVER(DriveSyncClientObserver, observers_, OnSyncTaskStarted()); |
| 196 | 197 |
| 197 queue_.pop_front(); | 198 queue_.pop_front(); |
| 198 if (sync_task.sync_type == FETCH) { | 199 if (sync_task.sync_type == FETCH) { |
| 199 DVLOG(1) << "Fetching " << sync_task.resource_id; | 200 DVLOG(1) << "Fetching " << sync_task.resource_id; |
| 200 file_system_->GetFileByResourceId( | 201 file_system_->GetFileByResourceId( |
| 201 sync_task.resource_id, | 202 sync_task.resource_id, |
| 202 base::Bind(&DriveSyncClient::OnFetchFileComplete, | 203 base::Bind(&DriveSyncClient::OnFetchFileComplete, |
| 203 weak_ptr_factory_.GetWeakPtr(), | 204 weak_ptr_factory_.GetWeakPtr(), |
| 204 sync_task), | 205 sync_task), |
| 205 google_apis::GetContentCallback()); | 206 google_apis::GetContentCallback()); |
| 206 } else if (sync_task.sync_type == UPLOAD) { | 207 } else if (sync_task.sync_type == UPLOAD) { |
| 207 DVLOG(1) << "Uploading " << sync_task.resource_id; | 208 DVLOG(1) << "Uploading " << sync_task.resource_id; |
| 208 file_system_->UpdateFileByResourceId( | 209 file_system_->UpdateFileByResourceId( |
| 209 sync_task.resource_id, | 210 sync_task.resource_id, |
| 210 base::Bind(&DriveSyncClient::OnUploadFileComplete, | 211 base::Bind(&DriveSyncClient::OnUploadFileComplete, |
| 211 weak_ptr_factory_.GetWeakPtr(), | 212 weak_ptr_factory_.GetWeakPtr(), |
| 212 sync_task.resource_id)); | 213 sync_task.resource_id)); |
| 213 } else { | 214 } else { |
| 214 NOTREACHED() << ": Unexpected sync type: " << sync_task.sync_type; | 215 NOTREACHED() << ": Unexpected sync type: " << sync_task.sync_type; |
| 215 } | 216 } |
| 216 } | 217 } |
| 217 | 218 |
| 218 bool DriveSyncClient::ShouldStopSyncLoop() { | 219 bool DriveSyncClient::ShouldStopSyncLoop(bool force_offline) { |
| 219 // Should stop if the drive feature was disabled while running the fetch | 220 // Should stop if the drive feature was disabled while running the fetch |
| 220 // loop. | 221 // loop. |
| 221 if (profile_->GetPrefs()->GetBoolean(prefs::kDisableDrive)) | 222 if (profile_->GetPrefs()->GetBoolean(prefs::kDisableDrive)) |
| 222 return true; | 223 return true; |
| 223 | 224 |
| 224 // Should stop if the network is not online. | 225 // Should stop if the network is not online. |
| 225 if (net::NetworkChangeNotifier::IsOffline()) | 226 if (force_offline || net::NetworkChangeNotifier::IsOffline()) |
| 226 return true; | 227 return true; |
| 227 | 228 |
| 228 // Should stop if the current connection is on cellular network, and | 229 // Should stop if the current connection is on cellular network, and |
| 229 // fetching is disabled over cellular. | 230 // fetching is disabled over cellular. |
| 230 if (profile_->GetPrefs()->GetBoolean(prefs::kDisableDriveOverCellular) && | 231 if (profile_->GetPrefs()->GetBoolean(prefs::kDisableDriveOverCellular) && |
| 231 net::NetworkChangeNotifier::IsConnectionCellular( | 232 net::NetworkChangeNotifier::IsConnectionCellular( |
| 232 net::NetworkChangeNotifier::GetConnectionType())) | 233 net::NetworkChangeNotifier::GetConnectionType())) |
| 233 return true; | 234 return true; |
| 234 | 235 |
| 235 return false; | 236 return false; |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 247 | 248 |
| 248 StartCheckingExistingPinnedFiles(); | 249 StartCheckingExistingPinnedFiles(); |
| 249 } | 250 } |
| 250 | 251 |
| 251 void DriveSyncClient::OnCachePinned(const std::string& resource_id, | 252 void DriveSyncClient::OnCachePinned(const std::string& resource_id, |
| 252 const std::string& md5) { | 253 const std::string& md5) { |
| 253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 254 | 255 |
| 255 AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now())); | 256 AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now())); |
| 256 StartSyncLoop(); | 257 StartSyncLoop(false); |
| 257 } | 258 } |
| 258 | 259 |
| 259 void DriveSyncClient::OnCacheUnpinned(const std::string& resource_id, | 260 void DriveSyncClient::OnCacheUnpinned(const std::string& resource_id, |
| 260 const std::string& md5) { | 261 const std::string& md5) { |
| 261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 262 | 263 |
| 263 // Remove the resource_id if it's in the queue. This can happen if the user | 264 // Remove the resource_id if it's in the queue. This can happen if the user |
| 264 // cancels pinning before the file is fetched. | 265 // cancels pinning before the file is fetched. |
| 265 std::deque<SyncTask>::iterator iter = | 266 std::deque<SyncTask>::iterator iter = |
| 266 std::find_if(queue_.begin(), queue_.end(), | 267 std::find_if(queue_.begin(), queue_.end(), |
| 267 CompareTypeAndResourceId(FETCH, resource_id)); | 268 CompareTypeAndResourceId(FETCH, resource_id)); |
| 268 if (iter != queue_.end()) | 269 if (iter != queue_.end()) |
| 269 queue_.erase(iter); | 270 queue_.erase(iter); |
| 270 } | 271 } |
| 271 | 272 |
| 272 void DriveSyncClient::OnCacheCommitted(const std::string& resource_id) { | 273 void DriveSyncClient::OnCacheCommitted(const std::string& resource_id) { |
| 273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 274 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 274 | 275 |
| 275 AddTaskToQueue(SyncTask(UPLOAD, resource_id, base::Time::Now())); | 276 AddTaskToQueue(SyncTask(UPLOAD, resource_id, base::Time::Now())); |
| 276 StartSyncLoop(); | 277 StartSyncLoop(false); |
| 277 } | 278 } |
| 278 | 279 |
| 279 void DriveSyncClient::AddObserver(DriveSyncClientObserver* observer) { | 280 void DriveSyncClient::AddObserver(DriveSyncClientObserver* observer) { |
| 280 observers_.AddObserver(observer); | 281 observers_.AddObserver(observer); |
| 281 } | 282 } |
| 282 | 283 |
| 283 void DriveSyncClient::RemoveObserver(DriveSyncClientObserver* observer) { | 284 void DriveSyncClient::RemoveObserver(DriveSyncClientObserver* observer) { |
| 284 observers_.RemoveObserver(observer); | 285 observers_.RemoveObserver(observer); |
| 285 } | 286 } |
| 286 | 287 |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 311 DVLOG(1) << "Queuing to upload: " << resource_id; | 312 DVLOG(1) << "Queuing to upload: " << resource_id; |
| 312 AddTaskToQueue(SyncTask(UPLOAD, resource_id, base::Time::Now())); | 313 AddTaskToQueue(SyncTask(UPLOAD, resource_id, base::Time::Now())); |
| 313 } | 314 } |
| 314 | 315 |
| 315 for (size_t i = 0; i < to_fetch->size(); ++i) { | 316 for (size_t i = 0; i < to_fetch->size(); ++i) { |
| 316 const std::string& resource_id = (*to_fetch)[i]; | 317 const std::string& resource_id = (*to_fetch)[i]; |
| 317 DVLOG(1) << "Queuing to fetch: " << resource_id; | 318 DVLOG(1) << "Queuing to fetch: " << resource_id; |
| 318 AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now())); | 319 AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now())); |
| 319 } | 320 } |
| 320 | 321 |
| 321 StartSyncLoop(); | 322 StartSyncLoop(false); |
| 322 } | 323 } |
| 323 | 324 |
| 324 void DriveSyncClient::OnGetResourceIdOfExistingPinnedFile( | 325 void DriveSyncClient::OnGetResourceIdOfExistingPinnedFile( |
| 325 const std::string& resource_id, | 326 const std::string& resource_id, |
| 326 const DriveCacheEntry& cache_entry) { | 327 const DriveCacheEntry& cache_entry) { |
| 327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 328 | 329 |
| 329 if (cache_entry.is_pinned() && cache_entry.is_present()) { | 330 if (cache_entry.is_pinned() && cache_entry.is_present()) { |
| 330 file_system_->GetEntryInfoByResourceId( | 331 file_system_->GetEntryInfoByResourceId( |
| 331 resource_id, | 332 resource_id, |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 386 DriveFileError error) { | 387 DriveFileError error) { |
| 387 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 388 | 389 |
| 389 if (error != DRIVE_FILE_OK) { | 390 if (error != DRIVE_FILE_OK) { |
| 390 LOG(WARNING) << "Failed to pin cache entry: " << resource_id; | 391 LOG(WARNING) << "Failed to pin cache entry: " << resource_id; |
| 391 return; | 392 return; |
| 392 } | 393 } |
| 393 | 394 |
| 394 // Finally, adding to the queue. | 395 // Finally, adding to the queue. |
| 395 AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now())); | 396 AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now())); |
| 396 StartSyncLoop(); | 397 StartSyncLoop(false); |
| 397 } | 398 } |
| 398 | 399 |
| 399 void DriveSyncClient::OnFetchFileComplete(const SyncTask& sync_task, | 400 void DriveSyncClient::OnFetchFileComplete(const SyncTask& sync_task, |
| 400 DriveFileError error, | 401 DriveFileError error, |
| 401 const FilePath& local_path, | 402 const FilePath& local_path, |
| 402 const std::string& ununsed_mime_type, | 403 const std::string& ununsed_mime_type, |
| 403 DriveFileType file_type) { | 404 DriveFileType file_type) { |
| 404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 405 | 406 |
| 406 if (error == DRIVE_FILE_OK) { | 407 if (error == DRIVE_FILE_OK) { |
| 407 DVLOG(1) << "Fetched " << sync_task.resource_id << ": " | 408 DVLOG(1) << "Fetched " << sync_task.resource_id << ": " |
| 408 << local_path.value(); | 409 << local_path.value(); |
| 409 } else { | 410 } else { |
| 410 switch (error) { | 411 switch (error) { |
| 411 case DRIVE_FILE_ERROR_NO_CONNECTION: | 412 case DRIVE_FILE_ERROR_NO_CONNECTION: |
| 412 // Re-queue the task so that we'll retry once the connection is back. | 413 // Re-queue the task so that we'll retry once the connection is back. |
| 413 queue_.push_front(sync_task); | 414 queue_.push_front(sync_task); |
| 414 break; | 415 break; |
| 415 default: | 416 default: |
| 416 LOG(WARNING) << "Failed to fetch " << sync_task.resource_id | 417 LOG(WARNING) << "Failed to fetch " << sync_task.resource_id |
| 417 << ": " << error; | 418 << ": " << error; |
| 418 } | 419 } |
| 419 } | 420 } |
| 420 | 421 |
| 421 // Continue the loop. | 422 // Continue the loop. |
| 422 DoSyncLoop(); | 423 DoSyncLoop(false); |
| 423 } | 424 } |
| 424 | 425 |
| 425 void DriveSyncClient::OnUploadFileComplete(const std::string& resource_id, | 426 void DriveSyncClient::OnUploadFileComplete(const std::string& resource_id, |
| 426 DriveFileError error) { | 427 DriveFileError error) { |
| 427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 428 | 429 |
| 429 if (error == DRIVE_FILE_OK) { | 430 if (error == DRIVE_FILE_OK) { |
| 430 DVLOG(1) << "Uploaded " << resource_id; | 431 DVLOG(1) << "Uploaded " << resource_id; |
| 431 } else { | 432 } else { |
| 432 // TODO(satorux): We should re-queue if the error is recoverable. | 433 // TODO(satorux): We should re-queue if the error is recoverable. |
| 433 LOG(WARNING) << "Failed to upload " << resource_id << ": " << error; | 434 LOG(WARNING) << "Failed to upload " << resource_id << ": " << error; |
| 434 } | 435 } |
| 435 | 436 |
| 436 // Continue the loop. | 437 // Continue the loop. |
| 437 DoSyncLoop(); | 438 DoSyncLoop(false); |
| 438 } | 439 } |
| 439 | 440 |
| 440 void DriveSyncClient::OnDriveSyncPreferenceChanged() { | 441 void DriveSyncClient::OnDriveSyncPreferenceChanged() { |
| 441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 442 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 442 | 443 |
| 443 // Resume the sync loop if gdata preferences are changed. Note that we | 444 // Resume the sync loop if gdata preferences are changed. Note that we |
| 444 // don't need to check the new values here as these will be checked in | 445 // don't need to check the new values here as these will be checked in |
| 445 // ShouldStopSyncLoop() as soon as the loop is resumed. | 446 // ShouldStopSyncLoop() as soon as the loop is resumed. |
| 446 StartSyncLoop(); | 447 StartSyncLoop(false); |
| 447 } | 448 } |
| 448 | 449 |
| 449 void DriveSyncClient::OnConnectionTypeChanged( | 450 void DriveSyncClient::OnNetworkChanged( |
| 450 net::NetworkChangeNotifier::ConnectionType type) { | 451 net::NetworkChangeNotifier::ConnectionType type) { |
| 451 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 452 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 452 | 453 |
| 453 // Resume the sync loop if the network is changed. Note that we don't need to | 454 // Resume the sync loop if the network is changed. Note that we don't need to |
| 454 // check the type of the network as it will be checked in ShouldStopSyncLoop() | 455 // check the type of the network as it will be checked in ShouldStopSyncLoop() |
| 455 // as soon as the loop is resumed. | 456 // as soon as the loop is resumed. |
| 456 StartSyncLoop(); | 457 StartSyncLoop(type == net::NetworkChangeNotifier::CONNECTION_NONE); |
|
szym
2013/01/20 06:52:08
Consider the result of |StartSyncLoop(true)|.
Cas
| |
| 457 } | 458 } |
| 458 | 459 |
| 459 } // namespace drive | 460 } // namespace drive |
| OLD | NEW |