140 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "export/output/export_output_file.h"
 | |
| 
 | |
| #include "export/output/export_output_result.h"
 | |
| #include "export/output/export_output_stats.h"
 | |
| #include "base/qt/qt_string_view.h"
 | |
| 
 | |
| #include <QtCore/QFileInfo>
 | |
| #include <QtCore/QDir>
 | |
| 
 | |
| #include <gsl/gsl_util>
 | |
| 
 | |
| namespace Export {
 | |
| namespace Output {
 | |
| 
 | |
| File::File(const QString &path, Stats *stats) : _path(path), _stats(stats) {
 | |
| }
 | |
| 
 | |
| int64 File::size() const {
 | |
| 	return _offset;
 | |
| }
 | |
| 
 | |
| bool File::empty() const {
 | |
| 	return !_offset;
 | |
| }
 | |
| 
 | |
| Result File::writeBlock(const QByteArray &block) {
 | |
| 	const auto result = writeBlockAttempt(block);
 | |
| 	if (!result) {
 | |
| 		_file.reset();
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| Result File::writeBlockAttempt(const QByteArray &block) {
 | |
| 	if (_stats && !_inStats) {
 | |
| 		_inStats = true;
 | |
| 		_stats->incrementFiles();
 | |
| 	}
 | |
| 	if (const auto result = reopen(); !result) {
 | |
| 		return result;
 | |
| 	}
 | |
| 	const auto size = block.size();
 | |
| 	if (!size) {
 | |
| 		return Result::Success();
 | |
| 	}
 | |
| 	if (_file->write(block) == size && _file->flush()) {
 | |
| 		_offset += size;
 | |
| 		if (_stats) {
 | |
| 			_stats->incrementBytes(size);
 | |
| 		}
 | |
| 		return Result::Success();
 | |
| 	}
 | |
| 	return error();
 | |
| }
 | |
| 
 | |
| Result File::reopen() {
 | |
| 	if (_file && _file->isOpen()) {
 | |
| 		return Result::Success();
 | |
| 	}
 | |
| 	_file.emplace(_path);
 | |
| 	if (_file->exists()) {
 | |
| 		if (_file->size() < _offset) {
 | |
| 			return fatalError();
 | |
| 		} else if (!_file->resize(_offset)) {
 | |
| 			return error();
 | |
| 		}
 | |
| 	} else if (_offset > 0) {
 | |
| 		return fatalError();
 | |
| 	}
 | |
| 	if (_file->open(QIODevice::Append)) {
 | |
| 		return Result::Success();
 | |
| 	}
 | |
| 	const auto info = QFileInfo(_path);
 | |
| 	const auto dir = info.absoluteDir();
 | |
| 	return (!dir.exists()
 | |
| 		&& dir.mkpath(dir.absolutePath())
 | |
| 		&& _file->open(QIODevice::Append))
 | |
| 		? Result::Success()
 | |
| 		: error();
 | |
| }
 | |
| 
 | |
| Result File::error() const {
 | |
| 	return Result(Result::Type::Error, _path);
 | |
| }
 | |
| 
 | |
| Result File::fatalError() const {
 | |
| 	return Result(Result::Type::FatalError, _path);
 | |
| }
 | |
| 
 | |
| QString File::PrepareRelativePath(
 | |
| 		const QString &folder,
 | |
| 		const QString &suggested) {
 | |
| 	if (!QFile::exists(folder + suggested)) {
 | |
| 		return suggested;
 | |
| 	}
 | |
| 
 | |
| 	// Not lastIndexOf('.') so that "file.tar.xz" won't be messed up.
 | |
| 	const auto position = suggested.indexOf('.');
 | |
| 	const auto base = suggested.mid(0, position);
 | |
| 	const auto extension = (position >= 0)
 | |
| 		? base::StringViewMid(suggested, position)
 | |
| 		: QStringView();
 | |
| 	const auto relativePart = [&](int attempt) {
 | |
| 		auto result = base + QString(" (%1)").arg(attempt);
 | |
| 		result.append(extension);
 | |
| 		return result;
 | |
| 	};
 | |
| 	auto attempt = 0;
 | |
| 	while (true) {
 | |
| 		const auto relativePath = relativePart(++attempt);
 | |
| 		if (!QFile::exists(folder + relativePath)) {
 | |
| 			return relativePath;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| Result File::Copy(
 | |
| 		const QString &source,
 | |
| 		const QString &path,
 | |
| 		Stats *stats) {
 | |
| 	QFile f(source);
 | |
| 	if (!f.exists() || !f.open(QIODevice::ReadOnly)) {
 | |
| 		return Result(Result::Type::FatalError, source);
 | |
| 	}
 | |
| 	const auto bytes = f.readAll();
 | |
| 	if (bytes.size() != f.size()) {
 | |
| 		return Result(Result::Type::FatalError, source);
 | |
| 	}
 | |
| 	return File(path, stats).writeBlock(bytes);
 | |
| }
 | |
| 
 | |
| } // namespace Output
 | |
| } // namespace File
 | 
