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.
4
5#include "chrome/browser/ui/webui/chromeos/drive_internals_ui.h"
6
7#include "base/bind.h"
8#include "base/files/file_enumerator.h"
9#include "base/files/file_util.h"
10#include "base/format_macros.h"
11#include "base/memory/scoped_vector.h"
12#include "base/memory/weak_ptr.h"
13#include "base/path_service.h"
14#include "base/prefs/pref_service.h"
15#include "base/strings/stringprintf.h"
16#include "base/sys_info.h"
17#include "chrome/browser/chromeos/drive/debug_info_collector.h"
18#include "chrome/browser/chromeos/drive/drive.pb.h"
19#include "chrome/browser/chromeos/drive/drive_integration_service.h"
20#include "chrome/browser/chromeos/drive/file_system_util.h"
21#include "chrome/browser/chromeos/drive/job_list.h"
22#include "chrome/browser/chromeos/file_manager/path_util.h"
23#include "chrome/browser/drive/drive_api_util.h"
24#include "chrome/browser/drive/drive_notification_manager.h"
25#include "chrome/browser/drive/drive_notification_manager_factory.h"
26#include "chrome/browser/drive/drive_service_interface.h"
27#include "chrome/browser/drive/event_logger.h"
28#include "chrome/browser/profiles/profile.h"
29#include "chrome/common/pref_names.h"
30#include "chrome/common/url_constants.h"
31#include "content/public/browser/browser_thread.h"
32#include "content/public/browser/web_ui.h"
33#include "content/public/browser/web_ui_data_source.h"
34#include "content/public/browser/web_ui_message_handler.h"
35#include "google_apis/drive/auth_service.h"
36#include "google_apis/drive/drive_api_parser.h"
37#include "google_apis/drive/gdata_errorcode.h"
38#include "google_apis/drive/time_util.h"
39#include "grit/browser_resources.h"
40
41using content::BrowserThread;
42
43namespace chromeos {
44
45namespace {
46
47// Gets metadata of all files and directories in |root_path|
48// recursively. Stores the result as a list of dictionaries like:
49//
50// [{ path: 'GCache/v1/tmp/<local_id>',
51//    size: 12345,
52//    is_directory: false,
53//    last_modified: '2005-08-09T09:57:00-08:00',
54//  },...]
55//
56// The list is sorted by the path.
57void GetGCacheContents(const base::FilePath& root_path,
58                       base::ListValue* gcache_contents,
59                       base::DictionaryValue* gcache_summary) {
60  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
61  DCHECK(gcache_contents);
62  DCHECK(gcache_summary);
63
64  // Use this map to sort the result list by the path.
65  std::map<base::FilePath, base::DictionaryValue*> files;
66
67  const int options = (base::FileEnumerator::FILES |
68                       base::FileEnumerator::DIRECTORIES |
69                       base::FileEnumerator::SHOW_SYM_LINKS);
70  base::FileEnumerator enumerator(root_path, true /* recursive */, options);
71
72  int64 total_size = 0;
73  for (base::FilePath current = enumerator.Next(); !current.empty();
74       current = enumerator.Next()) {
75    base::FileEnumerator::FileInfo info = enumerator.GetInfo();
76    int64 size = info.GetSize();
77    const bool is_directory = info.IsDirectory();
78    const bool is_symbolic_link = base::IsLink(info.GetName());
79    const base::Time last_modified = info.GetLastModifiedTime();
80
81    base::DictionaryValue* entry = new base::DictionaryValue;
82    entry->SetString("path", current.value());
83    // Use double instead of integer for large files.
84    entry->SetDouble("size", size);
85    entry->SetBoolean("is_directory", is_directory);
86    entry->SetBoolean("is_symbolic_link", is_symbolic_link);
87    entry->SetString(
88        "last_modified",
89        google_apis::util::FormatTimeAsStringLocaltime(last_modified));
90    // Print lower 9 bits in octal format.
91    entry->SetString(
92        "permission",
93        base::StringPrintf("%03o", info.stat().st_mode & 0x1ff));
94    files[current] = entry;
95
96    total_size += size;
97  }
98
99  // Convert |files| into |gcache_contents|.
100  for (std::map<base::FilePath, base::DictionaryValue*>::const_iterator
101           iter = files.begin(); iter != files.end(); ++iter) {
102    gcache_contents->Append(iter->second);
103  }
104
105  gcache_summary->SetDouble("total_size", total_size);
106}
107
108// Gets the available disk space for the path |home_path|.
109void GetFreeDiskSpace(const base::FilePath& home_path,
110                      base::DictionaryValue* local_storage_summary) {
111  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
112  DCHECK(local_storage_summary);
113
114  const int64 free_space = base::SysInfo::AmountOfFreeDiskSpace(home_path);
115  local_storage_summary->SetDouble("free_space", free_space);
116}
117
118// Formats |entry| into text.
119std::string FormatEntry(const base::FilePath& path,
120                        const drive::ResourceEntry& entry) {
121  DCHECK_CURRENTLY_ON(BrowserThread::UI);
122
123  using base::StringAppendF;
124
125  std::string out;
126  StringAppendF(&out, "%s\n", path.AsUTF8Unsafe().c_str());
127  StringAppendF(&out, "  title: %s\n", entry.title().c_str());
128  StringAppendF(&out, "  local_id: %s\n", entry.local_id().c_str());
129  StringAppendF(&out, "  resource_id: %s\n", entry.resource_id().c_str());
130  StringAppendF(&out, "  parent_local_id: %s\n",
131                entry.parent_local_id().c_str());
132  StringAppendF(&out, "  shared: %s\n", entry.shared() ? "true" : "false");
133  StringAppendF(&out, "  shared_with_me: %s\n",
134                entry.shared_with_me() ? "true" : "false");
135
136  const drive::PlatformFileInfoProto& file_info = entry.file_info();
137  StringAppendF(&out, "  file_info\n");
138  StringAppendF(&out, "    size: %" PRId64 "\n", file_info.size());
139  StringAppendF(&out, "    is_directory: %d\n", file_info.is_directory());
140  StringAppendF(&out, "    is_symbolic_link: %d\n",
141                file_info.is_symbolic_link());
142
143  const base::Time last_modified = base::Time::FromInternalValue(
144      file_info.last_modified());
145  const base::Time last_accessed = base::Time::FromInternalValue(
146      file_info.last_accessed());
147  const base::Time creation_time = base::Time::FromInternalValue(
148      file_info.creation_time());
149  StringAppendF(&out, "    last_modified: %s\n",
150                google_apis::util::FormatTimeAsString(last_modified).c_str());
151  StringAppendF(&out, "    last_accessed: %s\n",
152                google_apis::util::FormatTimeAsString(last_accessed).c_str());
153  StringAppendF(&out, "    creation_time: %s\n",
154                google_apis::util::FormatTimeAsString(creation_time).c_str());
155
156  if (entry.has_file_specific_info()) {
157    const drive::FileSpecificInfo& file_specific_info =
158        entry.file_specific_info();
159    StringAppendF(&out, "    alternate_url: %s\n",
160                  file_specific_info.alternate_url().c_str());
161    StringAppendF(&out, "    content_mime_type: %s\n",
162                  file_specific_info.content_mime_type().c_str());
163    StringAppendF(&out, "    file_md5: %s\n",
164                  file_specific_info.md5().c_str());
165    StringAppendF(&out, "    document_extension: %s\n",
166                  file_specific_info.document_extension().c_str());
167    StringAppendF(&out, "    is_hosted_document: %d\n",
168                  file_specific_info.is_hosted_document());
169  }
170
171  if (entry.has_directory_specific_info()) {
172    StringAppendF(&out, "  directory_info\n");
173    const drive::DirectorySpecificInfo& directory_specific_info =
174        entry.directory_specific_info();
175    StringAppendF(&out, "    changestamp: %" PRId64 "\n",
176                  directory_specific_info.changestamp());
177  }
178
179  return out;
180}
181
182std::string SeverityToString(logging::LogSeverity severity) {
183  switch (severity) {
184    case logging::LOG_INFO:
185      return "info";
186    case logging::LOG_WARNING:
187      return "warning";
188    case logging::LOG_ERROR:
189      return "error";
190    default:  // Treat all other higher severities as ERROR.
191      return "error";
192  }
193}
194
195// Appends {'key': key, 'value': value} dictionary to the |list|.
196void AppendKeyValue(base::ListValue* list,
197                    const std::string& key,
198                    const std::string& value) {
199  base::DictionaryValue* dict = new base::DictionaryValue;
200  dict->SetString("key", key);
201  dict->SetString("value", value);
202  list->Append(dict);
203}
204
205// Class to handle messages from chrome://drive-internals.
206class DriveInternalsWebUIHandler : public content::WebUIMessageHandler {
207 public:
208  DriveInternalsWebUIHandler()
209      : last_sent_event_id_(-1),
210        weak_ptr_factory_(this) {
211  }
212
213  virtual ~DriveInternalsWebUIHandler() {
214  }
215
216 private:
217  // WebUIMessageHandler override.
218  virtual void RegisterMessages() OVERRIDE;
219
220  // Returns a DriveIntegrationService.
221  drive::DriveIntegrationService* GetIntegrationService();
222
223  // Returns a DriveService instance.
224  drive::DriveServiceInterface* GetDriveService();
225
226  // Returns a DebugInfoCollector instance.
227  drive::DebugInfoCollector* GetDebugInfoCollector();
228
229  // Called when the page is first loaded.
230  void OnPageLoaded(const base::ListValue* args);
231
232  // Updates respective sections.
233  void UpdateDriveRelatedPreferencesSection();
234  void UpdateConnectionStatusSection(
235      drive::DriveServiceInterface* drive_service);
236  void UpdateAboutResourceSection(
237      drive::DriveServiceInterface* drive_service);
238  void UpdateAppListSection(
239      drive::DriveServiceInterface* drive_service);
240  void UpdateLocalMetadataSection(
241      drive::DebugInfoCollector* debug_info_collector);
242  void UpdateDeltaUpdateStatusSection(
243      drive::DebugInfoCollector* debug_info_collector);
244  void UpdateInFlightOperationsSection(drive::JobListInterface* job_list);
245  void UpdateGCacheContentsSection();
246  void UpdateFileSystemContentsSection();
247  void UpdateLocalStorageUsageSection();
248  void UpdateCacheContentsSection(
249      drive::DebugInfoCollector* debug_info_collector);
250  void UpdateEventLogSection();
251  void UpdatePathConfigurationsSection();
252
253  // Called when GetGCacheContents() is complete.
254  void OnGetGCacheContents(base::ListValue* gcache_contents,
255                           base::DictionaryValue* cache_summary);
256
257  // Called when GetResourceEntryByPath() is complete.
258  void OnGetResourceEntryByPath(const base::FilePath& path,
259                                drive::FileError error,
260                                scoped_ptr<drive::ResourceEntry> entry);
261
262  // Called when ReadDirectoryByPath() is complete.
263  void OnReadDirectoryByPath(const base::FilePath& parent_path,
264                             drive::FileError error,
265                             scoped_ptr<drive::ResourceEntryVector> entries);
266
267  // Called as the iterator for DebugInfoCollector::IterateFileCache().
268  void UpdateCacheEntry(const std::string& local_id,
269                        const drive::FileCacheEntry& cache_entry);
270
271  // Called when GetFreeDiskSpace() is complete.
272  void OnGetFreeDiskSpace(base::DictionaryValue* local_storage_summary);
273
274  // Called when GetAboutResource() call to DriveService is complete.
275  void OnGetAboutResource(
276      google_apis::GDataErrorCode status,
277      scoped_ptr<google_apis::AboutResource> about_resource);
278
279  // Called when GetAppList() call to DriveService is complete.
280  void OnGetAppList(
281      google_apis::GDataErrorCode status,
282      scoped_ptr<google_apis::AppList> app_list);
283
284  // Callback for DebugInfoCollector::GetMetadata for local update.
285  void OnGetFilesystemMetadataForLocal(
286      const drive::FileSystemMetadata& metadata);
287
288  // Callback for DebugInfoCollector::GetMetadata for delta update.
289  void OnGetFilesystemMetadataForDeltaUpdate(
290      const drive::FileSystemMetadata& metadata);
291
292  // Called when the page requests periodic update.
293  void OnPeriodicUpdate(const base::ListValue* args);
294
295  // Called when the corresponding button on the page is pressed.
296  void ClearAccessToken(const base::ListValue* args);
297  void ClearRefreshToken(const base::ListValue* args);
298  void ResetDriveFileSystem(const base::ListValue* args);
299  void ListFileEntries(const base::ListValue* args);
300
301  // Called after file system reset for ResetDriveFileSystem is done.
302  void ResetFinished(bool success);
303
304  // The last event sent to the JavaScript side.
305  int last_sent_event_id_;
306
307  base::WeakPtrFactory<DriveInternalsWebUIHandler> weak_ptr_factory_;
308  DISALLOW_COPY_AND_ASSIGN(DriveInternalsWebUIHandler);
309};
310
311void DriveInternalsWebUIHandler::OnGetAboutResource(
312    google_apis::GDataErrorCode status,
313    scoped_ptr<google_apis::AboutResource> parsed_about_resource) {
314  DCHECK_CURRENTLY_ON(BrowserThread::UI);
315
316  if (status != google_apis::HTTP_SUCCESS) {
317    LOG(ERROR) << "Failed to get about resource";
318    return;
319  }
320  DCHECK(parsed_about_resource);
321
322  base::DictionaryValue about_resource;
323  about_resource.SetDouble("account-quota-total",
324                           parsed_about_resource->quota_bytes_total());
325  about_resource.SetDouble("account-quota-used",
326                           parsed_about_resource->quota_bytes_used());
327  about_resource.SetDouble("account-largest-changestamp-remote",
328                           parsed_about_resource->largest_change_id());
329  about_resource.SetString("root-resource-id",
330                           parsed_about_resource->root_folder_id());
331
332  web_ui()->CallJavascriptFunction("updateAboutResource", about_resource);
333}
334
335void DriveInternalsWebUIHandler::OnGetAppList(
336    google_apis::GDataErrorCode status,
337    scoped_ptr<google_apis::AppList> parsed_app_list) {
338  DCHECK_CURRENTLY_ON(BrowserThread::UI);
339
340  if (status != google_apis::HTTP_SUCCESS) {
341    LOG(ERROR) << "Failed to get app list";
342    return;
343  }
344  DCHECK(parsed_app_list);
345
346  base::DictionaryValue app_list;
347  app_list.SetString("etag", parsed_app_list->etag());
348
349  base::ListValue* items = new base::ListValue();
350  for (size_t i = 0; i < parsed_app_list->items().size(); ++i) {
351    const google_apis::AppResource* app = parsed_app_list->items()[i];
352    base::DictionaryValue* app_data = new base::DictionaryValue();
353    app_data->SetString("name", app->name());
354    app_data->SetString("application_id", app->application_id());
355    app_data->SetString("object_type", app->object_type());
356    app_data->SetBoolean("supports_create", app->supports_create());
357
358    items->Append(app_data);
359  }
360  app_list.Set("items", items);
361
362  web_ui()->CallJavascriptFunction("updateAppList", app_list);
363}
364
365void DriveInternalsWebUIHandler::RegisterMessages() {
366  web_ui()->RegisterMessageCallback(
367      "pageLoaded",
368      base::Bind(&DriveInternalsWebUIHandler::OnPageLoaded,
369                 weak_ptr_factory_.GetWeakPtr()));
370  web_ui()->RegisterMessageCallback(
371      "periodicUpdate",
372      base::Bind(&DriveInternalsWebUIHandler::OnPeriodicUpdate,
373                 weak_ptr_factory_.GetWeakPtr()));
374  web_ui()->RegisterMessageCallback(
375      "clearAccessToken",
376      base::Bind(&DriveInternalsWebUIHandler::ClearAccessToken,
377                 weak_ptr_factory_.GetWeakPtr()));
378  web_ui()->RegisterMessageCallback(
379      "clearRefreshToken",
380      base::Bind(&DriveInternalsWebUIHandler::ClearRefreshToken,
381                 weak_ptr_factory_.GetWeakPtr()));
382  web_ui()->RegisterMessageCallback(
383      "resetDriveFileSystem",
384      base::Bind(&DriveInternalsWebUIHandler::ResetDriveFileSystem,
385                 weak_ptr_factory_.GetWeakPtr()));
386  web_ui()->RegisterMessageCallback(
387      "listFileEntries",
388      base::Bind(&DriveInternalsWebUIHandler::ListFileEntries,
389                 weak_ptr_factory_.GetWeakPtr()));
390}
391
392drive::DriveIntegrationService*
393DriveInternalsWebUIHandler::GetIntegrationService() {
394  DCHECK_CURRENTLY_ON(BrowserThread::UI);
395
396  Profile* profile = Profile::FromWebUI(web_ui());
397  drive::DriveIntegrationService* service =
398      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
399  if (!service || !service->is_enabled())
400    return NULL;
401  return service;
402}
403
404drive::DriveServiceInterface* DriveInternalsWebUIHandler::GetDriveService() {
405  DCHECK_CURRENTLY_ON(BrowserThread::UI);
406
407  Profile* profile = Profile::FromWebUI(web_ui());
408  return drive::util::GetDriveServiceByProfile(profile);
409}
410
411drive::DebugInfoCollector* DriveInternalsWebUIHandler::GetDebugInfoCollector() {
412  DCHECK_CURRENTLY_ON(BrowserThread::UI);
413
414  drive::DriveIntegrationService* integration_service = GetIntegrationService();
415  return integration_service ?
416      integration_service->debug_info_collector() : NULL;
417}
418
419void DriveInternalsWebUIHandler::OnPageLoaded(const base::ListValue* args) {
420  DCHECK_CURRENTLY_ON(BrowserThread::UI);
421
422  drive::DriveIntegrationService* integration_service =
423      GetIntegrationService();
424  // |integration_service| may be NULL in the guest/incognito mode.
425  if (!integration_service)
426    return;
427
428  drive::DriveServiceInterface* drive_service =
429      integration_service->drive_service();
430  DCHECK(drive_service);
431  drive::DebugInfoCollector* debug_info_collector =
432      integration_service->debug_info_collector();
433  DCHECK(debug_info_collector);
434
435  UpdateDriveRelatedPreferencesSection();
436  UpdateConnectionStatusSection(drive_service);
437  UpdateAboutResourceSection(drive_service);
438  UpdateAppListSection(drive_service);
439  UpdateLocalMetadataSection(debug_info_collector);
440  UpdateDeltaUpdateStatusSection(debug_info_collector);
441  UpdateInFlightOperationsSection(integration_service->job_list());
442  UpdateGCacheContentsSection();
443  UpdateCacheContentsSection(debug_info_collector);
444  UpdateLocalStorageUsageSection();
445  UpdatePathConfigurationsSection();
446
447  // When the drive-internals page is reloaded by the reload key, the page
448  // content is recreated, but this WebUI object is not (instead, OnPageLoaded
449  // is called again). In that case, we have to forget the last sent ID here,
450  // and resent whole the logs to the page.
451  last_sent_event_id_ = -1;
452  UpdateEventLogSection();
453}
454
455void DriveInternalsWebUIHandler::UpdateDriveRelatedPreferencesSection() {
456  DCHECK_CURRENTLY_ON(BrowserThread::UI);
457
458  const char* kDriveRelatedPreferences[] = {
459    prefs::kDisableDrive,
460    prefs::kDisableDriveOverCellular,
461    prefs::kDisableDriveHostedFiles,
462  };
463
464  Profile* profile = Profile::FromWebUI(web_ui());
465  PrefService* pref_service = profile->GetPrefs();
466
467  base::ListValue preferences;
468  for (size_t i = 0; i < arraysize(kDriveRelatedPreferences); ++i) {
469    const std::string key = kDriveRelatedPreferences[i];
470    // As of now, all preferences are boolean.
471    const std::string value =
472        (pref_service->GetBoolean(key.c_str()) ? "true" : "false");
473    AppendKeyValue(&preferences, key, value);
474  }
475
476  web_ui()->CallJavascriptFunction("updateDriveRelatedPreferences",
477                                   preferences);
478}
479
480void DriveInternalsWebUIHandler::UpdateConnectionStatusSection(
481    drive::DriveServiceInterface* drive_service) {
482  DCHECK_CURRENTLY_ON(BrowserThread::UI);
483  DCHECK(drive_service);
484
485  std::string status;
486  switch (drive::util::GetDriveConnectionStatus(Profile::FromWebUI(web_ui()))) {
487    case drive::util::DRIVE_DISCONNECTED_NOSERVICE:
488      status = "no service";
489      break;
490    case drive::util::DRIVE_DISCONNECTED_NONETWORK:
491      status = "no network";
492      break;
493    case drive::util::DRIVE_DISCONNECTED_NOTREADY:
494      status = "not ready";
495      break;
496    case drive::util::DRIVE_CONNECTED_METERED:
497      status = "metered";
498      break;
499    case drive::util::DRIVE_CONNECTED:
500      status = "connected";
501      break;
502  }
503
504  base::DictionaryValue connection_status;
505  connection_status.SetString("status", status);
506  connection_status.SetBoolean("has-refresh-token",
507                               drive_service->HasRefreshToken());
508  connection_status.SetBoolean("has-access-token",
509                               drive_service->HasAccessToken());
510  web_ui()->CallJavascriptFunction("updateConnectionStatus", connection_status);
511}
512
513void DriveInternalsWebUIHandler::UpdateAboutResourceSection(
514    drive::DriveServiceInterface* drive_service) {
515  DCHECK_CURRENTLY_ON(BrowserThread::UI);
516  DCHECK(drive_service);
517
518  drive_service->GetAboutResource(
519      base::Bind(&DriveInternalsWebUIHandler::OnGetAboutResource,
520                 weak_ptr_factory_.GetWeakPtr()));
521}
522
523void DriveInternalsWebUIHandler::UpdateAppListSection(
524    drive::DriveServiceInterface* drive_service) {
525  DCHECK_CURRENTLY_ON(BrowserThread::UI);
526  DCHECK(drive_service);
527
528  drive_service->GetAppList(
529      base::Bind(&DriveInternalsWebUIHandler::OnGetAppList,
530                 weak_ptr_factory_.GetWeakPtr()));
531}
532
533void DriveInternalsWebUIHandler::UpdateLocalMetadataSection(
534    drive::DebugInfoCollector* debug_info_collector) {
535  DCHECK_CURRENTLY_ON(BrowserThread::UI);
536  DCHECK(debug_info_collector);
537
538  debug_info_collector->GetMetadata(
539      base::Bind(&DriveInternalsWebUIHandler::OnGetFilesystemMetadataForLocal,
540                 weak_ptr_factory_.GetWeakPtr()));
541}
542
543void DriveInternalsWebUIHandler::OnGetFilesystemMetadataForLocal(
544    const drive::FileSystemMetadata& metadata) {
545  DCHECK_CURRENTLY_ON(BrowserThread::UI);
546
547  base::DictionaryValue local_metadata;
548  local_metadata.SetDouble("account-largest-changestamp-local",
549                           metadata.largest_changestamp);
550  local_metadata.SetBoolean("account-metadata-refreshing", metadata.refreshing);
551  web_ui()->CallJavascriptFunction("updateLocalMetadata", local_metadata);
552}
553
554void DriveInternalsWebUIHandler::ClearAccessToken(const base::ListValue* args) {
555  DCHECK_CURRENTLY_ON(BrowserThread::UI);
556
557  drive::DriveServiceInterface* drive_service = GetDriveService();
558  if (drive_service)
559    drive_service->ClearAccessToken();
560}
561
562void DriveInternalsWebUIHandler::ClearRefreshToken(
563    const base::ListValue* args) {
564  DCHECK_CURRENTLY_ON(BrowserThread::UI);
565
566  drive::DriveServiceInterface* drive_service = GetDriveService();
567  if (drive_service)
568    drive_service->ClearRefreshToken();
569}
570
571void DriveInternalsWebUIHandler::ResetDriveFileSystem(
572    const base::ListValue* args) {
573  DCHECK_CURRENTLY_ON(BrowserThread::UI);
574
575  drive::DriveIntegrationService* integration_service =
576      GetIntegrationService();
577  if (integration_service) {
578    integration_service->ClearCacheAndRemountFileSystem(
579        base::Bind(&DriveInternalsWebUIHandler::ResetFinished,
580                   weak_ptr_factory_.GetWeakPtr()));
581  }
582}
583
584void DriveInternalsWebUIHandler::ResetFinished(bool success) {
585  DCHECK_CURRENTLY_ON(BrowserThread::UI);
586
587  web_ui()->CallJavascriptFunction("updateResetStatus",
588                                   base::FundamentalValue(success));
589}
590
591void DriveInternalsWebUIHandler::ListFileEntries(const base::ListValue* args) {
592  DCHECK_CURRENTLY_ON(BrowserThread::UI);
593
594  UpdateFileSystemContentsSection();
595}
596
597void DriveInternalsWebUIHandler::UpdateDeltaUpdateStatusSection(
598    drive::DebugInfoCollector* debug_info_collector) {
599  DCHECK_CURRENTLY_ON(BrowserThread::UI);
600  DCHECK(debug_info_collector);
601
602  debug_info_collector->GetMetadata(
603      base::Bind(
604          &DriveInternalsWebUIHandler::OnGetFilesystemMetadataForDeltaUpdate,
605          weak_ptr_factory_.GetWeakPtr()));
606}
607
608void DriveInternalsWebUIHandler::OnGetFilesystemMetadataForDeltaUpdate(
609    const drive::FileSystemMetadata& metadata) {
610  DCHECK_CURRENTLY_ON(BrowserThread::UI);
611
612  Profile* profile = Profile::FromWebUI(web_ui());
613  drive::DriveNotificationManager* drive_notification_manager =
614      drive::DriveNotificationManagerFactory::FindForBrowserContext(profile);
615  if (!drive_notification_manager)
616    return;
617
618  base::DictionaryValue delta_update_status;
619  delta_update_status.SetBoolean(
620      "push-notification-enabled",
621      drive_notification_manager->push_notification_enabled());
622  delta_update_status.SetString(
623      "last-update-check-time",
624      google_apis::util::FormatTimeAsStringLocaltime(
625          metadata.last_update_check_time));
626  delta_update_status.SetString(
627      "last-update-check-error",
628      drive::FileErrorToString(metadata.last_update_check_error));
629
630  web_ui()->CallJavascriptFunction("updateDeltaUpdateStatus",
631                                   delta_update_status);
632}
633
634void DriveInternalsWebUIHandler::UpdateInFlightOperationsSection(
635    drive::JobListInterface* job_list) {
636  DCHECK_CURRENTLY_ON(BrowserThread::UI);
637  DCHECK(job_list);
638
639  std::vector<drive::JobInfo> info_list = job_list->GetJobInfoList();
640
641  base::ListValue in_flight_operations;
642  for (size_t i = 0; i < info_list.size(); ++i) {
643    const drive::JobInfo& info = info_list[i];
644
645    base::DictionaryValue* dict = new base::DictionaryValue;
646    dict->SetInteger("id", info.job_id);
647    dict->SetString("type", drive::JobTypeToString(info.job_type));
648    dict->SetString("file_path", info.file_path.AsUTF8Unsafe());
649    dict->SetString("state", drive::JobStateToString(info.state));
650    dict->SetDouble("progress_current", info.num_completed_bytes);
651    dict->SetDouble("progress_total", info.num_total_bytes);
652    in_flight_operations.Append(dict);
653  }
654  web_ui()->CallJavascriptFunction("updateInFlightOperations",
655                                   in_flight_operations);
656}
657
658void DriveInternalsWebUIHandler::UpdateGCacheContentsSection() {
659  DCHECK_CURRENTLY_ON(BrowserThread::UI);
660
661  // Start updating the GCache contents section.
662  Profile* profile = Profile::FromWebUI(web_ui());
663  const base::FilePath root_path = drive::util::GetCacheRootPath(profile);
664  base::ListValue* gcache_contents = new base::ListValue;
665  base::DictionaryValue* gcache_summary = new base::DictionaryValue;
666  BrowserThread::PostBlockingPoolTaskAndReply(
667      FROM_HERE,
668      base::Bind(&GetGCacheContents,
669                 root_path,
670                 gcache_contents,
671                 gcache_summary),
672      base::Bind(&DriveInternalsWebUIHandler::OnGetGCacheContents,
673                 weak_ptr_factory_.GetWeakPtr(),
674                 base::Owned(gcache_contents),
675                 base::Owned(gcache_summary)));
676}
677
678void DriveInternalsWebUIHandler::UpdateFileSystemContentsSection() {
679  DCHECK_CURRENTLY_ON(BrowserThread::UI);
680
681  drive::DebugInfoCollector* debug_info_collector = GetDebugInfoCollector();
682  if (!debug_info_collector)
683    return;
684
685  // Start rendering the file system tree as text.
686  const base::FilePath root_path = drive::util::GetDriveGrandRootPath();
687
688  debug_info_collector->GetResourceEntry(
689      root_path,
690      base::Bind(&DriveInternalsWebUIHandler::OnGetResourceEntryByPath,
691                 weak_ptr_factory_.GetWeakPtr(),
692                 root_path));
693
694  debug_info_collector->ReadDirectory(
695      root_path,
696      base::Bind(&DriveInternalsWebUIHandler::OnReadDirectoryByPath,
697                 weak_ptr_factory_.GetWeakPtr(),
698                 root_path));
699}
700
701void DriveInternalsWebUIHandler::UpdateLocalStorageUsageSection() {
702  DCHECK_CURRENTLY_ON(BrowserThread::UI);
703
704  // Propagate the amount of local free space in bytes.
705  base::FilePath home_path;
706  if (PathService::Get(base::DIR_HOME, &home_path)) {
707    base::DictionaryValue* local_storage_summary = new base::DictionaryValue;
708    BrowserThread::PostBlockingPoolTaskAndReply(
709        FROM_HERE,
710        base::Bind(&GetFreeDiskSpace, home_path, local_storage_summary),
711        base::Bind(&DriveInternalsWebUIHandler::OnGetFreeDiskSpace,
712                   weak_ptr_factory_.GetWeakPtr(),
713                   base::Owned(local_storage_summary)));
714  } else {
715    LOG(ERROR) << "Home directory not found";
716  }
717}
718
719void DriveInternalsWebUIHandler::UpdateCacheContentsSection(
720    drive::DebugInfoCollector* debug_info_collector) {
721  DCHECK_CURRENTLY_ON(BrowserThread::UI);
722  DCHECK(debug_info_collector);
723
724  debug_info_collector->IterateFileCache(
725      base::Bind(&DriveInternalsWebUIHandler::UpdateCacheEntry,
726                 weak_ptr_factory_.GetWeakPtr()),
727      base::Bind(&base::DoNothing));
728}
729
730void DriveInternalsWebUIHandler::UpdateEventLogSection() {
731  DCHECK_CURRENTLY_ON(BrowserThread::UI);
732
733  drive::DriveIntegrationService* integration_service =
734      GetIntegrationService();
735  if (!integration_service)
736    return;
737
738  const std::vector<drive::EventLogger::Event> log =
739      integration_service->event_logger()->GetHistory();
740
741  base::ListValue list;
742  for (size_t i = 0; i < log.size(); ++i) {
743    // Skip events which were already sent.
744    if (log[i].id <= last_sent_event_id_)
745      continue;
746
747    std::string severity = SeverityToString(log[i].severity);
748
749    base::DictionaryValue* dict = new base::DictionaryValue;
750    dict->SetString("key",
751        google_apis::util::FormatTimeAsStringLocaltime(log[i].when));
752    dict->SetString("value", "[" + severity + "] " + log[i].what);
753    dict->SetString("class", "log-" + severity);
754    list.Append(dict);
755    last_sent_event_id_ = log[i].id;
756  }
757  if (!list.empty())
758    web_ui()->CallJavascriptFunction("updateEventLog", list);
759}
760
761void DriveInternalsWebUIHandler::UpdatePathConfigurationsSection() {
762  DCHECK_CURRENTLY_ON(BrowserThread::UI);
763
764  Profile* const profile = Profile::FromWebUI(web_ui());
765
766  base::ListValue paths;
767
768  AppendKeyValue(
769      &paths, "Downloads",
770      file_manager::util::GetDownloadsFolderForProfile(profile).AsUTF8Unsafe());
771  AppendKeyValue(
772      &paths, "Drive",
773      drive::util::GetDriveMountPointPath(profile).AsUTF8Unsafe());
774
775  const char* kPathPreferences[] = {
776    prefs::kSelectFileLastDirectory,
777    prefs::kSaveFileDefaultDirectory,
778    prefs::kDownloadDefaultDirectory,
779  };
780  for (size_t i = 0; i < arraysize(kPathPreferences); ++i) {
781    const char* const key = kPathPreferences[i];
782    AppendKeyValue(&paths, key,
783                   profile->GetPrefs()->GetFilePath(key).AsUTF8Unsafe());
784  }
785
786  web_ui()->CallJavascriptFunction("updatePathConfigurations", paths);
787}
788
789void DriveInternalsWebUIHandler::OnGetGCacheContents(
790    base::ListValue* gcache_contents,
791    base::DictionaryValue* gcache_summary) {
792  DCHECK_CURRENTLY_ON(BrowserThread::UI);
793  DCHECK(gcache_contents);
794  DCHECK(gcache_summary);
795
796  web_ui()->CallJavascriptFunction("updateGCacheContents",
797                                   *gcache_contents,
798                                   *gcache_summary);
799}
800
801void DriveInternalsWebUIHandler::OnGetResourceEntryByPath(
802    const base::FilePath& path,
803    drive::FileError error,
804    scoped_ptr<drive::ResourceEntry> entry) {
805  DCHECK_CURRENTLY_ON(BrowserThread::UI);
806
807  if (error == drive::FILE_ERROR_OK) {
808    DCHECK(entry.get());
809    const base::StringValue value(FormatEntry(path, *entry) + "\n");
810    web_ui()->CallJavascriptFunction("updateFileSystemContents", value);
811  }
812}
813
814void DriveInternalsWebUIHandler::OnReadDirectoryByPath(
815    const base::FilePath& parent_path,
816    drive::FileError error,
817    scoped_ptr<drive::ResourceEntryVector> entries) {
818  DCHECK_CURRENTLY_ON(BrowserThread::UI);
819
820  if (error == drive::FILE_ERROR_OK) {
821    DCHECK(entries.get());
822
823    drive::DebugInfoCollector* debug_info_collector = GetDebugInfoCollector();
824    std::string file_system_as_text;
825    for (size_t i = 0; i < entries->size(); ++i) {
826      const drive::ResourceEntry& entry = (*entries)[i];
827      const base::FilePath current_path = parent_path.Append(
828          base::FilePath::FromUTF8Unsafe(entry.base_name()));
829
830      file_system_as_text.append(FormatEntry(current_path, entry) + "\n");
831
832      if (entry.file_info().is_directory()) {
833        debug_info_collector->ReadDirectory(
834            current_path,
835            base::Bind(&DriveInternalsWebUIHandler::OnReadDirectoryByPath,
836                       weak_ptr_factory_.GetWeakPtr(),
837                       current_path));
838      }
839    }
840
841    // There may be pending ReadDirectoryByPath() calls, but we can update
842    // the page with what we have now. This results in progressive
843    // updates, which is good for a large file system.
844    const base::StringValue value(file_system_as_text);
845    web_ui()->CallJavascriptFunction("updateFileSystemContents", value);
846  }
847}
848
849void DriveInternalsWebUIHandler::UpdateCacheEntry(
850    const std::string& local_id,
851    const drive::FileCacheEntry& cache_entry) {
852  DCHECK_CURRENTLY_ON(BrowserThread::UI);
853
854  // Convert |cache_entry| into a dictionary.
855  base::DictionaryValue value;
856  value.SetString("local_id", local_id);
857  value.SetString("md5", cache_entry.md5());
858  value.SetBoolean("is_present", cache_entry.is_present());
859  value.SetBoolean("is_pinned", cache_entry.is_pinned());
860  value.SetBoolean("is_dirty", cache_entry.is_dirty());
861
862  web_ui()->CallJavascriptFunction("updateCacheContents", value);
863}
864
865void DriveInternalsWebUIHandler::OnGetFreeDiskSpace(
866    base::DictionaryValue* local_storage_summary) {
867  DCHECK_CURRENTLY_ON(BrowserThread::UI);
868  DCHECK(local_storage_summary);
869
870  web_ui()->CallJavascriptFunction(
871      "updateLocalStorageUsage", *local_storage_summary);
872}
873
874void DriveInternalsWebUIHandler::OnPeriodicUpdate(const base::ListValue* args) {
875  DCHECK_CURRENTLY_ON(BrowserThread::UI);
876
877  drive::DriveIntegrationService* integration_service =
878      GetIntegrationService();
879  // |integration_service| may be NULL in the guest/incognito mode.
880  if (!integration_service)
881    return;
882
883  UpdateInFlightOperationsSection(integration_service->job_list());
884  UpdateEventLogSection();
885}
886
887}  // namespace
888
889DriveInternalsUI::DriveInternalsUI(content::WebUI* web_ui)
890    : WebUIController(web_ui) {
891  web_ui->AddMessageHandler(new DriveInternalsWebUIHandler());
892
893  content::WebUIDataSource* source =
894      content::WebUIDataSource::Create(chrome::kChromeUIDriveInternalsHost);
895  source->AddResourcePath("drive_internals.css", IDR_DRIVE_INTERNALS_CSS);
896  source->AddResourcePath("drive_internals.js", IDR_DRIVE_INTERNALS_JS);
897  source->SetDefaultResource(IDR_DRIVE_INTERNALS_HTML);
898
899  Profile* profile = Profile::FromWebUI(web_ui);
900  content::WebUIDataSource::Add(profile, source);
901}
902
903}  // namespace chromeos
904