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