Files
pip/libs/main/io_utils/pifiletransfer.cpp
peri4 caa7880cc4 get rid of piForeach
apply some code analyzer recommendations
ICU flag now check if libicu exists
prepare for more accurate growth of containers (limited PoT, then constantly increase size)
2024-11-20 20:01:47 +03:00

336 lines
9.5 KiB
C++

/*
PIP - Platform Independent Primitives
Class for send and receive files and directories via PIBaseTransfer
Andrey Bychkov work.a.b@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pifiletransfer.h"
const char PIFileTransfer::sign[] = {'P', 'F', 'T'};
PIFileTransfer::PIFileTransfer() {
for (uint i = 0; i < sizeof(sign); i++)
pftheader.sig[i] = sign[i];
pftheader.version = __PIFILETRANSFER_VERSION;
pftheader.session_id = 0;
pftheader.step = pft_None;
dir = PIDir::current();
started_ = scanning = false;
bytes_file_all = bytes_file_cur = 0;
CONNECT1(void, bool, this, sendFinished, this, send_finished);
CONNECT1(void, bool, this, receiveFinished, this, receive_finished);
}
PIFileTransfer::~PIFileTransfer() {
stop();
work_file.close();
}
bool PIFileTransfer::send(const PIFile & file) {
return send(file.path());
}
bool PIFileTransfer::send(const PIString & file) {
return send(PIVector<PIFile::FileInfo>() << PIFile::fileInfo(file));
}
bool PIFileTransfer::send(const PIStringList & files) {
PIVector<PIFile::FileInfo> fil;
for (const auto & file: files)
fil << PIFile::fileInfo(file);
return send(fil);
}
bool PIFileTransfer::send(PIVector<PIFile::FileInfo> entries) {
started_ = true;
PIVector<PFTFileInfo> allEntries;
for (int i = 0; i < entries.size_s(); i++) {
allEntries << PFTFileInfo(entries[i]);
allEntries.back().dest_path = entries[i].name();
if (entries[i].isDir()) {
cur_file_string = "scanning ... ";
scan_dir.setDir(entries[i].path);
scanning = true;
PIVector<PIFile::FileInfo> fls = scan_dir.allEntries();
scanning = false;
scan_dir.up();
// piCout << "files rel to" << d.absolutePath();
for (uint j = 0; j < fls.size(); j++) {
allEntries << PFTFileInfo(fls[j]);
allEntries.back().dest_path = scan_dir.relative(fls[j].path);
// piCout << " abs path" << fls[j].path;
// piCout << " rel path" << allEntries.back().dest_path;
}
}
}
return sendFiles(allEntries);
}
bool PIFileTransfer::sendFiles(const PIVector<PFTFileInfo> & files) {
files_ = files;
PIStringList names;
// piCoutObj << "prepare to send" << files_.size() << "files";
for (int i = 0; i < files_.size_s(); i++) {
if (names.contains(files_[i].path)) {
files_.remove(i);
--i;
} else
names << files_[i].path;
if (files_[i].isDir()) files_[i].size = 0;
// piCout << "prepare" << i << files_[i].path << files_[i].dest_path << files_[i].name();
}
randomize();
step_mutex.lock();
pftheader.session_id = randomi();
sendFilesStarted();
cur_file_string = "build session";
desc.clear();
desc << files_;
pftheader.step = pft_Description;
buildSession(PIVector<Part>() << Part(0, desc.size()));
cur_file_string = "";
step_mutex.unlock();
if (!send_process()) {
sendFilesFinished(false);
started_ = false;
return false;
}
step_mutex.lock();
pftheader.step = pft_Data;
PIVector<Part> pts;
for (int i = 0; i < files_.size_s(); i++) {
pts << Part(i + 1, files_[i].size);
}
buildSession(pts);
step_mutex.unlock();
bool ok = send_process();
sendFilesFinished(ok);
started_ = false;
return ok;
}
void PIFileTransfer::processFile(int id, ullong start, PIByteArray & data) {
// piCout << "processFile" << id << files_.size();
PFTFileInfo fi = files_[id - 1];
bytes_file_all = fi.size;
bytes_file_cur = start;
cur_file_string = fi.dest_path;
PIString path = dir.absolutePath() + dir.separator + fi.dest_path;
// piCout << "receive" << path << fi.size << data.size();
if (fi.isDir()) {
// piCoutObj << "make dir" << fi.entry.path;
if (!PIDir::make(path)) {
cur_file_string = "Error: While create directory \"" + path + "\"";
piCoutObj << cur_file_string;
stopReceive();
return;
}
}
if (fi.isFile()) {
if (work_file.path() != path || !work_file.isOpened()) {
// piCout << "file close";
work_file.close();
// piCout << "new file" << path << work_file.path() << work_file.isOpened();
if (!work_file.open(path, PIIODevice::ReadWrite)) {
cur_file_string = "Error: While open file \"" + path + "\"";
piCoutObj << cur_file_string;
stopReceive();
return;
}
PIFile::applyFileInfo(path, fi);
if (work_file.size() > fi.size) {
// piCoutObj << "error size" << work_file.size() << fi.size;
work_file.clear();
work_file.resize(fi.size);
// piCoutObj << "correct size" << work_file.size() << fi.size;
}
}
// piCoutObj << "write file" << path << work_file.path() << work_file.size() << fi.entry.size << work_file.pos() << fi.fstart <<
// fi.fsize;
if (work_file.size() < (llong)start) {
// piCoutObj << "error pos size" << work_file.pos() << fi.fstart;
work_file.resize(start);
// piCoutObj << "correct pos size" << work_file.pos() << fi.fstart;
}
if (work_file.size() > fi.size) {
piCoutObj << "****** error size" << work_file.size() << fi.size;
work_file.clear();
work_file.resize(fi.size);
piCoutObj << "****** correct size" << work_file.size() << fi.size;
}
// if (fi.fstart != work_file.pos()) piCoutObj << "error pos" << work_file.pos() << fi.fstart;
work_file.seek(start);
int rs = work_file.write(data.data(), data.size());
if (rs != data.size_s()) {
cur_file_string = "Error: While write file \"" + fi.path + "\" (must " + PIString::fromNumber(data.size()) + ", but write " +
PIString::fromNumber(rs) + ")";
piCoutObj << cur_file_string;
stopReceive();
return;
}
}
}
PIByteArray PIFileTransfer::buildPacket(Part p) {
// piCoutObj << "Step" << pftheader.step;
// piCoutObj << "session id" << pftheader.session_id;
PIByteArray ba;
switch (pftheader.step) {
case pft_None: stopSend(); return PIByteArray();
case pft_Description:
ba.resize(p.size);
memcpy(ba.data(), desc.data(p.start), p.size);
return ba;
case pft_Data:
// piCout << "send data" << p.id << files_.size();
PIFile::FileInfo fi = files_[p.id - 1];
if (fi.isFile()) {
// piCout << "send file" << fi.name() << fi.size;
PIString path = fi.path;
if (work_file.path() != path || !work_file.isOpened()) {
if (!work_file.open(path, PIIODevice::ReadOnly)) {
break_ = true;
cur_file_string = "Error: While open file \"" + fi.path + "\"";
piCoutObj << cur_file_string;
stopSend();
return PIByteArray();
}
}
work_file.seek(p.start);
ba.resize(p.size);
int rs = work_file.read(ba.data(), ba.size());
if ((llong)rs != (llong)p.size) {
break_ = true;
cur_file_string = "Error: While read file \"" + fi.path + "\" (must " + PIString::fromNumber(p.size) + ", but read " +
PIString::fromNumber(rs) + ")";
piCoutObj << cur_file_string;
stopSend();
return PIByteArray();
}
}
cur_file_string = fi.path;
bytes_file_all = fi.size;
bytes_file_cur = p.start;
return ba;
}
return ba;
}
void PIFileTransfer::receivePart(PIBaseTransfer::Part fi, PIByteArray ba, PIByteArray pheader) {
PFTHeader h;
// piCout << pheader.size() << sizeof(PFTHeader);
pheader >> h;
// piCout << h.session_id;
PIMutexLocker ml(step_mutex);
StepType st = (StepType)h.step;
pftheader.step = st;
if (!h.check_sig()) {
cur_file_string = "Error: Check signature: File Transfer incompatable or invalid version!";
piCoutObj << cur_file_string;
stopReceive();
return;
}
switch (st) {
case pft_None: break;
case pft_Description:
pftheader.session_id = h.session_id;
if (desc.size() < fi.start + fi.size) desc.resize(fi.start + fi.size);
memcpy(desc.data(fi.start), ba.data(), ba.size_s());
break;
case pft_Data:
if (h.session_id == pftheader.session_id)
processFile(fi.id, fi.start, ba);
else
stopReceive();
break;
default: break;
}
}
PIString PIFileTransfer::curFile() const {
PIString s = cur_file_string;
if (scanning) return s + scan_dir.scanDir();
return s;
}
PIByteArray PIFileTransfer::customHeader() {
PIByteArray ba;
ba << pftheader;
return ba;
}
void PIFileTransfer::beginReceive() {
if (pftheader.step == pft_None) {
files_.clear();
// piCoutObj << "start receive"
started_ = true;
receiveFilesStarted();
}
}
void PIFileTransfer::receive_finished(bool ok) {
step_mutex.lock();
StepType st = (StepType)pftheader.step;
step_mutex.unlock();
if (st == pft_Description) {
bool user_ok = true;
if (ok) {
// piCoutObj << desc.size() << PICoutManipulators::Hex << desc;
desc >> files_;
// piCoutObj << files_;
PIStringList files;
for (const auto & fi: files_)
files << fi.dest_path;
pause();
receiveFilesRequest(files, bytesAll(), &user_ok);
}
if (!ok || !user_ok) {
pftheader.session_id--;
piCoutObj << "receive aborted";
receiveFilesFinished(false);
started_ = false;
} else
resume();
}
if (st == pft_Data) {
// piCoutObj << "file close";
work_file.close();
receiveFilesFinished(ok);
started_ = false;
}
}
void PIFileTransfer::send_finished(bool ok) {
// piCoutObj << "file close";
if (pftheader.step == pft_Data) {
work_file.close();
}
}