| // 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/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) { |
| base::Time::Exploded exploded; |
| time.LocalExplode(&exploded); |
| return base::StringPrintf("%04d-%02d-%02dT%02d:%02d:%02d", exploded.year, |
| exploded.month, exploded.day_of_month, |
| exploded.hour, exploded.minute, exploded.second); |
| } |
| |
| // 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 = base::StringPrintf( |
| "%s.json", TimeToLocalString(base::Time::Now()).c_str()); |
| |
| 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 |