├── README.md ├── backup.pro ├── backup.pro.user ├── check.h ├── compressor.h ├── decompressor.h ├── image.qrc ├── image ├── add.png ├── backup.png ├── backup1.png ├── clear.png ├── cloud.png ├── delete.png ├── icon.ico ├── icon.png ├── restore.png ├── start.png └── task.png ├── main.cpp ├── md5.h ├── pack.h ├── server.py ├── task.cpp ├── task.h ├── unpack.h ├── widget.cpp ├── widget.h ├── widget.ui └── 小组实验报告.docx /README.md: -------------------------------------------------------------------------------- 1 | # 软件开发综合实验 2 | 3 | 如果Qt打开工程文件不正常的话,请删掉backup.pro.user,重新构建工程。 4 | -------------------------------------------------------------------------------- /backup.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2020-12-02T19:53:07 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | QT += network 9 | 10 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 11 | 12 | TARGET = backup 13 | TEMPLATE = app 14 | 15 | # The following define makes your compiler emit warnings if you use 16 | # any feature of Qt which has been marked as deprecated (the exact warnings 17 | # depend on your compiler). Please consult the documentation of the 18 | # deprecated API in order to know how to port your code away from it. 19 | DEFINES += QT_DEPRECATED_WARNINGS 20 | 21 | # You can also make your code fail to compile if you use deprecated APIs. 22 | # In order to do so, uncomment the following line. 23 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 24 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 25 | 26 | CONFIG += c++11 27 | RC_ICONS = image/icon.ico 28 | 29 | SOURCES += \ 30 | main.cpp \ 31 | widget.cpp \ 32 | task.cpp 33 | 34 | HEADERS += \ 35 | widget.h \ 36 | task.h \ 37 | compressor.h \ 38 | decompressor.h \ 39 | md5.h \ 40 | check.h \ 41 | pack.h \ 42 | unpack.h 43 | 44 | msvc { 45 | QMAKE_CFLAGS += /utf-8 46 | QMAKE_CXXFLAGS += /utf-8 47 | } 48 | 49 | FORMS += \ 50 | widget.ui 51 | # Default rules for deployment. 52 | qnx: target.path = /tmp/$${TARGET}/bin 53 | else: unix:!android: target.path = /opt/$${TARGET}/bin 54 | !isEmpty(target.path): INSTALLS += target 55 | 56 | RESOURCES += \ 57 | image.qrc 58 | -------------------------------------------------------------------------------- /backup.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {c4c26f2e-216f-439e-ba21-006133600414} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | -fno-delayed-template-parsing 60 | 61 | true 62 | false 63 | 64 | 65 | 66 | true 67 | 68 | 69 | 70 | ProjectExplorer.Project.Target.0 71 | 72 | Desktop Qt 5.11.2 MinGW 32bit 73 | Desktop Qt 5.11.2 MinGW 32bit 74 | qt.qt5.5112.win32_mingw53_kit 75 | 1 76 | 0 77 | 0 78 | 79 | D:/CODE/backup/build-backup-Desktop_Qt_5_11_2_MinGW_32bit-Debug 80 | 81 | 82 | true 83 | qmake 84 | 85 | QtProjectManager.QMakeBuildStep 86 | true 87 | 88 | false 89 | false 90 | false 91 | 92 | 93 | true 94 | Make 95 | 96 | Qt4ProjectManager.MakeStep 97 | 98 | false 99 | 100 | 101 | 102 | 2 103 | Build 104 | 105 | ProjectExplorer.BuildSteps.Build 106 | 107 | 108 | 109 | true 110 | Make 111 | 112 | Qt4ProjectManager.MakeStep 113 | 114 | true 115 | clean 116 | 117 | 118 | 1 119 | Clean 120 | 121 | ProjectExplorer.BuildSteps.Clean 122 | 123 | 2 124 | false 125 | 126 | Debug 127 | Debug 128 | Qt4ProjectManager.Qt4BuildConfiguration 129 | 2 130 | true 131 | 132 | 133 | D:/CODE/backup/build-backup-Desktop_Qt_5_11_2_MinGW_32bit-Release 134 | 135 | 136 | true 137 | qmake 138 | 139 | QtProjectManager.QMakeBuildStep 140 | false 141 | 142 | false 143 | false 144 | true 145 | 146 | 147 | true 148 | Make 149 | 150 | Qt4ProjectManager.MakeStep 151 | 152 | false 153 | 154 | 155 | 156 | 2 157 | Build 158 | 159 | ProjectExplorer.BuildSteps.Build 160 | 161 | 162 | 163 | true 164 | Make 165 | 166 | Qt4ProjectManager.MakeStep 167 | 168 | true 169 | clean 170 | 171 | 172 | 1 173 | Clean 174 | 175 | ProjectExplorer.BuildSteps.Clean 176 | 177 | 2 178 | false 179 | 180 | Release 181 | Release 182 | Qt4ProjectManager.Qt4BuildConfiguration 183 | 0 184 | true 185 | 186 | 187 | D:/CODE/backup/build-backup-Desktop_Qt_5_11_2_MinGW_32bit-Profile 188 | 189 | 190 | true 191 | qmake 192 | 193 | QtProjectManager.QMakeBuildStep 194 | true 195 | 196 | false 197 | true 198 | true 199 | 200 | 201 | true 202 | Make 203 | 204 | Qt4ProjectManager.MakeStep 205 | 206 | false 207 | 208 | 209 | 210 | 2 211 | Build 212 | 213 | ProjectExplorer.BuildSteps.Build 214 | 215 | 216 | 217 | true 218 | Make 219 | 220 | Qt4ProjectManager.MakeStep 221 | 222 | true 223 | clean 224 | 225 | 226 | 1 227 | Clean 228 | 229 | ProjectExplorer.BuildSteps.Clean 230 | 231 | 2 232 | false 233 | 234 | Profile 235 | Profile 236 | Qt4ProjectManager.Qt4BuildConfiguration 237 | 0 238 | true 239 | 240 | 3 241 | 242 | 243 | 0 244 | 部署 245 | 246 | ProjectExplorer.BuildSteps.Deploy 247 | 248 | 1 249 | Deploy Configuration 250 | 251 | ProjectExplorer.DefaultDeployConfiguration 252 | 253 | 1 254 | 255 | 256 | false 257 | false 258 | 1000 259 | 260 | true 261 | 262 | false 263 | false 264 | false 265 | false 266 | true 267 | 0.01 268 | 10 269 | true 270 | 1 271 | 25 272 | 273 | 1 274 | true 275 | false 276 | true 277 | valgrind 278 | 279 | 0 280 | 1 281 | 2 282 | 3 283 | 4 284 | 5 285 | 6 286 | 7 287 | 8 288 | 9 289 | 10 290 | 11 291 | 12 292 | 13 293 | 14 294 | 295 | 2 296 | 297 | backup 298 | 299 | Qt4ProjectManager.Qt4RunConfiguration:D:/CODE/backup/backup/backup.pro 300 | true 301 | 302 | backup.pro 303 | 304 | 305 | 3768 306 | false 307 | true 308 | false 309 | false 310 | true 311 | 312 | 1 313 | 314 | 315 | 316 | ProjectExplorer.Project.Target.1 317 | 318 | Desktop Qt 5.11.2 MSVC2017 64bit 319 | Desktop Qt 5.11.2 MSVC2017 64bit 320 | qt.qt5.5112.win64_msvc2017_64_kit 321 | 1 322 | 0 323 | 0 324 | 325 | D:/CODE/backup/build-backup-Desktop_Qt_5_11_2_MSVC2017_64bit-Debug 326 | 327 | 328 | true 329 | qmake 330 | 331 | QtProjectManager.QMakeBuildStep 332 | true 333 | 334 | false 335 | false 336 | false 337 | 338 | 339 | true 340 | Make 341 | 342 | Qt4ProjectManager.MakeStep 343 | 344 | false 345 | 346 | 347 | 348 | 2 349 | Build 350 | 351 | ProjectExplorer.BuildSteps.Build 352 | 353 | 354 | 355 | true 356 | Make 357 | 358 | Qt4ProjectManager.MakeStep 359 | 360 | true 361 | clean 362 | 363 | 364 | 1 365 | Clean 366 | 367 | ProjectExplorer.BuildSteps.Clean 368 | 369 | 2 370 | false 371 | 372 | Debug 373 | Debug 374 | Qt4ProjectManager.Qt4BuildConfiguration 375 | 2 376 | true 377 | 378 | 379 | D:/CODE/backup/build-backup-Desktop_Qt_5_11_2_MSVC2017_64bit-Release 380 | 381 | 382 | true 383 | qmake 384 | 385 | QtProjectManager.QMakeBuildStep 386 | false 387 | 388 | false 389 | false 390 | true 391 | 392 | 393 | true 394 | Make 395 | 396 | Qt4ProjectManager.MakeStep 397 | 398 | false 399 | 400 | 401 | 402 | 2 403 | Build 404 | 405 | ProjectExplorer.BuildSteps.Build 406 | 407 | 408 | 409 | true 410 | Make 411 | 412 | Qt4ProjectManager.MakeStep 413 | 414 | true 415 | clean 416 | 417 | 418 | 1 419 | Clean 420 | 421 | ProjectExplorer.BuildSteps.Clean 422 | 423 | 2 424 | false 425 | 426 | Release 427 | Release 428 | Qt4ProjectManager.Qt4BuildConfiguration 429 | 0 430 | true 431 | 432 | 433 | D:/CODE/backup/build-backup-Desktop_Qt_5_11_2_MSVC2017_64bit-Profile 434 | 435 | 436 | true 437 | qmake 438 | 439 | QtProjectManager.QMakeBuildStep 440 | true 441 | 442 | false 443 | true 444 | true 445 | 446 | 447 | true 448 | Make 449 | 450 | Qt4ProjectManager.MakeStep 451 | 452 | false 453 | 454 | 455 | 456 | 2 457 | Build 458 | 459 | ProjectExplorer.BuildSteps.Build 460 | 461 | 462 | 463 | true 464 | Make 465 | 466 | Qt4ProjectManager.MakeStep 467 | 468 | true 469 | clean 470 | 471 | 472 | 1 473 | Clean 474 | 475 | ProjectExplorer.BuildSteps.Clean 476 | 477 | 2 478 | false 479 | 480 | Profile 481 | Profile 482 | Qt4ProjectManager.Qt4BuildConfiguration 483 | 0 484 | true 485 | 486 | 3 487 | 488 | 489 | 0 490 | 部署 491 | 492 | ProjectExplorer.BuildSteps.Deploy 493 | 494 | 1 495 | Deploy Configuration 496 | 497 | ProjectExplorer.DefaultDeployConfiguration 498 | 499 | 1 500 | 501 | 502 | false 503 | false 504 | 1000 505 | 506 | true 507 | 508 | false 509 | false 510 | false 511 | false 512 | true 513 | 0.01 514 | 10 515 | true 516 | 1 517 | 25 518 | 519 | 1 520 | true 521 | false 522 | true 523 | valgrind 524 | 525 | 0 526 | 1 527 | 2 528 | 3 529 | 4 530 | 5 531 | 6 532 | 7 533 | 8 534 | 9 535 | 10 536 | 11 537 | 12 538 | 13 539 | 14 540 | 541 | 2 542 | 543 | backup 544 | 545 | Qt4ProjectManager.Qt4RunConfiguration:D:/CODE/backup/backup/backup.pro 546 | true 547 | 548 | backup.pro 549 | 550 | 551 | 3768 552 | false 553 | true 554 | false 555 | false 556 | true 557 | 558 | 1 559 | 560 | 561 | 562 | ProjectExplorer.Project.TargetCount 563 | 2 564 | 565 | 566 | ProjectExplorer.Project.Updater.FileVersion 567 | 18 568 | 569 | 570 | Version 571 | 18 572 | 573 | 574 | -------------------------------------------------------------------------------- /check.h: -------------------------------------------------------------------------------- 1 | #ifndef CHECK_H 2 | #define CHECK_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class Check { 12 | static QByteArray getMD5ByFilename(QString filename) { 13 | QFile file(filename); 14 | file.open(QIODevice::ReadOnly); 15 | QByteArray md5 = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5); 16 | file.close(); 17 | return md5.toHex(); 18 | } 19 | public: 20 | static QVector> check(QList files, QString directory) { 21 | QVector> result; 22 | if (files.empty()) return result; 23 | auto root = QFileInfo(files[0]).path(); 24 | for (const auto& file : files) { 25 | if (!QFileInfo(file).exists()) { 26 | result.push_back(QPair(file, 0)); 27 | continue; 28 | } 29 | if (QFileInfo(file).isDir()) { 30 | QDirIterator iter(file, QDirIterator::Subdirectories); 31 | while (iter.hasNext()) { 32 | iter.next(); 33 | QFileInfo info = iter.fileInfo(); 34 | if (info.fileName() == "." || info.fileName() == "..") continue; 35 | auto relativePath = info.absoluteFilePath().replace(root, ""); 36 | auto path = directory + "/" + relativePath; 37 | if (!QFileInfo(path).exists()) { 38 | result.push_back(QPair(info.absoluteFilePath(), 2)); 39 | } else if (info.isFile()) { 40 | if (getMD5ByFilename(info.absoluteFilePath()) != getMD5ByFilename(path)) { 41 | result.push_back(QPair(info.absoluteFilePath(), 1)); 42 | } 43 | } 44 | } 45 | } else { 46 | auto relativePath = QString(file).replace(root, ""); 47 | auto path = directory + "/" + relativePath; 48 | if (!QFileInfo(path).exists()) { 49 | result.push_back(QPair(file, 2)); 50 | } else if (getMD5ByFilename(file) != getMD5ByFilename(path)) { 51 | result.push_back(QPair(file, 1)); 52 | } 53 | } 54 | } 55 | return result; 56 | } 57 | }; 58 | 59 | #endif // CHECK_H 60 | -------------------------------------------------------------------------------- /compressor.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPRESSOR_H_INCLUDED 2 | #define COMPRESSOR_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "md5.h" 13 | 14 | #ifndef ull 15 | #define ull unsigned long long 16 | #endif // ull 17 | 18 | using namespace std; 19 | class Compressor { 20 | map codeMap; 21 | struct haffNode { 22 | ull freq; 23 | unsigned char uchar; 24 | string code; 25 | struct haffNode* left = 0; 26 | struct haffNode* right = 0; 27 | }; 28 | struct cmp { 29 | bool operator ()(const haffNode* a, const haffNode* b) { 30 | return a->freq > b->freq; 31 | } 32 | }; 33 | void encode(haffNode* pn, string code) { 34 | pn->code = code; 35 | if (pn->left) encode(pn->left, code + "0"); 36 | if (pn->right) encode(pn->right, code + "1"); 37 | if (!pn->left && !pn->right) { 38 | codeMap[pn->uchar] = code; 39 | } 40 | } 41 | public: 42 | /** 返回值说明: 43 | 0:正常执行 44 | 1:源文件扩展名不是tar 45 | 2:打开源文件失败 46 | 3:打开目标文件失败 47 | **/ 48 | int compress(string sourcePath, string destinationPath, string pw = "") { 49 | codeMap.clear(); 50 | if (sourcePath.substr(sourcePath.find_last_of(".") + 1) != "tar") 51 | return 1; // 源文件扩展名不是tar 52 | ifstream inFile; 53 | qDebug() << QString::fromStdString(sourcePath); 54 | inFile.open(sourcePath, ios::in | ios::binary); 55 | if (!inFile) 56 | return 2; // 打开源文件失败 57 | ofstream outFile; 58 | sourcePath = QFileInfo(QString::fromStdString(sourcePath)).fileName().toStdString(); 59 | string newFileName = destinationPath + sourcePath.substr(0, sourcePath.find_last_of(".")) + ".bak"; 60 | outFile.open(newFileName, ios::out | ios::binary); 61 | if (!outFile) { 62 | inFile.close(); 63 | return 3; // 打开目标文件失败 64 | } 65 | /**统计词频**/ 66 | unsigned char uchar; 67 | map freqMap; 68 | while (inFile.read((char*)&uchar, sizeof(char))) { 69 | freqMap[uchar]++; 70 | } 71 | /*for(int i=0;i<256;i++){ 72 | if(freqMap.count(i)) 73 | printf("%d %d\n",i,freqMap[i]); 74 | }*/ 75 | /**建立词频小顶堆**/ 76 | priority_queue, cmp> freqHeap; 77 | map::reverse_iterator it; 78 | for (it = freqMap.rbegin(); it != freqMap.rend(); it++) { 79 | haffNode* pn = new (haffNode); 80 | pn->freq = it->second; 81 | pn->uchar = it->first; 82 | pn->left = pn->right = 0; 83 | freqHeap.push(pn); 84 | } 85 | /**构建哈夫曼树**/ 86 | while (freqHeap.size() > 1) { 87 | haffNode* pn1 = freqHeap.top(); 88 | freqHeap.pop(); 89 | haffNode* pn2 = freqHeap.top(); 90 | freqHeap.pop(); 91 | haffNode* pn = new (haffNode); 92 | pn->freq = pn1->freq + pn2->freq; 93 | pn->left = pn1; 94 | pn->right = pn2; 95 | freqHeap.push(pn); 96 | } 97 | haffNode* root = freqHeap.top(); 98 | codeMap.clear(); 99 | /**用哈夫曼树编码**/ 100 | encode(root, ""); 101 | //cout << endl << codeMap.size() << endl; 102 | /**写入压缩文件头部:补零数+密码标志(暂时留空)**/ 103 | const unsigned char zeroUC = 0; 104 | outFile.write((char*)&zeroUC, sizeof(zeroUC)); 105 | /**写入压缩文件头部:密码**/ 106 | int pw_len = pw.length(); 107 | if (pw_len) { 108 | string pwMD5 = MD5().getMD5(pw).c_str(); 109 | //cout << "c:pwMD5=" << pwMD5 << endl; 110 | //outFile.write((char*)&pwMD5, 16); 111 | outFile << pwMD5; 112 | } 113 | /**写入压缩文件头部:频率表**/ 114 | string freqTable; 115 | const ull zeroULL = 0; 116 | for (int i = 0; i < 256; i++) { 117 | if (freqMap.count(i) == 0) { 118 | outFile.write((char*)&zeroULL, sizeof(zeroULL)); 119 | } else { 120 | ull freq = freqMap[i]; 121 | outFile.write((char*)&freq, sizeof(freq)); 122 | } 123 | } 124 | //outFile.write((char*)&bi, sizeof(bi)); 125 | /**写入压缩文件主体(加密),最后补上补零数+密码标志字段**/ 126 | { 127 | int pw_index = 0; // password_index 128 | inFile.clear(); 129 | inFile.seekg(0); 130 | string buf; 131 | unsigned char uchar; 132 | while (inFile.read((char*)&uchar, sizeof(uchar))) { 133 | buf += codeMap[uchar]; 134 | //printf("当前buf长:%d\n", buf.length()); 135 | while (buf.length() >= 8) { 136 | bitset<8> bs(buf.substr(0, 8)); 137 | uchar = bs.to_ulong(); 138 | if (pw_len) { 139 | uchar ^= pw[pw_index++]; 140 | pw_index %= pw_len; 141 | } 142 | outFile.write((char*)&uchar, sizeof(uchar)); 143 | buf = buf.substr(8); 144 | } 145 | } 146 | // 末尾处理 147 | int zeroNum = (8 - buf.length()) % 8; 148 | if (zeroNum) { 149 | for (int i = 0; i < zeroNum; i++) { 150 | buf += "0"; 151 | } 152 | bitset<8> bs(buf.substr(0, 8)); 153 | uchar = bs.to_ulong(); 154 | if (pw_len) { 155 | uchar ^= pw[pw_index++]; 156 | pw_index %= pw_len; 157 | } 158 | outFile.write((char*)&uchar, sizeof(uchar)); 159 | } 160 | //写入头部预留的补零数+密码标志字段 161 | outFile.clear(); 162 | outFile.seekp(0); 163 | if (pw_len) { 164 | zeroNum += 8; 165 | } 166 | //cout< 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "md5.h" 14 | 15 | #ifndef ull 16 | #define ull unsigned long long 17 | #endif // ull 18 | 19 | using namespace std; 20 | class Decompressor { 21 | map codeMap; 22 | struct haffNode { 23 | ull freq; 24 | unsigned char uchar; 25 | string code; 26 | struct haffNode* left = 0; 27 | struct haffNode* right = 0; 28 | }; 29 | struct cmp { 30 | bool operator ()(const haffNode* a, const haffNode* b) { 31 | return a->freq > b->freq; 32 | } 33 | }; 34 | void insert_node(haffNode* father, unsigned char uchar, string code) { 35 | if (code.empty()) { 36 | father->uchar = uchar; 37 | return; 38 | } 39 | char way = code[0]; 40 | if (way == '0') { 41 | if (!(father->left)) { 42 | haffNode* son = new (haffNode); 43 | father->left = son; 44 | } 45 | insert_node(father->left, uchar, code.substr(1)); 46 | } else { 47 | if (!(father->right)) { 48 | haffNode* son = new (haffNode); 49 | father->right = son; 50 | } 51 | insert_node(father->right, uchar, code.substr(1)); 52 | } 53 | } 54 | public: 55 | /** 返回值说明: 56 | 0:正常执行 57 | 1:源文件扩展名不是bak 58 | 2:打开源文件失败 59 | 3:打开目标文件失败 60 | 4:文件过短,频率表不完整 61 | 5:文件结尾不完整 62 | 6:密码错误 63 | 7:解码错误 64 | **/ 65 | int decompress(string sourcePath, string destinationPath, string pw = "") { 66 | /**打开源文件**/ 67 | if (sourcePath.substr(sourcePath.find_last_of(".") + 1) != "bak") 68 | return 1; // 源文件扩展名不是bak 69 | ifstream inFile; 70 | inFile.open(sourcePath, ios::in | ios::binary); 71 | if (!inFile) 72 | return 2; // 打开源文件失败 73 | /**密码校验**/ 74 | unsigned char uchar; 75 | int pw_len = pw.length(); 76 | inFile.read((char*)&uchar, sizeof(char)); 77 | int zeroNum = uchar; 78 | if (zeroNum >= 8) { 79 | zeroNum -= 8; 80 | char crMD5_c[33]; 81 | inFile.get(crMD5_c, 33); 82 | string crMD5 = string(crMD5_c, 32); 83 | //cout<<"d:crMD5="< freqMap; 105 | int i = 0; 106 | for (i = 0; i < 256; i++) { 107 | inFile.read((char*)&freq, sizeof(freq)); 108 | if (freq) { 109 | freqMap[i] = freq; 110 | } 111 | } 112 | if (i != 256) 113 | return 4; // 文件过短,频率表不完整 114 | /**建立词频小顶堆**/ 115 | priority_queue, cmp> freqHeap; 116 | map::reverse_iterator it; 117 | for (it = freqMap.rbegin(); it != freqMap.rend(); it++) { 118 | haffNode* pn = new (haffNode); 119 | pn->freq = it->second; 120 | pn->uchar = it->first; 121 | pn->left = pn->right = 0; 122 | freqHeap.push(pn); 123 | } 124 | /**构建哈夫曼树**/ 125 | while (freqHeap.size() > 1) { 126 | haffNode* pn1 = freqHeap.top(); 127 | freqHeap.pop(); 128 | haffNode* pn2 = freqHeap.top(); 129 | freqHeap.pop(); 130 | haffNode* pn = new (haffNode); 131 | pn->freq = pn1->freq + pn2->freq; 132 | pn->left = pn1; 133 | pn->right = pn2; 134 | freqHeap.push(pn); 135 | } 136 | haffNode* root = freqHeap.top(); 137 | codeMap.clear(); 138 | /**读出主体,用哈夫曼树树解码**/ 139 | haffNode* decodePointer = root; 140 | string buf, now; 141 | inFile.read((char*)&uchar, sizeof(unsigned char)); 142 | int pw_index = 0; 143 | if (pw_len) { 144 | uchar ^= pw[pw_index++]; 145 | pw_index %= pw_len; 146 | } 147 | bitset<8> bs = uchar; 148 | buf = bs.to_string(); 149 | while (inFile.read((char*)&uchar, sizeof(unsigned char))) { 150 | if (pw_len) { 151 | uchar ^= pw[pw_index++]; 152 | pw_index %= pw_len; 153 | } 154 | bitset<8> bs = uchar; 155 | now = buf; 156 | buf = bs.to_string(); 157 | for (char i = 0; i < 8; i++) { 158 | if (now[i] == '0') { 159 | if (!decodePointer->left) 160 | return 7; // 解码错误 161 | decodePointer = decodePointer->left; 162 | } else { 163 | if (!decodePointer->right) 164 | return 7; // 解码错误 165 | decodePointer = decodePointer->right; 166 | } 167 | if (!(decodePointer->left || decodePointer->right)) { 168 | //cout<uchar)<<" "; 169 | outFile.write((char*) & (decodePointer->uchar), sizeof(decodePointer->uchar)); 170 | decodePointer = root; 171 | } 172 | } 173 | } 174 | //最后一字节 175 | now = buf; 176 | for (char i = 0; i < (8 - zeroNum) % 8; i++) { 177 | if (now[i] == '0') { 178 | if (!decodePointer->left) 179 | return 7; // 解码错误 180 | decodePointer = decodePointer->left; 181 | } else { 182 | if (!decodePointer->right) 183 | return 7; // 解码错误 184 | decodePointer = decodePointer->right; 185 | } 186 | if (!(decodePointer->left || decodePointer->right)) { 187 | //cout<uchar)<<" "; 188 | outFile.write((char*) & (decodePointer->uchar), sizeof(unsigned char)); 189 | decodePointer = root; 190 | } 191 | } 192 | 193 | inFile.close(); 194 | outFile.close(); 195 | if (decodePointer == root) return 0; // 正常执行 196 | return 5; // 文件结尾不完整 197 | } 198 | }; 199 | #endif // DECOMPRESSOR_H_INCLUDED 200 | -------------------------------------------------------------------------------- /image.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/backup.png 4 | image/restore.png 5 | image/task.png 6 | image/icon.png 7 | image/add.png 8 | image/clear.png 9 | image/delete.png 10 | image/start.png 11 | 12 | 13 | -------------------------------------------------------------------------------- /image/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/add.png -------------------------------------------------------------------------------- /image/backup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/backup.png -------------------------------------------------------------------------------- /image/backup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/backup1.png -------------------------------------------------------------------------------- /image/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/clear.png -------------------------------------------------------------------------------- /image/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/cloud.png -------------------------------------------------------------------------------- /image/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/delete.png -------------------------------------------------------------------------------- /image/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/icon.ico -------------------------------------------------------------------------------- /image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/icon.png -------------------------------------------------------------------------------- /image/restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/restore.png -------------------------------------------------------------------------------- /image/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/start.png -------------------------------------------------------------------------------- /image/task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/image/task.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include 3 | 4 | int main(int argc, char* argv[]) { 5 | QApplication a(argc, argv); 6 | Widget w; 7 | w.show(); 8 | return a.exec(); 9 | } 10 | -------------------------------------------------------------------------------- /md5.h: -------------------------------------------------------------------------------- 1 | #ifndef MD5_H_INCLUDED 2 | #define MD5_H_INCLUDED 3 | #include 4 | using namespace std; 5 | 6 | class MD5 { 7 | unsigned int shift(unsigned int x, unsigned int n) { 8 | return ((x) << (n)) | ((x) >> (32 - (n))); 9 | } 10 | unsigned int F(unsigned int x, unsigned int y, unsigned int z) { 11 | return ((x) & (y)) | ((~x) & (z)); 12 | } 13 | unsigned int G(unsigned int x, unsigned int y, unsigned int z) { 14 | return ((x) & (z)) | ((y) & (~z)); 15 | } 16 | unsigned int H(unsigned int x, unsigned int y, unsigned int z) { 17 | return (x) ^ (y) ^ (z); 18 | } 19 | unsigned int I(unsigned int x, unsigned int y, unsigned int z) { 20 | return (y) ^ ((x) | (~z)); 21 | } 22 | 23 | static constexpr unsigned int A = 0x67452301; 24 | static constexpr unsigned int B = 0xefcdab89; 25 | static constexpr unsigned int C = 0x98badcfe; 26 | static constexpr unsigned int D = 0x10325476; 27 | 28 | //strBaye的长度 29 | unsigned int strlength; 30 | //A,B,C,D的临时变量 31 | unsigned int atemp; 32 | unsigned int btemp; 33 | unsigned int ctemp; 34 | unsigned int dtemp; 35 | 36 | const unsigned int k[64] = { 37 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 38 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 39 | 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 40 | 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 41 | 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 42 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 43 | 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 44 | 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 45 | 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 46 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 47 | 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 48 | 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 49 | 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 50 | }; 51 | //向左位移数 52 | const unsigned int s[64] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 53 | 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 54 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 55 | 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 56 | }; 57 | const char str16[17] = "0123456789abcdef"; 58 | void mainLoop(unsigned int M[]) { 59 | unsigned int f, g; 60 | unsigned int a = atemp; 61 | unsigned int b = btemp; 62 | unsigned int c = ctemp; 63 | unsigned int d = dtemp; 64 | for (unsigned int i = 0; i < 64; i++) { 65 | if (i < 16) { 66 | f = F(b, c, d); 67 | g = i; 68 | } else if (i < 32) { 69 | f = G(b, c, d); 70 | g = (5 * i + 1) % 16; 71 | } else if (i < 48) { 72 | f = H(b, c, d); 73 | g = (3 * i + 5) % 16; 74 | } else { 75 | f = I(b, c, d); 76 | g = (7 * i) % 16; 77 | } 78 | unsigned int tmp = d; 79 | d = c; 80 | c = b; 81 | b = b + shift((a + f + k[i] + M[g]), s[i]); 82 | a = tmp; 83 | } 84 | atemp = a + atemp; 85 | btemp = b + btemp; 86 | ctemp = c + ctemp; 87 | dtemp = d + dtemp; 88 | } 89 | /* 90 | *填充函数 91 | *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64) 92 | *填充方式为先加一个1,其它位补零 93 | *最后加上64位的原来长度 94 | */ 95 | unsigned int* add(string str) { 96 | unsigned int num = ((str.length() + 8) / 64) + 1; //以512位,64个字节为一组 97 | unsigned int* strByte = new unsigned int[num * 16]; //64/4=16,所以有16个整数 98 | strlength = num * 16; 99 | for (unsigned int i = 0; i < num * 16; i++) 100 | strByte[i] = 0; 101 | for (unsigned int i = 0; i < str.length(); i++) { 102 | strByte[i >> 2] |= (str[i]) << ((i % 4) * 8); //一个整数存储四个字节,i>>2表示i/4 一个unsigned int对应4个字节,保存4个字符信息 103 | } 104 | strByte[str.length() >> 2] |= 0x80 << (((str.length() % 4)) * 8); //尾部添加1 一个unsigned int保存4个字符信息,所以用128左移 105 | /* 106 | *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 107 | */ 108 | strByte[num * 16 - 2] = str.length() * 8; 109 | return strByte; 110 | } 111 | string changeHex(int a) { 112 | int b; 113 | string str1; 114 | string str = ""; 115 | for (int i = 0; i < 4; i++) { 116 | str1 = ""; 117 | b = ((a >> i * 8) % (1 << 8)) & 0xff; //逆序处理每个字节 118 | for (int j = 0; j < 2; j++) { 119 | str1.insert(0, 1, str16[b % 16]); 120 | b = b / 16; 121 | } 122 | str += str1; 123 | } 124 | return str; 125 | } 126 | public: 127 | string getMD5(string source) { 128 | atemp = A; //初始化 129 | btemp = B; 130 | ctemp = C; 131 | dtemp = D; 132 | unsigned int* strByte = add(source); 133 | for (unsigned int i = 0; i < strlength / 16; i++) { 134 | unsigned int num[16]; 135 | for (unsigned int j = 0; j < 16; j++) 136 | num[j] = strByte[i * 16 + j]; 137 | mainLoop(num); 138 | } 139 | return changeHex(atemp).append(changeHex(btemp)).append(changeHex(ctemp)).append(changeHex(dtemp)); 140 | } 141 | }; 142 | 143 | #endif // MD5_H_INCLUDED 144 | -------------------------------------------------------------------------------- /pack.h: -------------------------------------------------------------------------------- 1 | #ifndef PACK_H 2 | #define PACK_H 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | class Pack { 9 | public: 10 | /* 11 | * destination 打开失败返回1 12 | * files 中的文件打开失败返回2 13 | * 正常返回0 14 | */ 15 | static int pack(QStringList files, QString destination) { 16 | QFile tar(destination); 17 | bool success = tar.open(QFile::WriteOnly); 18 | if (!success) return 1; 19 | auto root = QFileInfo(files[0]).path(); 20 | for (auto& file : files) { 21 | if (QFileInfo(file).isFile()) { 22 | auto relativePath = QString(file).replace(root, ""); 23 | //qDebug() << relativePath.toStdString().c_str() << relativePath.length(); 24 | int pathLength = relativePath.length(); 25 | tar.write((const char*)&pathLength, sizeof (pathLength)); 26 | tar.write(relativePath.toStdString().c_str()); 27 | int fileLength = QFileInfo(file).size(); 28 | int dir = 0; 29 | tar.write((const char*)&dir, sizeof (dir)); 30 | tar.write((const char*)&fileLength, sizeof (fileLength)); 31 | QFile data(file); 32 | bool success = data.open(QFile::ReadOnly); 33 | if (!success) return 2; 34 | tar.write(data.readAll()); 35 | data.close(); 36 | } else { 37 | QDirIterator iter(file, QDirIterator::Subdirectories); 38 | while (iter.hasNext()) { 39 | iter.next(); 40 | QFileInfo info = iter.fileInfo(); 41 | if (info.fileName() == "..") continue; 42 | if (info.isDir()) { 43 | auto relativePath = QString(info.absoluteFilePath()).replace(root, ""); 44 | //qDebug() << relativePath.toStdString().c_str() << relativePath.length(); 45 | int pathLength = relativePath.length(); 46 | tar.write((const char*)&pathLength, sizeof (pathLength)); 47 | tar.write(relativePath.toStdString().c_str()); 48 | int fileLength = 0; 49 | int dir = 1; 50 | tar.write((const char*)&dir, sizeof (dir)); 51 | tar.write((const char*)&fileLength, sizeof (fileLength)); 52 | } else { 53 | auto relativePath = QString(info.absoluteFilePath()).replace(root, ""); 54 | //qDebug() << relativePath.toStdString().c_str() << relativePath.length(); 55 | int pathLength = relativePath.length(); 56 | tar.write((const char*)&pathLength, sizeof (pathLength)); 57 | tar.write(relativePath.toStdString().c_str()); 58 | int fileLength = info.size(); 59 | int dir = 0; 60 | tar.write((const char*)&dir, sizeof (dir)); 61 | tar.write((const char*)&fileLength, sizeof (fileLength)); 62 | QFile data(info.absoluteFilePath()); 63 | bool success = data.open(QFile::ReadOnly); 64 | if (!success) return 2; 65 | tar.write(data.readAll()); 66 | data.close(); 67 | } 68 | } 69 | } 70 | } 71 | tar.close(); 72 | return 0; 73 | } 74 | }; 75 | 76 | #endif // PACK_H 77 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | import os 3 | import json 4 | 5 | 6 | app = Flask(__name__) 7 | 8 | 9 | @app.route('/filelist', methods=['GET']) 10 | def filelist(): 11 | return json.dumps([x for x in os.listdir('.') if os.path.isfile(x) and x != "server.py"]) 12 | 13 | 14 | @app.route('/file/', methods=['PUT']) 15 | def add(filename): 16 | with open(filename, 'w') as f: 17 | f.write(str(request.get_data(), encoding="utf-8")) 18 | return 'success' 19 | return 'error' 20 | 21 | 22 | @app.route('/file/', methods=['DELETE']) 23 | def delete(filename): 24 | if os.path.exists(filename): 25 | os.remove(filename) 26 | return 'success' 27 | return 'error' 28 | 29 | 30 | @app.route('/file/', methods=['GET']) 31 | def get(filename): 32 | if os.path.exists(filename): 33 | with open(filename, 'r') as f: 34 | return f.read() 35 | return '' 36 | 37 | 38 | if __name__ == "__main__": 39 | app.run(host='0.0.0.0') 40 | -------------------------------------------------------------------------------- /task.cpp: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include 3 | 4 | TaskManager::TaskManager() { 5 | 6 | } 7 | 8 | void TaskManager::init() { 9 | if (QFileInfo("config.json").exists()) { 10 | QFile file("config.json"); 11 | file.open(QFile::ReadWrite); 12 | config = QJsonDocument::fromJson(file.readAll()); 13 | file.close(); 14 | QJsonArray tasks = config.array(); 15 | for (auto task : tasks) { 16 | auto information = task.toObject(); 17 | QList files; 18 | for (auto file : information["files"].toArray()) { 19 | files.append(file.toString()); 20 | } 21 | taskList.append(Task(files, 22 | information["backupFilename"].toString(), 23 | information["frequency"].toInt(), 24 | information["password"].toString(), 25 | information["cloud"].toBool(), 26 | QDateTime::fromString(information["nextTime"].toString()))); 27 | } 28 | } else { 29 | QFile file("config.json"); 30 | file.open(QFile::WriteOnly); 31 | file.write(""); 32 | file.close(); 33 | } 34 | } 35 | 36 | void TaskManager::writeJson() { 37 | QFile file("config.json"); 38 | file.open(QFile::WriteOnly); 39 | QJsonArray tasks; 40 | for (auto task : taskList) { 41 | QJsonObject information; 42 | QJsonArray files; 43 | for (auto file : task.files) { 44 | files.append(file); 45 | } 46 | information["files"] = files; 47 | information["backupFilename"] = task.backupFilename; 48 | information["frequency"] = task.frequency; 49 | information["password"] = task.password; 50 | information["cloud"] = task.cloud; 51 | information["nextTime"] = task.nextTime.toString(); 52 | tasks.append(information); 53 | } 54 | file.write(QJsonDocument(tasks).toJson()); 55 | file.close(); 56 | } 57 | 58 | void TaskManager::addTask(Task task) { 59 | taskList.append(task); 60 | writeJson(); 61 | } 62 | 63 | void TaskManager::removeTask(int index) { 64 | taskList.removeAt(index); 65 | writeJson(); 66 | } 67 | 68 | void TaskManager::clear() { 69 | taskList.clear(); 70 | QFile file("config.json"); 71 | file.open(QFile::WriteOnly); 72 | file.write(""); 73 | file.close(); 74 | } 75 | 76 | void TaskManager::updateTime(int index, QDateTime nextTime) { 77 | taskList[index].nextTime = nextTime; 78 | } 79 | 80 | const QList& TaskManager::getTaskList() { 81 | return taskList; 82 | } 83 | -------------------------------------------------------------------------------- /task.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_H 2 | #define TASK_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct Task { 12 | Task(QList _files, QString _backupFilename, int _frequency, QString _password, bool _cloud, QDateTime _nextTime) : 13 | files(_files), backupFilename(_backupFilename), frequency(_frequency), 14 | password(_password), cloud(_cloud), nextTime(_nextTime) {} 15 | 16 | QList files; 17 | QString backupFilename; 18 | int frequency; 19 | QString password; 20 | bool cloud; 21 | QDateTime nextTime; 22 | bool operator == (const Task& t) const { 23 | return files == t.files && backupFilename == t.backupFilename 24 | && frequency == t.frequency && password == t.password 25 | && cloud == t.cloud && nextTime == t.nextTime; 26 | } 27 | }; 28 | 29 | class TaskManager { 30 | public: 31 | TaskManager(); 32 | void init(); 33 | void addTask(Task task); 34 | void removeTask(int index); 35 | void clear(); 36 | void updateTime(int index, QDateTime nextTime); 37 | const QList& getTaskList(); 38 | void writeJson(); 39 | private: 40 | QList taskList; 41 | QJsonDocument config; 42 | }; 43 | 44 | #endif // TASK_H 45 | -------------------------------------------------------------------------------- /unpack.h: -------------------------------------------------------------------------------- 1 | #ifndef UNPACK_H 2 | #define UNPACK_H 3 | #include 4 | #include 5 | #include 6 | 7 | class Unpack { 8 | public: 9 | /* 10 | * tar打开失败返回1 11 | * 写目标文件失败返回2 12 | * 创建目录失败返回3 13 | * 正常返回0 14 | */ 15 | static int unpack(QString tarFilename, QString destination) { 16 | QFile tar(tarFilename); 17 | bool success = tar.open(QFile::ReadOnly); 18 | if (!success) return 1; 19 | int pathLength; 20 | int dir; 21 | int fileLength; 22 | QString relativePath; 23 | while (tar.read((char*)&pathLength, sizeof (pathLength))) { 24 | char* _relativePath = new char[pathLength + 1]; 25 | tar.read(_relativePath, pathLength); 26 | _relativePath[pathLength] = '\0'; 27 | relativePath = QString::fromStdString(std::string(_relativePath)); 28 | delete[] _relativePath; 29 | qDebug() << relativePath; 30 | tar.read((char*)&dir, sizeof (dir)); 31 | tar.read((char*)&fileLength, sizeof (fileLength)); 32 | qDebug() << fileLength; 33 | if (!dir) { 34 | QFile data(destination + "/" + relativePath); 35 | bool success = data.open(QFile::WriteOnly); 36 | if (!success) return 2; 37 | if (fileLength) { 38 | char* content = new char[fileLength]; 39 | tar.read(content, fileLength); 40 | data.write(content, fileLength); 41 | delete[] content; 42 | } else { 43 | data.write(""); 44 | } 45 | data.close(); 46 | } else { 47 | QDir dir; 48 | if (QFileInfo(destination + "/" + relativePath).exists()) continue; 49 | bool success = dir.mkdir(destination + "/" + relativePath); 50 | if (!success) return 3; 51 | } 52 | } 53 | return 0; 54 | } 55 | }; 56 | 57 | #endif // UNPACK_H 58 | -------------------------------------------------------------------------------- /widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) { 12 | ui->setupUi(this); 13 | this->setFixedSize({768, 768}); 14 | // 使 localGroupBox 可用而 cloudGroupBox不可用 15 | on_localGroupBox_clicked(true); 16 | on_cloudGroupBox_clicked(true); 17 | on_localGroupBox_clicked(true); 18 | taskManager.init(); 19 | for (const auto& task : taskManager.getTaskList()) { 20 | QTreeWidgetItem* taskItem = new QTreeWidgetItem; 21 | taskItem->setText(0, QFileInfo(task.backupFilename).fileName() + ".bak"); 22 | taskItem->setText(1, task.nextTime.toString()); 23 | taskItem->setText(2, task.frequency == 1 ? "每天" : "每周"); 24 | taskItem->setText(3, task.password.trimmed() != "" ? "是" : "否"); 25 | taskItem->setText(4, task.cloud ? "是" : "否"); 26 | taskItem->setText(5, task.backupFilename + ".bak"); 27 | ui->taskList->addTopLevelItem(taskItem); 28 | } 29 | if (!ui->taskList->currentItem() && ui->taskList->topLevelItemCount()) { 30 | ui->taskList->setCurrentItem(ui->taskList->topLevelItem(0)); 31 | } 32 | connect(&timer, &QTimer::timeout, this, [ = ]() { 33 | if (taskManager.getTaskList().count()) { 34 | for (auto& task : taskManager.getTaskList()) { 35 | // if (task.nextTime.toString() == QDateTime::currentDateTime().toString()) { 36 | if (task.nextTime <= QDateTime::currentDateTime()) { 37 | // 执行 38 | /*QProcess tar;*/ 39 | QStringList files; 40 | for (auto i : task.files) { 41 | files.append(i); 42 | } 43 | /*auto rootDirectory = QFileInfo(task.files[0]).path(); 44 | auto currentDirectory = QDir::current(); 45 | QDir::setCurrent(rootDirectory); 46 | tar.start(currentDirectory.path() + "/tar.exe", QStringList() << "-cvf" << QFileInfo(task.backupFilename).fileName() + ".tar" << files); 47 | tar.waitForStarted(-1); 48 | tar.waitForFinished(-1);*/ 49 | int errorCode = Pack::pack(files, QFileInfo(task.backupFilename).fileName() + ".tar"); 50 | if (errorCode) { 51 | QStringList message = {"正常执行", "目标文件打开失败", "打开源文件失败"}; 52 | QMessageBox::information(this, "提示", message[errorCode], 53 | QMessageBox::Yes, QMessageBox::Yes); 54 | return; 55 | } 56 | Compressor compressor; 57 | errorCode = compressor.compress(QFileInfo(task.backupFilename).fileName().toStdString() + ".tar", 58 | QFileInfo(task.backupFilename).path().toStdString() + "/", 59 | task.password.toStdString()); 60 | if (errorCode) { 61 | QStringList message = {"正常执行", "源文件扩展名不是bak", "打开源文件失败", "打开目标文件失败"}; 62 | QMessageBox::information(this, "提示", message[errorCode], 63 | QMessageBox::Yes, QMessageBox::Yes); 64 | qDebug() << errorCode; 65 | return; 66 | } 67 | QFile tarFile(QFileInfo(task.backupFilename).fileName() + ".tar"); 68 | tarFile.remove(); 69 | //QDir::setCurrent(currentDirectory.path()); 70 | // 云上传 71 | if (task.cloud) { 72 | QNetworkAccessManager* manager = new QNetworkAccessManager(this); 73 | QFile uploadFile(task.backupFilename + ".bak"); 74 | QNetworkRequest request(QUrl(api + "file/" + QFileInfo(task.backupFilename).fileName() + ".bak")); 75 | request.setRawHeader("Content-Type", "application/bak"); 76 | uploadFile.open(QFile::ReadOnly); 77 | QNetworkReply* reply = manager->put(request, uploadFile.readAll().toBase64()); 78 | uploadFile.close(); 79 | connect(manager, &QNetworkAccessManager::finished, this, [ = ](QNetworkReply * _reply) { 80 | if (_reply->readAll().toStdString() == "success") { 81 | QMessageBox::information(this, "提示", "上传成功。", 82 | QMessageBox::Yes, QMessageBox::Yes); 83 | } 84 | }); 85 | connect(reply, &QNetworkReply::uploadProgress, this, [](qint64 bytesReceived, qint64 bytesTotal) { 86 | qDebug() << bytesReceived << "/" << bytesTotal; 87 | }); 88 | connect(reply, 89 | QOverload::of(&QNetworkReply::error), 90 | this, 91 | [ = ](QNetworkReply::NetworkError code) { 92 | if (code) { 93 | QMessageBox::information(this, "提示", "上传失败。", 94 | QMessageBox::Yes, QMessageBox::Yes); 95 | } 96 | }); 97 | } else { 98 | QMessageBox::information(this, "提示", "备份成功。", 99 | QMessageBox::Yes, QMessageBox::Yes); 100 | } 101 | // 更新下一次备份时间 102 | int index = taskManager.getTaskList().indexOf(task); 103 | while (task.nextTime <= QDateTime::currentDateTime()) { 104 | taskManager.updateTime(index, task.nextTime.addDays(task.frequency == 1 ? 1 : 7)); 105 | } 106 | ui->taskList->topLevelItem(index)->setText(1, task.nextTime.toString()); 107 | taskManager.writeJson(); 108 | } 109 | } 110 | } 111 | }); 112 | timer.start(1000); 113 | } 114 | 115 | Widget::~Widget() { 116 | on_clearFileButton_clicked(); 117 | delete ui; 118 | } 119 | 120 | 121 | void Widget::on_passwordCheckBox_stateChanged(int arg1) { 122 | ui->passwordLineEdit->setEnabled(ui->passwordCheckBox->checkState()); 123 | } 124 | 125 | void Widget::on_browseButton_clicked() { 126 | QString directory = QFileDialog::getExistingDirectory( 127 | this, 128 | tr("备份到"), 129 | "/home", 130 | QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); 131 | if (directory != "") { 132 | ui->backupFileDirectoryLineEdit->setText(directory); 133 | } 134 | } 135 | 136 | void Widget::on_addFileButton_clicked() { 137 | QStringList files = QFileDialog::getOpenFileNames( 138 | this, 139 | "选择一个或多个文件", 140 | "/home", 141 | "所有文件 (*.*)"); 142 | for (const auto& file : files) { 143 | // 去重 144 | bool duplication = false; 145 | for (int i = 0; i < ui->backupFileList->topLevelItemCount(); ++i) { 146 | if (ui->backupFileList->topLevelItem(i)->text(1) == file) { 147 | duplication = true; 148 | break; 149 | } 150 | } 151 | if (duplication) continue; 152 | 153 | QTreeWidgetItem* fileItem = new QTreeWidgetItem; 154 | fileItem->setText(0, QFileInfo(file).fileName()); 155 | fileItem->setText(1, file); 156 | ui->backupFileList->addTopLevelItem(fileItem); 157 | } 158 | if (!ui->backupFileList->currentItem() && ui->backupFileList->topLevelItemCount()) { 159 | ui->backupFileList->setCurrentItem(ui->backupFileList->topLevelItem(0)); 160 | } 161 | } 162 | 163 | void Widget::on_deleteFileButton_clicked() { 164 | if (ui->backupFileList->currentItem()) { 165 | delete ui->backupFileList->currentItem(); 166 | } 167 | } 168 | 169 | void Widget::on_clearFileButton_clicked() { 170 | while (ui->backupFileList->currentItem()) { 171 | delete ui->backupFileList->currentItem(); 172 | } 173 | } 174 | 175 | void Widget::on_addDirectoryButton_clicked() { 176 | QString directory = QFileDialog::getExistingDirectory( 177 | this, 178 | tr("选择一个文件夹"), 179 | "/home", 180 | QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); 181 | if (directory != "") { 182 | // 去重 183 | for (int i = 0; i < ui->backupFileList->topLevelItemCount(); ++i) { 184 | if (ui->backupFileList->topLevelItem(i)->text(1) == directory) return; 185 | } 186 | QTreeWidgetItem* fileItem = new QTreeWidgetItem; 187 | fileItem->setText(0, QFileInfo(directory).fileName()); 188 | fileItem->setText(1, directory); 189 | ui->backupFileList->addTopLevelItem(fileItem); 190 | } 191 | if (!ui->backupFileList->currentItem() && ui->backupFileList->topLevelItemCount()) { 192 | ui->backupFileList->setCurrentItem(ui->backupFileList->topLevelItem(0)); 193 | } 194 | } 195 | 196 | void Widget::on_startBackupButton_clicked() { 197 | if (!ui->backupFileList->topLevelItemCount()) { 198 | QMessageBox::information(this, "提示", "请添加需要备份的文件。", 199 | QMessageBox::Yes, QMessageBox::Yes); 200 | return; 201 | } 202 | if (ui->backupFilenameLineEdit->text().trimmed() == "") { 203 | QMessageBox::information(this, "提示", "请输入备份后的文件名。", 204 | QMessageBox::Yes, QMessageBox::Yes); 205 | return; 206 | } 207 | if (ui->backupFileDirectoryLineEdit->text().trimmed() == "") { 208 | QMessageBox::information(this, "提示", "请输入备份保存到的目录。", 209 | QMessageBox::Yes, QMessageBox::Yes); 210 | return; 211 | } 212 | std::regex filenameExpress("[\\/:*?\"<>|]"); 213 | if (std::regex_search(ui->backupFilenameLineEdit->text().toStdString(), filenameExpress)) { 214 | QMessageBox::information(this, "提示", "请输入合法的备份文件名。", 215 | QMessageBox::Yes, QMessageBox::Yes); 216 | return; 217 | } 218 | if (QFileInfo(ui->backupFileDirectoryLineEdit->text() + "\\" + ui->backupFilenameLineEdit->text()).exists()) { 219 | if (QMessageBox::Yes != QMessageBox::question(this, "警告", "文件已存在,确认覆盖?", QMessageBox::Yes | QMessageBox::No)) { 220 | return; 221 | } 222 | } 223 | if (ui->passwordCheckBox->isChecked() && ui->passwordLineEdit->text().trimmed() == "") { 224 | QMessageBox::information(this, "提示", "请输入密码。", 225 | QMessageBox::Yes, QMessageBox::Yes); 226 | return; 227 | } 228 | // 都与第一个文件的目录对比 229 | auto rootDirectory = QFileInfo(ui->backupFileList->topLevelItem(0)->text(1)).path(); 230 | for (int i = 1; i < ui->backupFileList->topLevelItemCount(); ++i) { 231 | if (QFileInfo(ui->backupFileList->topLevelItem(i)->text(1)).path() != rootDirectory) { 232 | QMessageBox::information(this, "提示", "选择的文件或文件夹应位于同一目录下。", 233 | QMessageBox::Yes, QMessageBox::Yes); 234 | return; 235 | } 236 | } 237 | if (!ui->noneRadioButton->isChecked()) { 238 | QTreeWidgetItem* taskItem = new QTreeWidgetItem; 239 | taskItem->setText(0, ui->backupFilenameLineEdit->text() + ".bak"); 240 | auto nextTime = QDateTime::currentDateTime().addDays(ui->everydayRadioButton->isChecked() ? 1 : 7); 241 | taskItem->setText(1, nextTime.toString()); 242 | taskItem->setText(2, ui->everydayRadioButton->isChecked() ? "每天" : "每周"); 243 | taskItem->setText(3, ui->passwordCheckBox->isChecked() ? "是" : "否"); 244 | taskItem->setText(4, ui->cloudCheckBox->isChecked() ? "是" : "否"); 245 | taskItem->setText(5, ui->backupFileDirectoryLineEdit->text() + "/" + ui->backupFilenameLineEdit->text() + ".bak"); 246 | ui->taskList->addTopLevelItem(taskItem); 247 | QList files; 248 | for (int i = 0; i < ui->backupFileList->topLevelItemCount(); ++i) { 249 | files.append(ui->backupFileList->topLevelItem(i)->text(1)); 250 | } 251 | taskManager.addTask(Task(files, 252 | ui->backupFileDirectoryLineEdit->text() + "/" + ui->backupFilenameLineEdit->text(), 253 | ui->everydayRadioButton->isChecked() ? 1 : 2, 254 | ui->passwordLineEdit->text(), 255 | ui->cloudCheckBox->isChecked(), 256 | nextTime)); 257 | if (!ui->taskList->currentItem() && ui->taskList->topLevelItemCount()) { 258 | ui->taskList->setCurrentItem(ui->taskList->topLevelItem(0)); 259 | } 260 | } 261 | // 调用打包压缩加密 262 | /*QProcess tar;*/ 263 | QStringList files; 264 | for (int i = 0; i < ui->backupFileList->topLevelItemCount(); ++i) { 265 | files.append(ui->backupFileList->topLevelItem(i)->text(1)); 266 | } 267 | /*auto currentDirectory = QDir::current(); 268 | QDir::setCurrent(rootDirectory); 269 | tar.start(currentDirectory.path() + "/tar.exe", QStringList() << "-cvf" << ui->backupFilenameLineEdit->text() + ".tar" << files); 270 | tar.waitForStarted(-1); 271 | tar.waitForFinished(-1);*/ 272 | int errorCode = Pack::pack(files, ui->backupFilenameLineEdit->text() + ".tar"); 273 | if (errorCode) { 274 | QStringList message = {"正常执行", "目标文件打开失败", "打开源文件失败"}; 275 | QMessageBox::information(this, "提示", message[errorCode], 276 | QMessageBox::Yes, QMessageBox::Yes); 277 | return; 278 | } 279 | Compressor compressor; 280 | errorCode = compressor.compress(ui->backupFilenameLineEdit->text().toStdString() + ".tar", 281 | ui->backupFileDirectoryLineEdit->text().toStdString() + "/", 282 | ui->passwordCheckBox->isChecked() ? ui->passwordLineEdit->text().toStdString() : ""); 283 | if (errorCode) { 284 | QStringList message = {"正常执行", "源文件扩展名不是bak", "打开源文件失败", "打开目标文件失败"}; 285 | QMessageBox::information(this, "提示", message[errorCode], 286 | QMessageBox::Yes, QMessageBox::Yes); 287 | qDebug() << errorCode; 288 | return; 289 | } 290 | QFile tarFile(ui->backupFilenameLineEdit->text() + ".tar"); 291 | tarFile.remove(); 292 | //QDir::setCurrent(currentDirectory.path()); 293 | // 云上传 294 | if (ui->cloudCheckBox->isChecked()) { 295 | QNetworkAccessManager* manager = new QNetworkAccessManager(this); 296 | QFile uploadFile(ui->backupFileDirectoryLineEdit->text() + "/" + ui->backupFilenameLineEdit->text() + ".bak"); 297 | QNetworkRequest request(QUrl(api + "file/" + ui->backupFilenameLineEdit->text() + ".bak")); 298 | request.setRawHeader("Content-Type", "application/bak"); 299 | uploadFile.open(QFile::ReadOnly); 300 | QNetworkReply* reply = manager->put(request, uploadFile.readAll().toBase64()); 301 | uploadFile.close(); 302 | connect(manager, &QNetworkAccessManager::finished, this, [ = ](QNetworkReply * _reply) { 303 | if (_reply->readAll().toStdString() == "success") { 304 | QMessageBox::information(this, "提示", "上传成功。", 305 | QMessageBox::Yes, QMessageBox::Yes); 306 | } 307 | }); 308 | connect(reply, &QNetworkReply::uploadProgress, this, [](qint64 bytesReceived, qint64 bytesTotal) { 309 | qDebug() << bytesReceived << "/" << bytesTotal; 310 | }); 311 | connect(reply, 312 | QOverload::of(&QNetworkReply::error), 313 | this, 314 | [ = ](QNetworkReply::NetworkError code) { 315 | if (code) { 316 | QMessageBox::information(this, "提示", "上传失败。", 317 | QMessageBox::Yes, QMessageBox::Yes); 318 | } 319 | }); 320 | } else { 321 | QMessageBox::information(this, "提示", "备份成功。", 322 | QMessageBox::Yes, QMessageBox::Yes); 323 | } 324 | } 325 | 326 | void Widget::on_localGroupBox_clicked(bool checked) { 327 | ui->localGroupBox->setChecked(true); 328 | ui->cloudGroupBox->setChecked(false); 329 | } 330 | 331 | void Widget::on_cloudGroupBox_clicked(bool checked) { 332 | ui->localGroupBox->setChecked(false); 333 | ui->cloudGroupBox->setChecked(true); 334 | ui->cloudFileRestoreLineEdit->setEnabled(false); 335 | ui->cloudFileList->clear(); 336 | QNetworkAccessManager* manager = new QNetworkAccessManager(this); 337 | QNetworkRequest request(QUrl(api + "filelist")); 338 | QNetworkReply* reply = manager->get(request); 339 | connect(manager, &QNetworkAccessManager::finished, this, [ = ](QNetworkReply * _reply) { 340 | auto filelist = QJsonDocument::fromJson(_reply->readAll()).array(); 341 | for (auto file : filelist) { 342 | QTreeWidgetItem* item = new QTreeWidgetItem; 343 | item->setText(0, file.toString()); 344 | ui->cloudFileList->addTopLevelItem(item); 345 | } 346 | }); 347 | connect(reply, 348 | QOverload::of(&QNetworkReply::error), 349 | this, 350 | [ = ](QNetworkReply::NetworkError code) { 351 | if (code) { 352 | QMessageBox::information(this, "提示", "拉取云端列表失败。", 353 | QMessageBox::Yes, QMessageBox::Yes); 354 | } 355 | }); 356 | } 357 | 358 | void Widget::on_browseLocalFile_clicked() { 359 | auto file = QFileDialog::getOpenFileName( 360 | this, 361 | "选择一个备份文件", 362 | "", 363 | "所有文件 (*.*)"); 364 | if (file != "") { 365 | ui->localFileRestoreLineEdit->setText(file); 366 | } 367 | } 368 | 369 | void Widget::on_cloudFileList_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { 370 | if (current) 371 | ui->cloudFileRestoreLineEdit->setText(current->text(0)); 372 | } 373 | 374 | void Widget::on_browseRestoreDirectoryButton_clicked() { 375 | QString directory = QFileDialog::getExistingDirectory( 376 | this, 377 | tr("恢复到"), 378 | "/home", 379 | QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); 380 | if (directory != "") { 381 | ui->backupFileRestoreDirectoryLineEdit->setText(directory); 382 | } 383 | } 384 | 385 | void Widget::on_passwordCheckBox_2_stateChanged(int arg1) { 386 | ui->passwordLineEdit_2->setEnabled(ui->passwordCheckBox_2->checkState()); 387 | } 388 | 389 | void Widget::on_deleteTaskButton_clicked() { 390 | if (ui->taskList->currentItem()) { 391 | taskManager.removeTask(ui->taskList->indexOfTopLevelItem(ui->taskList->currentItem())); 392 | delete ui->taskList->currentItem(); 393 | if (!ui->taskList->currentItem() && ui->taskList->topLevelItemCount()) { 394 | ui->taskList->setCurrentItem(ui->taskList->topLevelItem(0)); 395 | } 396 | } 397 | } 398 | 399 | void Widget::on_clearTaskButton_clicked() { 400 | ui->taskList->clear(); 401 | taskManager.clear(); 402 | } 403 | 404 | void Widget::on_taskList_customContextMenuRequested(const QPoint& pos) { 405 | QTreeWidgetItem* currentItem = ui->taskList->itemAt(pos); 406 | if (currentItem) { 407 | int index = ui->taskList->indexOfTopLevelItem(currentItem); 408 | popMenu = new QMenu(this); 409 | openFolder = popMenu->addAction("打开备份文件所在目录"); 410 | check = popMenu->addAction("与原文件校验"); 411 | connect(openFolder, &QAction::triggered, this, [ = ]() { 412 | QDesktopServices::openUrl(QUrl("file:///" + QFileInfo(currentItem->text(5)).path(), QUrl::TolerantMode)); 413 | }); 414 | connect(check, &QAction::triggered, this, [ = ]() { 415 | Decompressor decompressor; 416 | QDir dir; 417 | dir.mkdir("./TEMP"); 418 | QFileInfo TEMP("./TEMP"); 419 | int errorCode = decompressor.decompress(currentItem->text(5).toStdString(), 420 | TEMP.absoluteFilePath().toStdString() + "/", 421 | taskManager.getTaskList()[index].password.toStdString()); 422 | if (errorCode) { 423 | QStringList message = {"正常执行", "源文件扩展名不是bak", "打开源文件失败", "打开目标文件失败", "文件过短,频率表不完整", "文件结尾不完整", "密码错误", "解码错误"}; 424 | QMessageBox::information(this, "提示", message[errorCode], 425 | QMessageBox::Yes, QMessageBox::Yes); 426 | dir.rmdir("./TEMP"); 427 | return; 428 | } 429 | /*QProcess tar; 430 | auto currentDirectory = QDir::current(); 431 | QDir::setCurrent("./TEMP");*/ 432 | QString tempFilename = "./TEMP/" + QFileInfo(currentItem->text(5)).fileName(); 433 | tempFilename = tempFilename.left(tempFilename.length() - 3) + "tar"; 434 | qDebug() << tempFilename; 435 | errorCode = Unpack::unpack(QFileInfo(tempFilename).absoluteFilePath(), "./TEMP"); 436 | if (errorCode) { 437 | QStringList message = {"正常执行", "打开源文件失败", "目标文件打开失败", "创建目录失败"}; 438 | QMessageBox::information(this, "提示", message[errorCode], 439 | QMessageBox::Yes, QMessageBox::Yes); 440 | return; 441 | } 442 | /*tar.start(currentDirectory.path() + "/tar.exe", QStringList() << "-xvf" << tempFilename); 443 | tar.waitForStarted(-1); 444 | tar.waitForFinished(-1);*/ 445 | QFile tarFile(tempFilename); 446 | tarFile.remove(); 447 | /*QDir::setCurrent(currentDirectory.path());*/ 448 | qDebug() << "!!!"; 449 | auto difference = Check::check(taskManager.getTaskList()[index].files, "./TEMP"); 450 | if (difference.empty()) { 451 | QMessageBox::information(this, "提示", "备份无差异", 452 | QMessageBox::Yes, QMessageBox::Yes); 453 | } else { 454 | QMessageBox::information(this, "提示", "备份有差异", 455 | QMessageBox::Yes, QMessageBox::Yes); 456 | QFile diff("diff.txt"); 457 | diff.open(QFile::WriteOnly); 458 | QStringList types = {"删除", "修改", "增加"}; 459 | for (auto& d : difference) { 460 | auto filename = d.first; 461 | auto type = d.second; 462 | diff.write(types[type].toStdString().c_str()); 463 | diff.write(" "); 464 | diff.write(filename.toStdString().c_str()); 465 | diff.write("\n"); 466 | } 467 | diff.close(); 468 | QProcess notepad; 469 | notepad.startDetached("notepad diff.txt"); 470 | } 471 | dir.rmdir("./TEMP"); 472 | }); 473 | popMenu->exec(QCursor::pos()); 474 | } 475 | } 476 | 477 | void Widget::on_startRestoreButton_clicked() { 478 | if (ui->localGroupBox->isChecked() && ui->localFileRestoreLineEdit->text().trimmed() == "") { 479 | QMessageBox::information(this, "提示", "请选择要恢复的备份文件。", 480 | QMessageBox::Yes, QMessageBox::Yes); 481 | return; 482 | } 483 | if (ui->cloudGroupBox->isChecked() && ui->cloudFileRestoreLineEdit->text().trimmed() == "") { 484 | QMessageBox::information(this, "提示", "请选择要恢复的备份文件。", 485 | QMessageBox::Yes, QMessageBox::Yes); 486 | return; 487 | } 488 | if (ui->backupFileRestoreDirectoryLineEdit->text().trimmed() == "") { 489 | QMessageBox::information(this, "提示", "请选择要恢复到的目录。", 490 | QMessageBox::Yes, QMessageBox::Yes); 491 | return; 492 | } 493 | if (ui->passwordCheckBox_2->isChecked() && ui->passwordLineEdit_2->text().trimmed() == "") { 494 | QMessageBox::information(this, "提示", "请输入密码。", 495 | QMessageBox::Yes, QMessageBox::Yes); 496 | return; 497 | } 498 | if (ui->cloudGroupBox->isChecked()) { 499 | QNetworkAccessManager* manager = new QNetworkAccessManager(this); 500 | QNetworkRequest request(QUrl(api + "file/" + ui->cloudFileRestoreLineEdit->text())); 501 | QNetworkReply* reply = manager->get(request); 502 | connect(manager, &QNetworkAccessManager::finished, this, [ = ](QNetworkReply * _reply) { 503 | auto file = QByteArray::fromBase64(_reply->readAll()); 504 | qDebug() << file.size(); 505 | QFile cloudFile("temp_" + ui->cloudFileRestoreLineEdit->text()); 506 | cloudFile.open(QFile::WriteOnly); 507 | cloudFile.write(file); 508 | cloudFile.close(); 509 | Decompressor decompressor; 510 | int errorCode = decompressor.decompress("temp_" + ui->cloudFileRestoreLineEdit->text().toStdString(), 511 | ui->backupFileRestoreDirectoryLineEdit->text().toStdString() + "/", 512 | ui->passwordCheckBox_2->isChecked() ? ui->passwordLineEdit_2->text().toStdString() : ""); 513 | if (errorCode) { 514 | QStringList message = {"正常执行", "源文件扩展名不是bak", "打开源文件失败", "打开目标文件失败", "文件过短,频率表不完整", "文件结尾不完整", "密码错误", "解码错误"}; 515 | QMessageBox::information(this, "提示", message[errorCode], 516 | QMessageBox::Yes, QMessageBox::Yes); 517 | return; 518 | } 519 | cloudFile.remove(); 520 | /*QProcess tar; 521 | auto currentDirectory = QDir::current();*/ 522 | //QDir::setCurrent(ui->backupFileRestoreDirectoryLineEdit->text()); 523 | QString tempFilename = ui->backupFileRestoreDirectoryLineEdit->text() + "/" + "temp_" + ui->cloudFileRestoreLineEdit->text().left(ui->cloudFileRestoreLineEdit->text().length() - 3) + "tar"; 524 | /*tar.start(currentDirectory.path() + "/tar.exe", QStringList() << "-xvf" << tempFilename); 525 | tar.waitForStarted(-1); 526 | tar.waitForFinished(-1);*/ 527 | errorCode = Unpack::unpack(tempFilename, ui->backupFileRestoreDirectoryLineEdit->text()); 528 | if (errorCode) { 529 | QStringList message = {"正常执行", "打开源文件失败", "目标文件打开失败", "创建目录失败"}; 530 | QMessageBox::information(this, "提示", message[errorCode], 531 | QMessageBox::Yes, QMessageBox::Yes); 532 | return; 533 | } 534 | QFile tarFile(tempFilename); 535 | tarFile.remove(); 536 | /*QDir::setCurrent(currentDirectory.path());*/ 537 | QMessageBox::information(this, "提示", "恢复完成。", 538 | QMessageBox::Yes, QMessageBox::Yes); 539 | QDesktopServices::openUrl(QUrl("file:///" + ui->backupFileRestoreDirectoryLineEdit->text(), QUrl::TolerantMode)); 540 | }); 541 | connect(reply, 542 | QOverload::of(&QNetworkReply::error), 543 | this, 544 | [ = ](QNetworkReply::NetworkError code) { 545 | if (code) { 546 | QMessageBox::information(this, "提示", "云备份下载失败。", 547 | QMessageBox::Yes, QMessageBox::Yes); 548 | } 549 | }); 550 | } else { 551 | Decompressor decompressor; 552 | int errorCode = decompressor.decompress(ui->localFileRestoreLineEdit->text().toStdString(), 553 | ui->backupFileRestoreDirectoryLineEdit->text().toStdString() + "/", 554 | ui->passwordCheckBox_2->isChecked() ? ui->passwordLineEdit_2->text().toStdString() : ""); 555 | if (errorCode) { 556 | QStringList message = {"正常执行", "源文件扩展名不是bak", "打开源文件失败", "打开目标文件失败", "文件过短,频率表不完整", "文件结尾不完整", "密码错误", "解码错误"}; 557 | QMessageBox::information(this, "提示", message[errorCode], 558 | QMessageBox::Yes, QMessageBox::Yes); 559 | return; 560 | } 561 | /*QProcess tar; 562 | auto currentDirectory = QDir::current(); 563 | QDir::setCurrent(ui->backupFileRestoreDirectoryLineEdit->text());*/ 564 | QString tempFilename = ui->backupFileRestoreDirectoryLineEdit->text() + "/" + QFileInfo(ui->localFileRestoreLineEdit->text()).fileName(); 565 | tempFilename = tempFilename.left(tempFilename.length() - 3) + "tar"; 566 | /*tar.start(currentDirectory.path() + "/tar.exe", QStringList() << "-xvf" << tempFilename); 567 | tar.waitForStarted(-1); 568 | tar.waitForFinished(-1);*/ 569 | errorCode = Unpack::unpack(tempFilename, ui->backupFileRestoreDirectoryLineEdit->text()); 570 | if (errorCode) { 571 | QStringList message = {"正常执行", "打开源文件失败", "目标文件打开失败", "创建目录失败"}; 572 | QMessageBox::information(this, "提示", message[errorCode], 573 | QMessageBox::Yes, QMessageBox::Yes); 574 | return; 575 | } 576 | QFile tarFile(tempFilename); 577 | tarFile.remove(); 578 | /*QDir::setCurrent(currentDirectory.path());*/ 579 | QMessageBox::information(this, "提示", "恢复完成。", 580 | QMessageBox::Yes, QMessageBox::Yes); 581 | QDesktopServices::openUrl(QUrl("file:///" + ui->backupFileRestoreDirectoryLineEdit->text(), QUrl::TolerantMode)); 582 | } 583 | } 584 | 585 | void Widget::on_cloudFileList_customContextMenuRequested(const QPoint& pos) { 586 | QTreeWidgetItem* currentItem = ui->cloudFileList->itemAt(pos); 587 | if (currentItem) { 588 | popMenu = new QMenu(this); 589 | removeCloudBackupFile = popMenu->addAction("删除该云备份"); 590 | connect(removeCloudBackupFile, &QAction::triggered, this, [ = ]() { 591 | if (QMessageBox::Yes != QMessageBox::question(this, "警告", "确认删除?", QMessageBox::Yes | QMessageBox::No)) { 592 | return; 593 | } 594 | QNetworkAccessManager* manager = new QNetworkAccessManager(this); 595 | QNetworkRequest request(QUrl(api + "file/" + currentItem->text(0))); 596 | QNetworkReply* reply = manager->deleteResource(request); 597 | connect(manager, &QNetworkAccessManager::finished, this, [ = ](QNetworkReply * _reply) { 598 | if (_reply->readAll().toStdString() == "success") { 599 | QMessageBox::information(this, "提示", "删除成功。", 600 | QMessageBox::Yes, QMessageBox::Yes); 601 | if (ui->cloudFileList->topLevelItemCount()) { 602 | ui->cloudFileList->setCurrentItem(ui->cloudFileList->topLevelItem(0)); 603 | } 604 | } 605 | }); 606 | connect(reply, 607 | QOverload::of(&QNetworkReply::error), 608 | this, 609 | [ = ](QNetworkReply::NetworkError code) { 610 | if (code) { 611 | QMessageBox::information(this, "提示", "删除失败。", 612 | QMessageBox::Yes, QMessageBox::Yes); 613 | } 614 | }); 615 | }); 616 | popMenu->exec(QCursor::pos()); 617 | } 618 | } 619 | -------------------------------------------------------------------------------- /widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "task.h" 13 | #include "compressor.h" 14 | #include "decompressor.h" 15 | #include "check.h" 16 | #include "pack.h" 17 | #include "unpack.h" 18 | 19 | const QString api = "http://119.28.65.16:5000/"; 20 | 21 | namespace Ui { 22 | class Widget; 23 | } 24 | 25 | class Widget : public QWidget { 26 | Q_OBJECT 27 | 28 | public: 29 | explicit Widget(QWidget* parent = nullptr); 30 | ~Widget(); 31 | 32 | private slots: 33 | void on_passwordCheckBox_stateChanged(int arg1); 34 | 35 | void on_browseButton_clicked(); 36 | 37 | void on_addFileButton_clicked(); 38 | 39 | void on_deleteFileButton_clicked(); 40 | 41 | void on_clearFileButton_clicked(); 42 | 43 | void on_addDirectoryButton_clicked(); 44 | 45 | void on_startBackupButton_clicked(); 46 | 47 | void on_localGroupBox_clicked(bool checked); 48 | 49 | void on_cloudGroupBox_clicked(bool checked); 50 | 51 | void on_browseLocalFile_clicked(); 52 | 53 | void on_cloudFileList_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); 54 | 55 | void on_browseRestoreDirectoryButton_clicked(); 56 | 57 | void on_passwordCheckBox_2_stateChanged(int arg1); 58 | 59 | void on_deleteTaskButton_clicked(); 60 | 61 | void on_clearTaskButton_clicked(); 62 | 63 | void on_taskList_customContextMenuRequested(const QPoint& pos); 64 | 65 | void on_startRestoreButton_clicked(); 66 | 67 | void on_cloudFileList_customContextMenuRequested(const QPoint& pos); 68 | 69 | private: 70 | Ui::Widget* ui; 71 | QMenu* popMenu; 72 | QAction* openFolder; 73 | QAction* removeCloudBackupFile; 74 | QAction* check; 75 | TaskManager taskManager; 76 | QTimer timer; 77 | }; 78 | 79 | #endif // WIDGET_H 80 | -------------------------------------------------------------------------------- /widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 768 10 | 768 11 | 12 | 13 | 14 | 文件备份系统 15 | 16 | 17 | 18 | :/image/image/icon.png:/image/image/icon.png 19 | 20 | 21 | 22 | 23 | 0 24 | 0 25 | 768 26 | 768 27 | 28 | 29 | 30 | QTabWidget::Triangular 31 | 32 | 33 | 0 34 | 35 | 36 | 37 | 24 38 | 24 39 | 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | :/image/image/backup.png:/image/image/backup.png 48 | 49 | 50 | 备份 51 | 52 | 53 | 54 | 55 | 10 56 | 5 57 | 111 58 | 21 59 | 60 | 61 | 62 | false 63 | 64 | 65 | 待备份文件列表 66 | 67 | 68 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 69 | 70 | 71 | 72 | 73 | 74 | 10 75 | 30 76 | 746 77 | 481 78 | 79 | 80 | 81 | true 82 | 83 | 84 | QAbstractItemView::NoDragDrop 85 | 86 | 87 | false 88 | 89 | 90 | false 91 | 92 | 93 | 94 | 文件名 95 | 96 | 97 | 98 | 99 | 完整路径 100 | 101 | 102 | 103 | 104 | 105 | 106 | 10 107 | 550 108 | 72 109 | 15 110 | 111 | 112 | 113 | 选项 114 | 115 | 116 | 117 | 118 | 119 | 369 120 | 521 121 | 101 122 | 26 123 | 124 | 125 | 126 | 添加文件 127 | 128 | 129 | 130 | :/image/image/add.png:/image/image/add.png 131 | 132 | 133 | Qt::ToolButtonTextBesideIcon 134 | 135 | 136 | 137 | 138 | 139 | 597 140 | 521 141 | 71 142 | 26 143 | 144 | 145 | 146 | 删除 147 | 148 | 149 | 150 | :/image/image/delete.png:/image/image/delete.png 151 | 152 | 153 | Qt::ToolButtonTextBesideIcon 154 | 155 | 156 | 157 | 158 | 159 | 681 160 | 521 161 | 71 162 | 26 163 | 164 | 165 | 166 | 清空 167 | 168 | 169 | 170 | :/image/image/clear.png:/image/image/clear.png 171 | 172 | 173 | Qt::ToolButtonTextBesideIcon 174 | 175 | 176 | 177 | 178 | 179 | 50 180 | 552 181 | 707 182 | 16 183 | 184 | 185 | 186 | Qt::Horizontal 187 | 188 | 189 | 190 | 191 | 192 | 749 193 | 560 194 | 16 195 | 171 196 | 197 | 198 | 199 | Qt::Vertical 200 | 201 | 202 | 203 | 204 | 205 | 14 206 | 722 207 | 743 208 | 20 209 | 210 | 211 | 212 | Qt::Horizontal 213 | 214 | 215 | 216 | 217 | 218 | 5 219 | 570 220 | 16 221 | 161 222 | 223 | 224 | 225 | Qt::Vertical 226 | 227 | 228 | 229 | 230 | 231 | 30 232 | 581 233 | 81 234 | 16 235 | 236 | 237 | 238 | 备份文件名 239 | 240 | 241 | 242 | 243 | 244 | 120 245 | 580 246 | 571 247 | 21 248 | 249 | 250 | 251 | 252 | 253 | 254 | 60 255 | 611 256 | 51 257 | 20 258 | 259 | 260 | 261 | 备份到 262 | 263 | 264 | 265 | 266 | false 267 | 268 | 269 | 270 | 120 271 | 610 272 | 571 273 | 21 274 | 275 | 276 | 277 | 278 | 279 | 280 | 700 281 | 610 282 | 47 283 | 21 284 | 285 | 286 | 287 | ... 288 | 289 | 290 | 291 | 292 | 293 | 45 294 | 641 295 | 61 296 | 20 297 | 298 | 299 | 300 | 定时备份 301 | 302 | 303 | 304 | 305 | 306 | 120 307 | 642 308 | 115 309 | 19 310 | 311 | 312 | 313 | 不定时 314 | 315 | 316 | true 317 | 318 | 319 | 320 | 321 | 322 | 250 323 | 642 324 | 115 325 | 19 326 | 327 | 328 | 329 | 每天 330 | 331 | 332 | false 333 | 334 | 335 | 336 | 337 | 338 | 380 339 | 642 340 | 115 341 | 19 342 | 343 | 344 | 345 | 每周 346 | 347 | 348 | false 349 | 350 | 351 | 352 | 353 | 354 | 53 355 | 671 356 | 61 357 | 19 358 | 359 | 360 | 361 | 密码 362 | 363 | 364 | 365 | 366 | false 367 | 368 | 369 | 370 | 120 371 | 671 372 | 571 373 | 21 374 | 375 | 376 | 377 | 378 | 379 | 380 | 53 381 | 701 382 | 101 383 | 19 384 | 385 | 386 | 387 | 上传到云端 388 | 389 | 390 | 391 | 392 | 393 | 700 394 | 640 395 | 47 396 | 81 397 | 398 | 399 | 400 | 开始 401 | 备份 402 | 403 | 404 | 405 | :/image/image/start.png:/image/image/start.png 406 | 407 | 408 | 409 | 24 410 | 24 411 | 412 | 413 | 414 | Qt::ToolButtonTextUnderIcon 415 | 416 | 417 | 418 | 419 | 420 | 483 421 | 521 422 | 101 423 | 26 424 | 425 | 426 | 427 | 添加目录 428 | 429 | 430 | 431 | :/image/image/add.png:/image/image/add.png 432 | 433 | 434 | Qt::ToolButtonTextBesideIcon 435 | 436 | 437 | 438 | 439 | 440 | 441 | :/image/image/restore.png:/image/image/restore.png 442 | 443 | 444 | 恢复 445 | 446 | 447 | 448 | 449 | 10 450 | 10 451 | 741 452 | 71 453 | 454 | 455 | 456 | 从本地文件恢复 457 | 458 | 459 | false 460 | 461 | 462 | true 463 | 464 | 465 | 466 | 467 | 20 468 | 32 469 | 72 470 | 15 471 | 472 | 473 | 474 | 备份文件 475 | 476 | 477 | 478 | 479 | false 480 | 481 | 482 | 483 | 101 484 | 30 485 | 561 486 | 21 487 | 488 | 489 | 490 | 491 | 492 | 493 | 670 494 | 30 495 | 47 496 | 21 497 | 498 | 499 | 500 | ... 501 | 502 | 503 | 504 | 505 | 506 | 507 | 10 508 | 100 509 | 741 510 | 501 511 | 512 | 513 | 514 | 从云端恢复 515 | 516 | 517 | true 518 | 519 | 520 | false 521 | 522 | 523 | 524 | true 525 | 526 | 527 | 528 | 20 529 | 30 530 | 72 531 | 15 532 | 533 | 534 | 535 | 云端文件 536 | 537 | 538 | 539 | 540 | true 541 | 542 | 543 | 544 | 20 545 | 60 546 | 701 547 | 381 548 | 549 | 550 | 551 | Qt::CustomContextMenu 552 | 553 | 554 | 555 | 文件名 556 | 557 | 558 | 559 | 560 | 561 | true 562 | 563 | 564 | 565 | 19 566 | 462 567 | 72 568 | 15 569 | 570 | 571 | 572 | 备份文件 573 | 574 | 575 | 576 | 577 | 578 | 100 579 | 460 580 | 621 581 | 21 582 | 583 | 584 | 585 | 586 | 587 | 588 | false 589 | 590 | 591 | 592 | 110 593 | 620 594 | 561 595 | 21 596 | 597 | 598 | 599 | 600 | 601 | 602 | 680 603 | 620 604 | 47 605 | 21 606 | 607 | 608 | 609 | ... 610 | 611 | 612 | 613 | 614 | 615 | 42 616 | 620 617 | 51 618 | 20 619 | 620 | 621 | 622 | 恢复到 623 | 624 | 625 | 626 | 627 | 628 | 35 629 | 660 630 | 61 631 | 19 632 | 633 | 634 | 635 | 密码 636 | 637 | 638 | 639 | 640 | false 641 | 642 | 643 | 644 | 110 645 | 660 646 | 561 647 | 21 648 | 649 | 650 | 651 | 652 | 653 | 654 | 110 655 | 700 656 | 561 657 | 28 658 | 659 | 660 | 661 | 开始恢复 662 | 663 | 664 | 665 | :/image/image/start.png:/image/image/start.png 666 | 667 | 668 | 669 | 670 | 671 | 672 | :/image/image/task.png:/image/image/task.png 673 | 674 | 675 | 任务 676 | 677 | 678 | 679 | 680 | 10 681 | 5 682 | 101 683 | 16 684 | 685 | 686 | 687 | 定时任务列表 688 | 689 | 690 | 691 | 692 | 693 | 10 694 | 30 695 | 746 696 | 661 697 | 698 | 699 | 700 | Qt::CustomContextMenu 701 | 702 | 703 | false 704 | 705 | 706 | 707 | 备份文件名 708 | 709 | 710 | 711 | 712 | 下一次备份时间 713 | 714 | 715 | 716 | 717 | 备份频率 718 | 719 | 720 | 721 | 722 | 加密 723 | 724 | 725 | 726 | 727 | 上传到云端 728 | 729 | 730 | 731 | 732 | 完整路径 733 | 734 | 735 | 736 | 737 | 738 | 739 | 684 740 | 700 741 | 71 742 | 26 743 | 744 | 745 | 746 | 清空 747 | 748 | 749 | 750 | :/image/image/clear.png:/image/image/clear.png 751 | 752 | 753 | Qt::ToolButtonTextBesideIcon 754 | 755 | 756 | 757 | 758 | 759 | 600 760 | 700 761 | 71 762 | 26 763 | 764 | 765 | 766 | 删除 767 | 768 | 769 | 770 | :/image/image/delete.png:/image/image/delete.png 771 | 772 | 773 | Qt::ToolButtonTextBesideIcon 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | -------------------------------------------------------------------------------- /小组实验报告.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuheicat/backup/50fdbf6516193704545ef865ed3d0bbd2ca82075/小组实验报告.docx --------------------------------------------------------------------------------