/* * Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "filepropsdialog.h" #include "ui_file-props.h" #include "utilities.h" #include "fileoperation.h" #include #include #include #include #include #include #include #include #include "core/totalsizejob.h" #include "core/folder.h" #define DIFFERENT_UIDS ((uid)-1) #define DIFFERENT_GIDS ((gid)-1) #define DIFFERENT_PERMS ((mode_t)-1) namespace Fm { enum { ACCESS_NO_CHANGE = 0, ACCESS_READ_ONLY, ACCESS_READ_WRITE, ACCESS_FORBID }; FilePropsDialog::FilePropsDialog(Fm::FileInfoList files, QWidget* parent, Qt::WindowFlags f): QDialog(parent, f), fileInfos_{std::move(files)}, fileInfo{fileInfos_.front()}, singleType(fileInfos_.isSameType()), singleFile(fileInfos_.size() == 1 ? true : false) { setAttribute(Qt::WA_DeleteOnClose); ui = new Ui::FilePropsDialog(); ui->setupUi(this); if(singleType) { mimeType = fileInfo->mimeType(); } totalSizeJob = new Fm::TotalSizeJob(fileInfos_.paths(), Fm::TotalSizeJob::DEFAULT); initGeneralPage(); initPermissionsPage(); } FilePropsDialog::~FilePropsDialog() { // Stop the timer if it's still running if(fileSizeTimer) { fileSizeTimer->stop(); delete fileSizeTimer; fileSizeTimer = nullptr; } // Cancel the indexing job if it hasn't finished if(totalSizeJob) { totalSizeJob->cancel(); totalSizeJob = nullptr; } // And finally delete the dialog's UI delete ui; } void FilePropsDialog::initApplications() { if(singleType && mimeType && !fileInfo->isDir()) { ui->openWith->setMimeType(mimeType); } else { ui->openWith->hide(); ui->openWithLabel->hide(); } } void FilePropsDialog::initPermissionsPage() { // ownership handling // get owner/group and mode of the first file in the list uid = fileInfo->uid(); gid = fileInfo->gid(); mode_t mode = fileInfo->mode(); ownerPerm = (mode & (S_IRUSR | S_IWUSR | S_IXUSR)); groupPerm = (mode & (S_IRGRP | S_IWGRP | S_IXGRP)); otherPerm = (mode & (S_IROTH | S_IWOTH | S_IXOTH)); execPerm = (mode & (S_IXUSR | S_IXGRP | S_IXOTH)); allNative = fileInfo->isNative(); hasDir = S_ISDIR(mode); // check if all selected files belongs to the same owner/group or have the same mode // at the same time, check if all files are on native unix filesystems for(auto& fi: fileInfos_) { if(allNative && !fi->isNative()) { allNative = false; // not all of the files are native } mode_t fi_mode = fi->mode(); if(S_ISDIR(fi_mode)) { hasDir = true; // the files list contains dir(s) } if(uid != DIFFERENT_UIDS && static_cast(uid) != fi->uid()) { uid = DIFFERENT_UIDS; // not all files have the same owner } if(gid != DIFFERENT_GIDS && static_cast(gid) != fi->gid()) { gid = DIFFERENT_GIDS; // not all files have the same owner group } if(ownerPerm != DIFFERENT_PERMS && ownerPerm != (fi_mode & (S_IRUSR | S_IWUSR | S_IXUSR))) { ownerPerm = DIFFERENT_PERMS; // not all files have the same permission for owner } if(groupPerm != DIFFERENT_PERMS && groupPerm != (fi_mode & (S_IRGRP | S_IWGRP | S_IXGRP))) { groupPerm = DIFFERENT_PERMS; // not all files have the same permission for grop } if(otherPerm != DIFFERENT_PERMS && otherPerm != (fi_mode & (S_IROTH | S_IWOTH | S_IXOTH))) { otherPerm = DIFFERENT_PERMS; // not all files have the same permission for other } if(execPerm != DIFFERENT_PERMS && execPerm != (fi_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { execPerm = DIFFERENT_PERMS; // not all files have the same executable permission } } // init owner/group initOwner(); // if all files are of the same type, and some of them are dirs => all of the items are dirs // rwx values have different meanings for dirs // Let's make it clear for the users // init combo boxes for file permissions here QStringList comboItems; comboItems.append("---"); // no change if(singleType && hasDir) { // all files are dirs comboItems.append(tr("View folder content")); comboItems.append(tr("View and modify folder content")); ui->executable->hide(); } else { //not all of the files are dirs comboItems.append(tr("Read")); comboItems.append(tr("Read and write")); } comboItems.append(tr("Forbidden")); QStringListModel* comboModel = new QStringListModel(comboItems, this); ui->ownerPerm->setModel(comboModel); ui->groupPerm->setModel(comboModel); ui->otherPerm->setModel(comboModel); // owner ownerPermSel = ACCESS_NO_CHANGE; if(ownerPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files if(ownerPerm & S_IRUSR) { // can read if(ownerPerm & S_IWUSR) { // can write ownerPermSel = ACCESS_READ_WRITE; } else { ownerPermSel = ACCESS_READ_ONLY; } } else { if((ownerPerm & S_IWUSR) == 0) { // cannot read or write ownerPermSel = ACCESS_FORBID; } } } ui->ownerPerm->setCurrentIndex(ownerPermSel); // owner and group groupPermSel = ACCESS_NO_CHANGE; if(groupPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files if(groupPerm & S_IRGRP) { // can read if(groupPerm & S_IWGRP) { // can write groupPermSel = ACCESS_READ_WRITE; } else { groupPermSel = ACCESS_READ_ONLY; } } else { if((groupPerm & S_IWGRP) == 0) { // cannot read or write groupPermSel = ACCESS_FORBID; } } } ui->groupPerm->setCurrentIndex(groupPermSel); // other otherPermSel = ACCESS_NO_CHANGE; if(otherPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files if(otherPerm & S_IROTH) { // can read if(otherPerm & S_IWOTH) { // can write otherPermSel = ACCESS_READ_WRITE; } else { otherPermSel = ACCESS_READ_ONLY; } } else { if((otherPerm & S_IWOTH) == 0) { // cannot read or write otherPermSel = ACCESS_FORBID; } } } ui->otherPerm->setCurrentIndex(otherPermSel); // set the checkbox to partially checked state // when owner, group, and other have different executable flags set. // some of them have exec, and others do not have. execCheckState = Qt::PartiallyChecked; if(execPerm != DIFFERENT_PERMS) { // if all files have the same executable permission // check if the files are all executable if((mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == (S_IXUSR | S_IXGRP | S_IXOTH)) { // owner, group, and other all have exec permission. ui->executable->setTristate(false); execCheckState = Qt::Checked; } else if((mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) { // owner, group, and other all have no exec permission ui->executable->setTristate(false); execCheckState = Qt::Unchecked; } } ui->executable->setCheckState(execCheckState); } void FilePropsDialog::initGeneralPage() { // update UI if(singleType) { // all files are of the same mime-type std::shared_ptr icon; // FIXME: handle custom icons for some files // FIXME: display special property pages for special files or // some specified mime-types. if(singleFile) { // only one file is selected. icon = fileInfo->icon(); } if(mimeType) { if(!icon) { // get an icon from mime type if needed icon = mimeType->icon(); } ui->fileType->setText(mimeType->desc()); ui->mimeType->setText(mimeType->name()); } if(icon) { ui->iconButton->setIcon(icon->qicon()); } if(singleFile && fileInfo->isSymlink()) { ui->target->setText(QString::fromStdString(fileInfo->target())); } else { ui->target->hide(); ui->targetLabel->hide(); } if(fileInfo->isDir() && fileInfo->isNative()) { // all files are native dirs connect(ui->iconButton, &QAbstractButton::clicked, this, &FilePropsDialog::onIconButtonclicked); } } // end if(singleType) else { // not singleType, multiple files are selected at the same time ui->fileType->setText(tr("Files of different types")); ui->target->hide(); ui->targetLabel->hide(); } // FIXME: check if all files has the same parent dir, mtime, or atime if(singleFile) { // only one file is selected auto parent_path = fileInfo->path().parent(); auto parent_str = parent_path ? parent_path.displayName(): nullptr; ui->fileName->setText(fileInfo->displayName()); if(parent_str) { ui->location->setText(parent_str.get()); } else { ui->location->clear(); } auto mtime = QDateTime::fromMSecsSinceEpoch(fileInfo->mtime() * 1000); ui->lastModified->setText(mtime.toString(Qt::SystemLocaleShortDate)); auto atime = QDateTime::fromMSecsSinceEpoch(fileInfo->atime() * 1000); ui->lastAccessed->setText(atime.toString(Qt::SystemLocaleShortDate)); } else { ui->fileName->setText(tr("Multiple Files")); ui->fileName->setEnabled(false); } initApplications(); // init applications combo box // calculate total file sizes fileSizeTimer = new QTimer(this); connect(fileSizeTimer, &QTimer::timeout, this, &FilePropsDialog::onFileSizeTimerTimeout); fileSizeTimer->start(600); connect(totalSizeJob, &Fm::TotalSizeJob::finished, this, &FilePropsDialog::onDeepCountJobFinished, Qt::BlockingQueuedConnection); totalSizeJob->setAutoDelete(true); totalSizeJob->runAsync(); } void FilePropsDialog::onDeepCountJobFinished() { onFileSizeTimerTimeout(); // update file size display totalSizeJob = nullptr; // stop the timer if(fileSizeTimer) { fileSizeTimer->stop(); delete fileSizeTimer; fileSizeTimer = nullptr; } } void FilePropsDialog::onFileSizeTimerTimeout() { if(totalSizeJob && !totalSizeJob->isCancelled()) { // FIXME: // OMG! It's really unbelievable that Qt developers only implement // QObject::tr(... int n). GNU gettext developers are smarter and // they use unsigned long instead of int. // We cannot use Qt here to handle plural forms. So sad. :-( QString str = Fm::formatFileSize(totalSizeJob->totalSize(), fm_config->si_unit) % QString(" (%1 B)").arg(totalSizeJob->totalSize()); // tr(" (%n) byte(s)", "", deepCountJob->total_size); ui->fileSize->setText(str); str = Fm::formatFileSize(totalSizeJob->totalOnDiskSize(), fm_config->si_unit) % QString(" (%1 B)").arg(totalSizeJob->totalOnDiskSize()); // tr(" (%n) byte(s)", "", deepCountJob->total_ondisk_size); ui->onDiskSize->setText(str); } } void FilePropsDialog::onIconButtonclicked() { QString iconDir; QString iconThemeName = QIcon::themeName(); QStringList icons = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "icons", QStandardPaths::LocateDirectory); for (QStringList::ConstIterator it = icons.constBegin(); it != icons.constEnd(); ++it) { QString iconThemeFolder = *it + '/' + iconThemeName; if (QDir(iconThemeFolder).exists() && QFileInfo(iconThemeFolder).permission(QFileDevice::ReadUser)) { // give priority to the "places" folder const QString places = iconThemeFolder + QLatin1String("/places"); if (QDir(places).exists() && QFileInfo(places).permission(QFileDevice::ReadUser)) { iconDir = places; } else { iconDir = iconThemeFolder; } break; } } if(iconDir.isEmpty()) { iconDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "icons", QStandardPaths::LocateDirectory); if(iconDir.isEmpty()) { return; } } const QString iconPath = QFileDialog::getOpenFileName(this, tr("Select an icon"), iconDir, tr("Images (*.png *.xpm *.svg *.svgz )")); if(!iconPath.isEmpty()) { QStringList parts = iconPath.split("/", QString::SkipEmptyParts); if(!parts.isEmpty()) { QString iconName = parts.at(parts.count() - 1); int ln = iconName.lastIndexOf("."); if(ln > -1) { iconName.remove(ln, iconName.length() - ln); customIcon = QIcon::fromTheme(iconName); ui->iconButton->setIcon(customIcon); } } } } void FilePropsDialog::accept() { // applications if(mimeType && ui->openWith->isChanged()) { auto currentApp = ui->openWith->selectedApp(); g_app_info_set_as_default_for_type(currentApp.get(), mimeType->name(), nullptr); } // check if chown or chmod is needed uid_t newUid = uidFromName(ui->owner->text()); gid_t newGid = gidFromName(ui->ownerGroup->text()); bool needChown = (newUid != INVALID_UID && newUid != uid) || (newGid != INVALID_GID && newGid != gid); int newOwnerPermSel = ui->ownerPerm->currentIndex(); int newGroupPermSel = ui->groupPerm->currentIndex(); int newOtherPermSel = ui->otherPerm->currentIndex(); Qt::CheckState newExecCheckState = ui->executable->checkState(); bool needChmod = ((newOwnerPermSel != ownerPermSel) || (newGroupPermSel != groupPermSel) || (newOtherPermSel != otherPermSel) || (newExecCheckState != execCheckState)); if(needChmod || needChown) { FileOperation* op = new FileOperation(FileOperation::ChangeAttr, fileInfos_.paths()); if(needChown) { // don't do chown if new uid/gid and the original ones are actually the same. if(newUid == uid) { newUid = INVALID_UID; } if(newGid == gid) { newGid = INVALID_GID; } op->setChown(newUid, newGid); } if(needChmod) { mode_t newMode = 0; mode_t newModeMask = 0; // FIXME: we need to make sure that folders with "r" permission also have "x" // at the same time. Otherwise, it's not able to browse the folder later. if(newOwnerPermSel != ownerPermSel && newOwnerPermSel != ACCESS_NO_CHANGE) { // owner permission changed newModeMask |= (S_IRUSR | S_IWUSR); // affect user bits if(newOwnerPermSel == ACCESS_READ_ONLY) { newMode |= S_IRUSR; } else if(newOwnerPermSel == ACCESS_READ_WRITE) { newMode |= (S_IRUSR | S_IWUSR); } } if(newGroupPermSel != groupPermSel && newGroupPermSel != ACCESS_NO_CHANGE) { qDebug("newGroupPermSel: %d", newGroupPermSel); // group permission changed newModeMask |= (S_IRGRP | S_IWGRP); // affect group bits if(newGroupPermSel == ACCESS_READ_ONLY) { newMode |= S_IRGRP; } else if(newGroupPermSel == ACCESS_READ_WRITE) { newMode |= (S_IRGRP | S_IWGRP); } } if(newOtherPermSel != otherPermSel && newOtherPermSel != ACCESS_NO_CHANGE) { // other permission changed newModeMask |= (S_IROTH | S_IWOTH); // affect other bits if(newOtherPermSel == ACCESS_READ_ONLY) { newMode |= S_IROTH; } else if(newOtherPermSel == ACCESS_READ_WRITE) { newMode |= (S_IROTH | S_IWOTH); } } if(newExecCheckState != execCheckState && newExecCheckState != Qt::PartiallyChecked) { // executable state changed newModeMask |= (S_IXUSR | S_IXGRP | S_IXOTH); if(newExecCheckState == Qt::Checked) { newMode |= (S_IXUSR | S_IXGRP | S_IXOTH); } } op->setChmod(newMode, newModeMask); if(hasDir) { // if there are some dirs in our selected files QMessageBox::StandardButton r = QMessageBox::question(this, tr("Apply changes"), tr("Do you want to recursively apply these changes to all files and sub-folders?"), QMessageBox::Yes | QMessageBox::No); if(r == QMessageBox::Yes) { op->setRecursiveChattr(true); } } } op->setAutoDestroy(true); op->run(); } // Renaming if(singleFile) { QString new_name = ui->fileName->text(); if(fileInfo->displayName() != new_name) { auto path = fileInfo->path(); auto parent_path = path.parent(); auto dest = parent_path.child(new_name.toLocal8Bit().constData()); Fm::GErrorPtr err; if(!g_file_move(path.gfile().get(), dest.gfile().get(), GFileCopyFlags(G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NO_FALLBACK_FOR_MOVE | G_FILE_COPY_NOFOLLOW_SYMLINKS), nullptr, nullptr, nullptr, &err)) { QMessageBox::critical(this, QObject::tr("Error"), err.message()); } } } // Custom (folder) icon if(!customIcon.isNull()) { bool reloadNeeded(false); QString iconNamne = customIcon.name(); for(auto& fi: fileInfos_) { std::shared_ptr icon = fi->icon(); if (!fi->icon() || fi->icon()->qicon().name() != iconNamne) { auto dot_dir = CStrPtr{g_build_filename(fi->path().localPath().get(), ".directory", nullptr)}; GKeyFile* kf = g_key_file_new(); g_key_file_set_string(kf, "Desktop Entry", "Icon", iconNamne.toLocal8Bit().constData()); Fm::GErrorPtr err; if (!g_key_file_save_to_file(kf, dot_dir.get(), &err)) { QMessageBox::critical(this, QObject::tr("Custom Icon Error"), err.message()); } else { reloadNeeded = true; } g_key_file_free(kf); } } if(reloadNeeded) { // since there can be only one parent dir, only one reload is needed auto parent = fileInfo->path().parent(); if(parent.isValid()) { auto folder = Fm::Folder::fromPath(parent); if(folder->isLoaded()) { folder->reload(); } } } } QDialog::accept(); } void FilePropsDialog::initOwner() { if(allNative) { if(uid != DIFFERENT_UIDS) { ui->owner->setText(uidToName(uid)); } if(gid != DIFFERENT_GIDS) { ui->ownerGroup->setText(gidToName(gid)); } if(geteuid() != 0) { // on local filesystems, only root can do chown. ui->owner->setEnabled(false); ui->ownerGroup->setEnabled(false); } } } } // namespace Fm