chromium/ios/chrome/app/dump_documents_statistics.mm

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/app/dump_documents_statistics.h"

#import "base/apple/backup_util.h"
#import "base/apple/foundation_util.h"
#import "base/files/file.h"
#import "base/files/file_enumerator.h"
#import "base/files/file_path.h"
#import "base/files/file_util.h"
#import "base/i18n/time_formatting.h"
#import "base/json/json_writer.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/thread_pool.h"
#import "base/time/time.h"

namespace documents_statistics {

// Converts time to a human readable string in the device's local time.
std::string TimeToLocalString(base::Time time) {
  return base::UnlocalizedTimeFormatWithPattern(time, "yyyy-MM-dd'T'HH:mm:ss");
}

// Gathers statistics for `root`, recusively if `root` is a directory.
base::Value::Dict CollectFileStatistics(base::FilePath root) {
  base::Value::Dict statistics;
  std::u16string name = root.BaseName().LossyDisplayName();
  statistics.Set("name", name);

  base::File file(root, base::File::FLAG_OPEN | base::File::FLAG_READ);
  base::File::Info info;
  if (!file.IsValid() || !file.GetInfo(&info)) {
    return statistics;
  }

  if (info.is_directory) {
    int64_t total_directory_size = 0;
    base::Value::List contents;

    base::FileEnumerator enumerator(
        root, /*recursive=*/false,
        base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
    for (base::FilePath path = enumerator.Next(); !path.empty();
         path = enumerator.Next()) {
      base::Value::Dict dir_item_statistics =
          CollectFileStatistics(root.Append(path.BaseName()));
      auto size = dir_item_statistics.FindDouble("size");
      if (size) {
        total_directory_size += size.value();
      }

      contents.Append(std::move(dir_item_statistics));
    }
    statistics.Set("size", static_cast<double>(total_directory_size));
    statistics.Set("contents", std::move(contents));
  } else {
    statistics.Set("size", static_cast<double>(info.size));
  }
  statistics.Set("accessed", TimeToLocalString(info.last_accessed));
  statistics.Set("created", TimeToLocalString(info.creation_time));
  statistics.Set("modified", TimeToLocalString(info.last_modified));

  statistics.Set("excludedFromBackups", base::apple::GetBackupExclusion(root));

  return statistics;
}

// Gathers statistics for `root` and writes them to a new JSON file within
// `statistics_dir`.
void WriteSandboxStatisticsToFile(base::FilePath root,
                                  base::FilePath statistics_dir) {
  base::Value::Dict statistics = CollectFileStatistics(root);

  auto json = base::WriteJson(statistics);
  if (json) {
    if (!base::PathExists(statistics_dir)) {
      base::CreateDirectory(statistics_dir);
    }

    std::string file_name = TimeToLocalString(base::Time::Now()) + ".json";

    base::FilePath statistics_file_path = statistics_dir.Append(file_name);
    base::File statistics_file(
        statistics_file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
    if (statistics_file.IsValid()) {
      std::string json_value = json.value();
      statistics_file.WriteAtCurrentPos(json_value.data(), json_value.size());
      statistics_file.Flush();
    } else {
      DLOG(ERROR) << "Statistics file path could not be opened.";
    }
  } else {
    DLOG(ERROR) << "Statistics could not be converted to JSON.";
  }
}

// Dumps statistics in JSON format for the user's entire Document directory.
void DumpSandboxFileStatistics() {
  base::FilePath documents_path = base::apple::GetUserDocumentPath();
  base::FilePath file_stats_directory =
      base::apple::GetUserDocumentPath().Append("sandboxFileStats");

  // Go up one directory from documents to include all surrounding directories.
  base::FilePath root = documents_path.DirName();

  base::ThreadPool::PostTask(FROM_HERE,
                             {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
                              base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
                             base::BindOnce(&WriteSandboxStatisticsToFile, root,
                                            file_stats_directory));
}

}  // namespace documents_statistics