File: archiver.cpp

package info (click to toggle)
lxqt-archiver 0.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 2,088 kB
  • sloc: ansic: 16,286; cpp: 2,658; python: 30; sh: 23; makefile: 11
file content (608 lines) | stat: -rw-r--r-- 22,709 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
#include "archiver.h"
#include "archiveritem.h"

extern "C" {
#include "core/fr-command.h"
#include "core/file-utils.h"
#include "core/fr-init.h"
}

#include <glib.h>
#include <gobject/gobject.h>

#include <QMimeDatabase>
#include <QMimeType>
#include <QFile>
#include <QDir>

#include <unordered_map>


Archiver::Archiver(QObject* parent):
    QObject(parent),
    frArchive_{fr_archive_new()},
    rootItem_{nullptr},
    busy_{false},
    isEncrypted_{false},
    uncompressedSize_{0} {

    g_signal_connect(frArchive_, "start", G_CALLBACK(&onStart), this);
    g_signal_connect(frArchive_, "done", G_CALLBACK(&onDone), this);
    g_signal_connect(frArchive_, "progress", G_CALLBACK(&onProgress), this);
    g_signal_connect(frArchive_, "message", G_CALLBACK(&onMessage), this);
    g_signal_connect(frArchive_, "stoppable", G_CALLBACK(&onStoppable), this);
    g_signal_connect(frArchive_, "working-archive", G_CALLBACK(&onWorkingArchive), this);
}

Archiver::~Archiver() {
    if(frArchive_) {
        g_signal_handlers_disconnect_by_data(frArchive_, this);
        g_object_unref(frArchive_);
    }
}

Fm::FilePath Archiver::currentArchivePath() const {
    Fm::FilePath path;
    if(frArchive_) {
        path = Fm::FilePath{frArchive_->file, true};
    }
    return path;
}

const char *Archiver::currentArchiveContentType() const {
    return frArchive_ ? frArchive_->content_type : nullptr;
}

bool Archiver::createNewArchive(const char* uri) {
    Q_EMIT invalidateContent();
    isEncrypted_ = false;
    uncompressedSize_ = 0;

    if(!fr_archive_create(frArchive_, uri)) {
        // this will trigger a queued finished() signal
        fr_archive_action_completed(frArchive_,
                                    FR_ACTION_CREATING_NEW_ARCHIVE,
                                    FR_PROC_ERROR_GENERIC,
                                    tr("Archive type not supported.").toUtf8().constData());
        return false;
    }

    // this will trigger a queued finished() signal
    fr_archive_action_completed(frArchive_,
                                FR_ACTION_CREATING_NEW_ARCHIVE,
                                FR_PROC_ERROR_NONE,
                                NULL);
    return true;
}

bool Archiver::createNewArchive(const QUrl& uri) {
    return createNewArchive(uri.toEncoded().constData());
}

bool Archiver::openArchive(const char* uri, const char* password) {
    Q_EMIT invalidateContent();
    isEncrypted_ = false;
    uncompressedSize_ = 0;

    return fr_archive_load(frArchive_, uri, password);
}

bool Archiver::openArchive(const QUrl& uri, const char* password) {
    return openArchive(uri.toEncoded().constData(), password);
}

void Archiver::reloadArchive(const char* password) {
    Q_EMIT invalidateContent();
    isEncrypted_ = false;
    uncompressedSize_ = 0;

    fr_archive_reload(frArchive_, password);
}

bool Archiver::isLoaded() const {
    return frArchive_->file != nullptr;
}

void Archiver::addFiles(GList* relativefileNames, const char* srcDirUri, const char* destDirPath, bool onlyIfNewer, const char* password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    fr_archive_add_files(frArchive_, relativefileNames, srcDirUri, destDirPath, onlyIfNewer, password, encrypt_header, compression, volume_size);
}

void Archiver::addFiles(const Fm::FilePathList& srcPaths, const char* destDirPath, bool onlyIfNewer, const char* password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    if(srcPaths.empty()) {
        return;
    }

    bool hasError = false;
    GList* relNames = nullptr;
    auto srcParent = srcPaths.front().parent();
    for(int i = srcPaths.size() - 1; i >= 0; --i) {
        const auto& path = srcPaths[i];
        // ensure that all source files are children of the same base dir
        if(!srcParent.isPrefixOf(path)) {
            hasError = true;
            break;
        }
        // get relative paths of the src files
        relNames = g_list_prepend(relNames,
                                  g_strdup(srcParent.relativePathStr(path).get()));
    }

    if(hasError) {
        // TODO: report errors properly
    }
    else {
        addFiles(relNames, srcParent.uri().get(), destDirPath, onlyIfNewer, password, encrypt_header, compression, volume_size);
    }

    freeStrsGList(relNames);
}

void Archiver::addDirectory(const char* directoryUri, const char* baseDirUri, const char* destDirPath, bool onlyIfNewer, const char* password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    fr_archive_add_directory(frArchive_, directoryUri, baseDirUri, destDirPath, onlyIfNewer, password, encrypt_header, compression, volume_size);
}

void Archiver::addDirectory(const Fm::FilePath& directory, const char* destDirPath, bool onlyIfNewer, const char* password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    auto parent = directory.parent();
    addDirectory(directory.uri().get(), parent.uri().get(), destDirPath,
                 onlyIfNewer, password, encrypt_header, compression, volume_size);
}

void Archiver::addDroppedItems(GList *item_list, const char *base_dir, const char *dest_dir, bool update, const char *password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    fr_archive_add_dropped_items(frArchive_, item_list, base_dir, dest_dir, update, password, encrypt_header, compression, volume_size);
}

void Archiver::addDroppedItems(const Fm::FilePathList &srcPaths, const char *base_dir, const char *dest_dir, bool update, const char *password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    GList* itemGList = nullptr;
    for(int i = srcPaths.size() - 1; i >= 0; --i) {
        const auto& path = srcPaths[i];
        itemGList = g_list_prepend(itemGList,
                                  g_strdup(path.uri().get()));
    }
    addDroppedItems(itemGList, base_dir, dest_dir, update, password, encrypt_header, compression, volume_size);
    freeStrsGList(itemGList);
}

/*
void Archive::addWithWildcard(const char* include_files, const char* exclude_files, const char* exclude_folders, const char* base_dir, const char* dest_dir, bool update, bool follow_links, const char* password, bool encrypt_header, FrCompression compression, unsigned int volume_size) {
    return fr_archive_add_with_wildcard(impl_, include_files, exclude_files, exclude_folders, base_dir, dest_dir, update, follow_links, password, encrypt_header, compression, volume_size);
}
*/

void Archiver::removeFiles(GList* fileNames, FrCompression compression) {
    fr_process_clear(frArchive_->process);
    fr_archive_remove(frArchive_, fileNames, compression);
    fr_process_start(frArchive_->process);
}

void Archiver::removeFiles(const std::vector<const FileData*>& files, FrCompression compression) {
    GList* glist = nullptr;
    for(int i = files.size() - 1; i >= 0; --i) {
        glist = g_list_prepend(glist, g_strdup(files[i]->original_path));
    }
    removeFiles(glist, compression);
    freeStrsGList(glist);
}

void Archiver::extractFiles(GList* fileNames, const char* destDirUri, const char* baseDirPath, bool skip_older, bool overwrite, bool junk_path, const char* password) {
    fr_process_clear(frArchive_->process);
    fr_archive_extract(frArchive_, fileNames, destDirUri, baseDirPath, skip_older, overwrite, junk_path, password);
    fr_process_start(frArchive_->process);
}

void Archiver::extractFiles(const std::vector<const FileData*>& files, const Fm::FilePath& destDir, const char* baseDirPath, bool skip_older, bool overwrite, bool junk_path, const char* password) {
    GList* glist = nullptr;
    for(int i = files.size() - 1; i >= 0; --i) {
        glist = g_list_prepend(glist, g_strdup(files[i]->original_path));
    }
    extractFiles(glist, destDir.uri().get(), baseDirPath, skip_older, overwrite, junk_path, password);
    freeStrsGList(glist);
}

void Archiver::extractAll(const char* destDirUri, bool skip_older, bool overwrite, bool junk_path, const char* password) {
    if(!overwrite && rootItem_) {
        // if the archive has multiple files but no parent folder, make a directory
        // and extract them in it, instead of putting them among existing files
        if(rootItem_->children().size() > 1
           || (rootItem_->children().size() == 1
               && rootItem_->children()[0]->isDir()
               && (rootItem_->children()[0]->name() == nullptr
                   || strcmp(rootItem_->children()[0]->name(), ".") == 0))) { // may happen with rpm
            auto dirUrl = QUrl::fromEncoded(destDirUri);
            if(dirUrl.isLocalFile()) {
                QString dirName = archiveDisplayName().section(QStringLiteral("/"), -1);
                if(dirName.contains(QStringLiteral("."))) {
                    dirName = dirName.section(QStringLiteral("."), 0, -2);
                }
                if(dirName.isEmpty()) { // this is possible (as in ".zip")
                    dirName = QStringLiteral("lxqt-archiver-extracted");
                }

                QString extDir = dirUrl.toLocalFile();
                if(!extDir.endsWith(QStringLiteral("/"))) {
                    extDir += QStringLiteral("/");
                }
                extDir += dirName;

                // don't overwrite but make a directory
                QString suffix;
                int i = 0;
                while(QFile::exists(extDir + suffix)) {
                    suffix = QStringLiteral("-") + QString::number(i);
                    i++;
                }
                extDir += suffix;
                QDir dir(extDir);
                dir.mkpath(extDir);

                dirUrl = QUrl::fromLocalFile(extDir);
                extractFiles(nullptr, dirUrl.toEncoded().constData(), nullptr, skip_older, false, junk_path, password);
                return;
            }
        }
    }
    extractFiles(nullptr, destDirUri, nullptr, skip_older, overwrite, junk_path, password);
}

bool Archiver::extractHere(bool skip_older, bool overwrite, bool junk_path, const char* password) {
    fr_process_clear(frArchive_->process);
    if(fr_archive_extract_here(frArchive_, skip_older, overwrite, junk_path, password)) {
        fr_process_start(frArchive_->process);
        return true;
    }
    return false;
}

const char* Archiver::lastExtractionDestination() const {
    return fr_archive_get_last_extraction_destination(frArchive_);
}

void Archiver::testArchiveIntegrity(const char* password) {
    fr_archive_test(frArchive_, password);
}

QStringList Archiver::supportedCreateMimeTypes() {
    QStringList types;
    for(auto p = create_type; *p != -1; ++p) {
        types << QString::fromUtf8(mime_type_desc[*p].mime_type);
    }
    return types;
}

static QString suffixesToNameFilter(QString name, const QStringList& suffixes) {
    QString filter = std::move(name);
    filter += QLatin1String(" (");
    for(const auto& suffix: suffixes) {
        if(filter[filter.length() - 1] != QLatin1Char('(')) {
            filter += QLatin1Char(' ');
        }
        filter += QLatin1String("*.");
        filter += suffix;
    }
    filter += QLatin1String(")");
    return filter;
}

bool Archiver::isEncrypted() const {
    return isEncrypted_;
}


std::uint64_t Archiver::uncompressedSize() const {
    return uncompressedSize_;
}

QStringList Archiver::mimeDescToNameFilters(int* mimeDescIndexes) {
    QStringList filters;
    QStringList allSuffixes;
    QMimeDatabase mimeDb;
    for(auto p = mimeDescIndexes; *p != -1; ++p) {
        auto mimeType = mimeDb.mimeTypeForName(QString::fromUtf8(mime_type_desc[*p].mime_type));
        QString filter;
        if(mimeType.isValid()) {
            auto suffixes = mimeType.suffixes();
            if(suffixes.empty()) {
                suffixes.append(QString::fromUtf8(mime_type_desc[*p].default_ext));
            }
            filter = suffixesToNameFilter(mimeType.comment(), suffixes);
            allSuffixes += suffixes;
        }
        else {
            filter = tr("*%1 files (*%1)").arg(QString::fromUtf8(mime_type_desc[*p].default_ext));
        }
        filters << filter;
    }
    filters.insert(0, suffixesToNameFilter(tr("All supported formats"), allSuffixes));
    return filters;
}

QStringList Archiver::supportedCreateNameFilters() {
    return mimeDescToNameFilters(create_type);
}

QStringList Archiver::supportedOpenMimeTypes() {
    QStringList types;
    for(auto p = open_type; *p != -1; ++p) {
        types << QString::fromUtf8(mime_type_desc[*p].mime_type);
    }
    return types;

}

QStringList Archiver::supportedOpenNameFilters() {
    return mimeDescToNameFilters(open_type);
}

QStringList Archiver::supportedSaveMimeTypes() {
    QStringList types;
    for(auto p = save_type; *p != -1; ++p) {
        types << QString::fromUtf8(mime_type_desc[*p].mime_type);
    }
    return types;
}

QStringList Archiver::supportedSaveNameFilters() {
    return mimeDescToNameFilters(save_type);
}

std::string Archiver::stripTrailingSlash(std::string dirPath) {
    if(dirPath != "/" && dirPath.back() == '/') {
        dirPath.pop_back();
    }
    return dirPath;
}

void Archiver::freeStrsGList(GList* strs) {
    g_list_foreach(strs, (GFunc)g_free, nullptr);
}

void Archiver::rebuildDirTree() {
    // The archive content is listed by Archiver in a flat list
    // Let's rebuild the tree structure by mapping dir_path => [file1, file2, ...]
    rootItem_ = nullptr;
    items_.clear();
    dirMap_.clear();

    isEncrypted_ = false;
    uncompressedSize_ = 0;

    if(!frArchive_->command || !frArchive_->command->files) {
        return;
    }

    auto n_files = frArchive_->command->files->len;

    // create one ArchiverItem per file and build dir_path => ArchiverItem mappings
    items_.reserve(n_files);
    for(unsigned int i = 0; i < n_files; ++i) {
        auto fileData = reinterpret_cast<FileData*>(g_ptr_array_index(frArchive_->command->files, i));
        items_.emplace_back(new ArchiverItem{fileData, false}); // do not take ownership of the existing FileData object
        auto item = items_.back().get();
        if(item->isDir()) {
            std::string dirName = stripTrailingSlash(item->fullPath());
            dirMap_[dirName] = item;
        }

        if(fileData->encrypted) {
            isEncrypted_ = true;
        }

        uncompressedSize_ += fileData->size;
    }

    // By default, file-roller FrArchive does not creates FileData for some parent dirs.
    // For example, for the following content:
    //   /exampe/sub/dir/file1.txt
    //   /exampe/sub/dir/file2.txt
    // FrArchive only generates /example/sub/dir/, /example/sub/dir/file1.txt, /example/sub/dir/file2.txt.
    // However we still need create FileData for /, /example/, /example/sub/ to form a full dir tree.
    // So we create the missing items by ourselves :-(

    // for each item, ensure all its parent dirs exist and setup the parent-child links
    for(unsigned int i = 0; i < n_files; ++i) {
        auto item = items_[i].get();
        while(strcmp(item->fullPath(), "/")) {
            ArchiverItem* parent = nullptr;
            std::string dirName = stripTrailingSlash(item->fullPath());
            if(dirName.empty() || dirName == "/") {
                break;
            }
            // get parent dir of the current file
            dirName = Fm::CStrPtr{g_path_get_dirname(dirName.c_str())}.get();
            auto it = dirMap_.find(dirName);
            if(it == dirMap_.end()) { // parent dir is not found, create an item for it
                // Create a new FileData item for this parent dir
                auto fileData = file_data_new();

                // ensure that dir paths end with '/'
                fileData->full_path = dirName.back() == '/' ? g_strdup(dirName.c_str()) : g_strconcat(dirName.c_str(), "/", nullptr); // ensure slash
                auto full_path_len = strlen(fileData->full_path);
                if(fileData->full_path[full_path_len - 1] == '/') {
                    fileData->full_path[full_path_len] = '\0';
                }

                fileData->original_path = g_path_get_dirname(stripTrailingSlash(item->originalPath()).c_str());
                auto original_path_len = strlen(fileData->original_path);
                if(fileData->original_path[original_path_len - 1] != '/') {
                    auto tmp = fileData->original_path;
                    fileData->original_path = g_strconcat(tmp, "/", nullptr);
                    g_free(tmp);
                }

                //qDebug("op: %s, %s", fileData->original_path, item->originalPath());
                //qDebug("fp: %s, %s", fileData->full_path, item->fullPath());
                fileData->name = g_path_get_basename(dirName.c_str());
                fileData->dir = 1;
                file_data_update_content_type(fileData);
                items_.emplace_back(new ArchiverItem{fileData, true}); // take ownership of the new FileData object
                parent = items_.back().get();
                it = dirMap_.emplace(dirName, parent).first;

                // add current file to its children
                parent->addChild(item);
                item = parent; // go up one level and continue
            }
            else { // parent item found, add current file to its children
                parent = it->second;
                parent->addChild(item);
                break;
            }
        }
    }

    // if the archive is completey empty, at least generate a root node "/"
    if(dirMap_.empty()) {
        auto fileData = file_data_new();
        fileData->full_path = g_strdup("/");
        fileData->original_path = g_strdup("/");
        fileData->name = g_strdup("/");
        fileData->dir = 1;
        file_data_update_content_type(fileData);
        items_.emplace_back(new ArchiverItem{fileData, true}); // take ownership of the new FileData object
        dirMap_.emplace("/", items_.back().get());
    }
    rootItem_ = dirMap_["/"];

    /*for(auto& kv: dirMap_) {
        qDebug("dir: %s: %d", kv.first.c_str(), kv.second->children().size());
    }*/
}

void Archiver::stopCurrentAction() {
    fr_archive_stop(frArchive_);
}

QUrl Archiver::archiveUrl() const {
    QUrl ret;
    if(frArchive_->file) {
        char* uriStr = g_file_get_uri(frArchive_->file);
        ret = QUrl::fromEncoded(uriStr);
        g_free(uriStr);
    }
    return ret;
}

QString Archiver::archiveDisplayName() const {
    QString ret;
    if(frArchive_->file) {
        char* name = g_file_get_parse_name(frArchive_->file);
        ret = QString::fromUtf8(name);
        g_free(name);
    }
    return ret;
}

bool Archiver::isBusy() const {
    return busy_;
}

FrAction Archiver::currentAction() const {
    if(frArchive_->command) {
        return frArchive_->command->action;
    }
    return FR_ACTION_NONE;
}

ArchiverError Archiver::lastError() const {
    return ArchiverError{&frArchive_->error};
}

std::vector<const ArchiverItem*> Archiver::flatFileList() const {
    std::vector<const ArchiverItem*> files;
    for(const auto& item: items_) {
        if(!item->isDir()) {
            files.emplace_back(item.get());
        }
    }
    return files;
}

const ArchiverItem *Archiver::itemByPath(const char *fullPath) const {
    return nullptr;
}

const ArchiverItem *Archiver::dirTreeRoot() const {
    return rootItem_;
}

const ArchiverItem *Archiver::dirByPath(const char *path) const {
    // strip trailing /
    std::string dirPath{path};
    if(dirPath.length() > 1 && dirPath.back() == '/') {
        dirPath.pop_back();
    }
    auto it = dirMap_.find(path);
    return it != dirMap_.end() ? it->second : nullptr;
}

const ArchiverItem *Archiver::parentDir(const ArchiverItem *file) const {
    if(file && strcmp(file->fullPath(), "/")) {
        auto parentPath = Fm::CStrPtr{g_path_get_dirname(stripTrailingSlash(file->fullPath()).c_str())};
        return dirByPath(parentPath.get());
    }
    return nullptr;
}

bool Archiver::isDir(const FileData *file) const {
    return file_data_is_dir(const_cast<FileData*>(file));
}

const FileData* Archiver::fileDataByOriginalPath(const char* originalPath) {
    // FIXME: this searches using file->original_path but sometimes we want file->full_path instead :-(
    auto idx = find_path_in_file_data_array(frArchive_->command->files, originalPath);
    if(idx >= 0) {
        return reinterpret_cast<FileData*>(g_ptr_array_index(frArchive_->command->files, idx));
    }
    return nullptr;
}


// GObject signal callbacks

// NOTE: emitting Qt signals within glib signal callbacks does not work in some cases.
// We use the workaround provided here: https://bugreports.qt.io/browse/QTBUG-18434

void Archiver::onStart(FrArchive*, FrAction action, Archiver* _this) {
    //qDebug("start");

    _this->busy_ = true;
    QMetaObject::invokeMethod(_this, "start", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(FrAction, action));
}

void Archiver::onDone(FrArchive*, FrAction action, FrProcError* error, Archiver* _this) {
    //qDebug("done: %s", error && error->gerror ? error->gerror->message : "");
    // FIXME: error might become dangling pointer for queued connections. :-(

    switch(action) {
    case FR_ACTION_CREATING_NEW_ARCHIVE:  // same as listing empty content
    case FR_ACTION_CREATING_ARCHIVE:           /* creating a local archive */
    case FR_ACTION_LISTING_CONTENT:            /* listing the content of the archive */
        _this->rebuildDirTree();
        break;
    default:
        break;
    }

    _this->busy_ = false;

    QMetaObject::invokeMethod(_this, "finish", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(FrAction, action), Q_ARG(ArchiverError, error));
}

void Archiver::onProgress(FrArchive*, double fraction, Archiver* _this) {
    QMetaObject::invokeMethod(_this, "progress", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(double, fraction));
    //qDebug("progress: %lf", fraction);
}

void Archiver::onMessage(FrArchive*, const char* msg, Archiver* _this) {
    //qDebug("message: %s", msg);
    QMetaObject::invokeMethod(_this, "message", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(QString, QString::fromUtf8(msg)));
}

void Archiver::onStoppable(FrArchive*, gboolean value, Archiver* _this) {
    QMetaObject::invokeMethod(_this, "stoppableChanged", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(bool, bool(value)));
}

void Archiver::onWorkingArchive(FrCommand* comm, const char* filename, Archiver* _this) {
    // FIXME: why the first param is comm?
    //qDebug("working: %s", filename);
    Q_EMIT _this->workingArchive(QString::fromUtf8(filename));
}