2018-12-31 20:04:05 +01:00
|
|
|
//
|
2020-01-01 02:23:48 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
2018-12-31 20:04:05 +01:00
|
|
|
//
|
|
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
//
|
|
|
|
#include "td/telegram/files/FileGcWorker.h"
|
|
|
|
|
2019-01-19 22:54:29 +01:00
|
|
|
#include "td/telegram/files/FileLocation.h"
|
2019-01-20 01:21:26 +01:00
|
|
|
#include "td/telegram/files/FileManager.h"
|
2019-01-19 22:54:29 +01:00
|
|
|
#include "td/telegram/files/FileType.h"
|
2018-12-31 20:04:05 +01:00
|
|
|
#include "td/telegram/Global.h"
|
|
|
|
|
|
|
|
#include "td/utils/format.h"
|
|
|
|
#include "td/utils/logging.h"
|
|
|
|
#include "td/utils/misc.h"
|
|
|
|
#include "td/utils/port/Clocks.h"
|
|
|
|
#include "td/utils/port/path.h"
|
2019-05-22 20:17:24 +02:00
|
|
|
#include "td/utils/Status.h"
|
2018-12-31 20:04:05 +01:00
|
|
|
#include "td/utils/Time.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <array>
|
|
|
|
|
|
|
|
namespace td {
|
2018-11-15 22:25:08 +01:00
|
|
|
|
|
|
|
int VERBOSITY_NAME(file_gc) = VERBOSITY_NAME(INFO);
|
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
void FileGcWorker::run_gc(const FileGcParameters ¶meters, std::vector<FullFileInfo> files,
|
2020-03-02 09:59:47 +01:00
|
|
|
Promise<FileGcResult> promise) {
|
2018-12-31 20:04:05 +01:00
|
|
|
auto begin_time = Time::now();
|
2018-11-15 22:25:08 +01:00
|
|
|
VLOG(file_gc) << "Start files gc with " << parameters;
|
2018-12-31 20:04:05 +01:00
|
|
|
// quite stupid implementations
|
|
|
|
// needs a lot of memory
|
|
|
|
// may write something more clever, but i will need at least 2 passes over the files
|
|
|
|
// TODO update atime for all files in android (?)
|
|
|
|
|
|
|
|
std::array<bool, file_type_size> immune_types{{false}};
|
|
|
|
|
|
|
|
if (G()->parameters().use_file_db) {
|
|
|
|
// immune by default
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::Sticker)] = true;
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::ProfilePhoto)] = true;
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::Thumbnail)] = true;
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::Wallpaper)] = true;
|
2019-05-07 04:51:56 +02:00
|
|
|
immune_types[narrow_cast<size_t>(FileType::Background)] = true;
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!parameters.file_types.empty()) {
|
|
|
|
std::fill(immune_types.begin(), immune_types.end(), true);
|
|
|
|
for (auto file_type : parameters.file_types) {
|
2019-05-07 04:51:56 +02:00
|
|
|
if (file_type == FileType::Secure) {
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::SecureRaw)] = false;
|
|
|
|
} else if (file_type == FileType::Background) {
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::Wallpaper)] = false;
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
immune_types[narrow_cast<size_t>(file_type)] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (G()->parameters().use_file_db) {
|
|
|
|
immune_types[narrow_cast<size_t>(FileType::EncryptedThumbnail)] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto file_cnt = files.size();
|
|
|
|
int32 type_immunity_ignored_cnt = 0;
|
|
|
|
int32 time_immunity_ignored_cnt = 0;
|
|
|
|
int32 exclude_owner_dialog_id_ignored_cnt = 0;
|
|
|
|
int32 owner_dialog_id_ignored_cnt = 0;
|
|
|
|
int32 remove_by_atime_cnt = 0;
|
|
|
|
int32 remove_by_count_cnt = 0;
|
|
|
|
int32 remove_by_size_cnt = 0;
|
|
|
|
int64 total_removed_size = 0;
|
|
|
|
int64 total_size = 0;
|
|
|
|
for (auto &info : files) {
|
|
|
|
if (info.atime_nsec < info.mtime_nsec) {
|
|
|
|
info.atime_nsec = info.mtime_nsec;
|
|
|
|
}
|
|
|
|
total_size += info.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
FileStats new_stats;
|
2020-03-02 09:59:47 +01:00
|
|
|
FileStats removed_stats;
|
|
|
|
removed_stats.split_by_owner_dialog_id = new_stats.split_by_owner_dialog_id = parameters.dialog_limit != 0;
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2020-03-02 09:59:47 +01:00
|
|
|
auto do_remove_file = [&removed_stats](const FullFileInfo &info) {
|
|
|
|
removed_stats.add_copy(info);
|
2020-03-02 00:41:47 +01:00
|
|
|
auto status = unlink(info.path);
|
|
|
|
LOG_IF(WARNING, status.is_error()) << "Failed to unlink file \"" << info.path << "\" during files gc: " << status;
|
|
|
|
send_closure(G()->file_manager(), &FileManager::on_file_unlink,
|
|
|
|
FullLocalFileLocation(info.file_type, info.path, info.mtime_nsec));
|
|
|
|
};
|
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
double now = Clocks::system();
|
2020-03-02 00:41:47 +01:00
|
|
|
|
|
|
|
// Keep all immune files
|
|
|
|
// Remove all files with (atime > now - max_time_from_last_access)
|
2019-10-21 15:25:56 +02:00
|
|
|
td::remove_if(files, [&](const FullFileInfo &info) {
|
|
|
|
if (token_) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (immune_types[narrow_cast<size_t>(info.file_type)]) {
|
|
|
|
type_immunity_ignored_cnt++;
|
2020-03-02 00:55:08 +01:00
|
|
|
new_stats.add_copy(info);
|
2019-10-21 15:25:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-10-22 01:12:58 +02:00
|
|
|
if (td::contains(parameters.exclude_owner_dialog_ids, info.owner_dialog_id)) {
|
2019-10-21 15:25:56 +02:00
|
|
|
exclude_owner_dialog_id_ignored_cnt++;
|
2020-03-02 00:55:08 +01:00
|
|
|
new_stats.add_copy(info);
|
2019-10-21 15:25:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-10-22 01:12:58 +02:00
|
|
|
if (!parameters.owner_dialog_ids.empty() && !td::contains(parameters.owner_dialog_ids, info.owner_dialog_id)) {
|
2019-10-21 15:25:56 +02:00
|
|
|
owner_dialog_id_ignored_cnt++;
|
2020-03-02 00:55:08 +01:00
|
|
|
new_stats.add_copy(info);
|
2019-10-21 15:25:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
2020-03-02 00:41:47 +01:00
|
|
|
if (info.mtime_nsec * 1e-9 > now - parameters.immunity_delay) {
|
2019-10-21 15:25:56 +02:00
|
|
|
// new files are immune to gc
|
|
|
|
time_immunity_ignored_cnt++;
|
2020-03-02 00:55:08 +01:00
|
|
|
new_stats.add_copy(info);
|
2019-10-21 15:25:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2020-03-02 00:41:47 +01:00
|
|
|
if (info.atime_nsec * 1e-9 < now - parameters.max_time_from_last_access) {
|
2019-10-21 15:25:56 +02:00
|
|
|
do_remove_file(info);
|
|
|
|
total_removed_size += info.size;
|
|
|
|
remove_by_atime_cnt++;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
2019-05-16 14:05:22 +02:00
|
|
|
if (token_) {
|
|
|
|
return promise.set_error(Status::Error(500, "Request aborted"));
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
// sort by max(atime, mtime)
|
2018-03-20 13:18:16 +01:00
|
|
|
std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) { return a.atime_nsec < b.atime_nsec; });
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2020-03-02 09:59:47 +01:00
|
|
|
// 1. Total size must be less than parameters.max_files_size
|
|
|
|
// 2. Total file count must be less than parameters.max_file_count
|
2018-12-31 20:04:05 +01:00
|
|
|
size_t remove_count = 0;
|
|
|
|
if (files.size() > parameters.max_file_count) {
|
|
|
|
remove_count = files.size() - parameters.max_file_count;
|
|
|
|
}
|
|
|
|
int64 remove_size = -parameters.max_files_size;
|
|
|
|
for (auto &file : files) {
|
|
|
|
remove_size += file.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
while (pos < files.size() && (remove_count > 0 || remove_size > 0)) {
|
2019-05-01 16:15:54 +02:00
|
|
|
if (token_) {
|
2019-05-16 14:05:22 +02:00
|
|
|
return promise.set_error(Status::Error(500, "Request aborted"));
|
2019-05-01 16:15:54 +02:00
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
if (remove_count > 0) {
|
|
|
|
remove_by_count_cnt++;
|
|
|
|
} else {
|
|
|
|
remove_by_size_cnt++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remove_count > 0) {
|
|
|
|
remove_count--;
|
|
|
|
}
|
|
|
|
remove_size -= files[pos].size;
|
|
|
|
|
|
|
|
total_removed_size += files[pos].size;
|
|
|
|
do_remove_file(files[pos]);
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (pos < files.size()) {
|
2020-03-02 00:55:08 +01:00
|
|
|
new_stats.add_copy(files[pos]);
|
2018-12-31 20:04:05 +01:00
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto end_time = Time::now();
|
|
|
|
|
2018-11-15 22:25:08 +01:00
|
|
|
VLOG(file_gc) << "Finish files gc: " << tag("time", end_time - begin_time) << tag("total", file_cnt)
|
|
|
|
<< tag("removed", remove_by_atime_cnt + remove_by_count_cnt + remove_by_size_cnt)
|
|
|
|
<< tag("total_size", format::as_size(total_size))
|
|
|
|
<< tag("total_removed_size", format::as_size(total_removed_size))
|
|
|
|
<< tag("by_atime", remove_by_atime_cnt) << tag("by_count", remove_by_count_cnt)
|
|
|
|
<< tag("by_size", remove_by_size_cnt) << tag("type_immunity", type_immunity_ignored_cnt)
|
|
|
|
<< tag("time_immunity", time_immunity_ignored_cnt)
|
|
|
|
<< tag("owner_dialog_id_immunity", owner_dialog_id_ignored_cnt)
|
|
|
|
<< tag("exclude_owner_dialog_id_immunity", exclude_owner_dialog_id_ignored_cnt);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2020-03-02 09:59:47 +01:00
|
|
|
promise.set_value({std::move(new_stats), std::move(removed_stats)});
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
2020-03-02 09:59:47 +01:00
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
} // namespace td
|