diff --git a/plugins/libzipplugin/libzipplugin.cpp b/plugins/libzipplugin/libzipplugin.cpp --- a/plugins/libzipplugin/libzipplugin.cpp +++ b/plugins/libzipplugin/libzipplugin.cpp @@ -38,6 +38,9 @@ #include #include +#include +#include + K_PLUGIN_FACTORY_WITH_JSON(LibZipPluginFactory, "kerfuffle_libzip.json", registerPlugin();) // This is needed for hooking a C callback to a C++ non-static member @@ -534,139 +537,176 @@ destination = destDirCorrected + QFileInfo(entry).fileName(); } + // Store parent mtime. + QString parentDir; + if (isDirectory) { + QDir pDir = QFileInfo(destination).dir(); + pDir.cdUp(); + parentDir = pDir.path(); + } else { + parentDir = QFileInfo(destination).path(); + } + // For top-level items, dont restore parent dir mtime. + bool restoreParentMtime = (parentDir+QDir::separator() != destDirCorrected); + + time_t parent_mtime; + if (restoreParentMtime) { + struct stat result; + if (stat(parentDir.toUtf8(), &result) != 0) { + qCWarning(ARK) << "Failed to read mtime for parent dir of" << destination; + restoreParentMtime = false; + } + parent_mtime = result.st_mtim.tv_sec; + } + // Create parent directories for files. For directories create them. if (!QDir().mkpath(QFileInfo(destination).path())) { qCDebug(ARK) << "Failed to create directory:" << QFileInfo(destination).path(); emit error(xi18n("Failed to create directory: %1", QFileInfo(destination).path())); return false; } - if (isDirectory) { - return true; + // Get statistic for entry. Used to get entry size and mtime. + zip_stat_t sb; + if (zip_stat(archive, entry.toUtf8(), 0, &sb) != 0) { + qCCritical(ARK) << "Failed to read stat for entry" << entry; + return false; } - // Handle existing destination files. - QString renamedEntry = entry; - while (!m_overwriteAll && QFileInfo::exists(destination)) { - if (m_skipAll) { - return true; - } else { - Kerfuffle::OverwriteQuery query(renamedEntry); - emit userQuery(&query); - query.waitForResponse(); + if (!isDirectory) { - if (query.responseCancelled()) { - return false; - } else if (query.responseSkip()) { + // Handle existing destination files. + QString renamedEntry = entry; + while (!m_overwriteAll && QFileInfo::exists(destination)) { + if (m_skipAll) { return true; - } else if (query.responseAutoSkip()) { - m_skipAll = true; - return true; - } else if (query.responseRename()) { - const QString newName(query.newFilename()); - destination = QFileInfo(destination).path() + QDir::separator() + QFileInfo(newName).fileName(); - renamedEntry = QFileInfo(entry).path() + QDir::separator() + QFileInfo(newName).fileName(); - } else if (query.responseOverwriteAll()) { - m_overwriteAll = true; - break; - } else if (query.responseOverwrite()) { - break; + } else { + Kerfuffle::OverwriteQuery query(renamedEntry); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseCancelled()) { + return false; + } else if (query.responseSkip()) { + return true; + } else if (query.responseAutoSkip()) { + m_skipAll = true; + return true; + } else if (query.responseRename()) { + const QString newName(query.newFilename()); + destination = QFileInfo(destination).path() + QDir::separator() + QFileInfo(newName).fileName(); + renamedEntry = QFileInfo(entry).path() + QDir::separator() + QFileInfo(newName).fileName(); + } else if (query.responseOverwriteAll()) { + m_overwriteAll = true; + break; + } else if (query.responseOverwrite()) { + break; + } } } - } - // Handle password-protected files. - zip_file *zf = nullptr; - bool firstTry = true; - while (!zf) { - zf = zip_fopen(archive, entry.toUtf8(), 0); - if (zf) { - break; - } else if (zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_NOPASSWD || - zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_WRONGPASSWD) { - Kerfuffle::PasswordNeededQuery query(filename(), !firstTry); - emit userQuery(&query); - query.waitForResponse(); - - if (query.responseCancelled()) { - emit cancelled(); + // Handle password-protected files. + zip_file *zf = nullptr; + bool firstTry = true; + while (!zf) { + zf = zip_fopen(archive, entry.toUtf8(), 0); + if (zf) { + break; + } else if (zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_NOPASSWD || + zip_error_code_zip(zip_get_error(archive)) == ZIP_ER_WRONGPASSWD) { + Kerfuffle::PasswordNeededQuery query(filename(), !firstTry); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseCancelled()) { + emit cancelled(); + return false; + } + setPassword(query.password()); + + if (zip_set_default_password(archive, password().toUtf8())) { + qCDebug(ARK) << "Failed to set password for:" << entry; + } + firstTry = false; + } else { + qCCritical(ARK) << "Failed to open file:" << zip_strerror(archive); + emit error(xi18n("Failed to open '%1':%2", entry, QString::fromUtf8(zip_strerror(archive)))); return false; } - setPassword(query.password()); + } - if (zip_set_default_password(archive, password().toUtf8())) { - qCDebug(ARK) << "Failed to set password for:" << entry; - } - firstTry = false; - } else { - qCCritical(ARK) << "Failed to open file:" << zip_strerror(archive); - emit error(xi18n("Failed to open '%1':%2", entry, QString::fromUtf8(zip_strerror(archive)))); + QFile file(destination); + if (!file.open(QIODevice::WriteOnly)) { + qCCritical(ARK) << "Failed to open file for writing"; + emit error(xi18n("Failed to open file for writing: %1", destination)); return false; } - } - - QFile file(destination); - if (!file.open(QIODevice::WriteOnly)) { - qCCritical(ARK) << "Failed to open file for writing"; - emit error(xi18n("Failed to open file for writing: %1", destination)); - return false; - } - QDataStream out(&file); + QDataStream out(&file); + + // Write archive entry to file. We use a read/write buffer of 1000 chars. + qulonglong sum = 0; + char buf[1000]; + int len; + while (sum != sb.size) { + len = zip_fread(zf, buf, 1000); + if (len < 0) { + qCCritical(ARK) << "Failed to read data"; + emit error(xi18n("Failed to read data for entry: %1", entry)); + return false; + } + if (out.writeRawData(buf, len) != len) { + qCCritical(ARK) << "Failed to write data"; + emit error(xi18n("Failed to write data for entry: %1", entry)); + return false; + } - // Get statistic for entry. Used below to get entry size. - zip_stat_t sb; - if (zip_stat(archive, entry.toUtf8(), 0, &sb) != 0) { - qCCritical(ARK) << "Failed to read stat for entry" << entry; - return false; - } + sum += len; + } - // Write archive entry to file. We use a read/write buffer of 1000 chars. - qulonglong sum = 0; - char buf[1000]; - int len; - while (sum != sb.size) { - len = zip_fread(zf, buf, 1000); - if (len < 0) { - qCCritical(ARK) << "Failed to read data"; - emit error(xi18n("Failed to read data for entry: %1", entry)); + const auto index = zip_name_locate(archive, entry.toUtf8(), ZIP_FL_ENC_GUESS); + if (index == -1) { + qCCritical(ARK) << "Could not locate entry:" << entry; + emit error(xi18n("Failed to locate entry: %1", entry)); return false; } - if (out.writeRawData(buf, len) != len) { - qCCritical(ARK) << "Failed to write data"; - emit error(xi18n("Failed to write data for entry: %1", entry)); + + zip_uint8_t opsys; + zip_uint32_t attributes; + if (zip_file_get_external_attributes(archive, index, ZIP_FL_UNCHANGED, &opsys, &attributes) == -1) { + qCCritical(ARK) << "Could not read external attributes for entry:" << entry; + emit error(xi18n("Failed to read metadata for entry: %1", entry)); return false; } - sum += len; - } + // Inspired by fuse-zip source code: fuse-zip/lib/fileNode.cpp + switch (opsys) { + case ZIP_OPSYS_UNIX: + // Unix permissions are stored in the leftmost 16 bits of the external file attribute. + file.setPermissions(KIO::convertPermissions(attributes >> 16)); + break; + default: // TODO: non-UNIX. + break; + } - const auto index = zip_name_locate(archive, entry.toUtf8(), ZIP_FL_ENC_GUESS); - if (index == -1) { - qCCritical(ARK) << "Could not locate entry:" << entry; - emit error(xi18n("Failed to locate entry: %1", entry)); - return false; + file.close(); } - zip_uint8_t opsys; - zip_uint32_t attributes; - if (zip_file_get_external_attributes(archive, index, ZIP_FL_UNCHANGED, &opsys, &attributes) == -1) { - qCCritical(ARK) << "Could not read external attributes for entry:" << entry; - emit error(xi18n("Failed to read metadata for entry: %1", entry)); - return false; + // Set mtime for entry. + utimbuf times; + times.modtime = sb.mtime; + if (utime(destination.toUtf8(), ×) != 0) { + qCWarning(ARK) << "Failed to restore mtime:" << destination; } - // Inspired by fuse-zip source code: fuse-zip/lib/fileNode.cpp - switch (opsys) { - case ZIP_OPSYS_UNIX: - // Unix permissions are stored in the leftmost 16 bits of the external file attribute. - file.setPermissions(KIO::convertPermissions(attributes >> 16)); - break; - default: // TODO: non-UNIX. - break; + if (restoreParentMtime) { + // Restore mtime for parent dir. + times.modtime = parent_mtime; + if (utime(parentDir.toUtf8(), ×) != 0) { + qCWarning(ARK) << "Failed to restore mtime for parent dir of:" << destination; + } } - return true; }