Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions YACReaderLibrary/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ qt_add_executable(YACReaderLibrary WIN32
add_library_dialog.cpp
rename_library_dialog.h
rename_library_dialog.cpp
organize_files_dialog.h
organize_files_dialog.cpp
organize_files_preview_dialog.h
organize_files_preview_dialog.cpp
properties_dialog.h
properties_dialog.cpp
options_dialog.h
Expand Down
205 changes: 205 additions & 0 deletions YACReaderLibrary/library_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
#include <QApplication>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileIconProvider>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QHash>
#include <QHeaderView>
#include <QInputDialog>
#include <QLabel>
Expand All @@ -17,6 +20,7 @@
#include <QMessageBox>
#include <QProcess>
#include <QProgressDialog>
#include <QSet>
#include <QSettings>
#include <QShowEvent>
#include <QSplitter>
Expand Down Expand Up @@ -64,6 +68,8 @@
#include "library_creator.h"
#include "no_libraries_widget.h"
#include "options_dialog.h"
#include "organize_files_dialog.h"
#include "organize_files_preview_dialog.h"
#include "package_manager.h"
#include "properties_dialog.h"
#include "reading_list_item.h"
Expand Down Expand Up @@ -1505,6 +1511,7 @@ void LibraryWindow::showComicsContextMenu(const QPoint &point, bool showFullScre
menu.addAction(actions.saveCoversToAction);
menu.addSeparator();
menu.addAction(actions.openContainingFolderComicAction);
menu.addAction(actions.organizeComicsFilesAction);
menu.addAction(actions.updateCurrentFolderAction);
menu.addSeparator();
menu.addAction(actions.resetComicRatingAction);
Expand Down Expand Up @@ -2629,6 +2636,203 @@ void LibraryWindow::openContainingFolder()
QDesktopServices::openUrl(QUrl("file:///" + path, QUrl::TolerantMode));
}

static void collectComicsRecursively(qulonglong libraryId, qulonglong folderId, QList<ComicDB> &out)
{
const auto comics = DBHelper::getFolderComicsFromLibrary(libraryId, folderId);
for (auto *item : comics) {
if (auto *comic = static_cast<ComicDB *>(item))
out.append(*comic);
}
qDeleteAll(comics);

const auto subfolders = DBHelper::getFolderSubfoldersFromLibrary(libraryId, folderId);
for (auto *item : subfolders) {
collectComicsRecursively(libraryId, item->id, out);
}
qDeleteAll(subfolders);
}

static void removeEmptyDirs(const QString &basePath)
{
QDir base(basePath);
const auto entries = base.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString &entry : entries) {
const QString childPath = base.absoluteFilePath(entry);
removeEmptyDirs(childPath);
QDir().rmdir(childPath);
}
}

static QString uniqueDestination(const QString &destination, const QSet<QString> &taken)
{
if (!QFileInfo::exists(destination) && !taken.contains(destination))
return destination;

const QFileInfo destInfo(destination);
const QString dir = destInfo.absolutePath();
const QString base = destInfo.completeBaseName();
const QString suffix = destInfo.suffix().isEmpty() ? QString() : QStringLiteral(".") + destInfo.suffix();
int counter = 1;
QString candidate;
do {
candidate = QDir::cleanPath(dir + QStringLiteral("/") + base + QStringLiteral(" (") + QString::number(counter++) + QStringLiteral(")") + suffix);
} while (QFileInfo::exists(candidate) || taken.contains(candidate));
return candidate;
}

void LibraryWindow::organizeFiles()
{
const QModelIndex sourceIndex = getCurrentFolderIndex();
if (!sourceIndex.isValid())
return;

const auto libraryId = libraries.getId(selectedLibrary->currentText());
const auto folder = foldersModel->getFolder(sourceIndex);
const QString folderAbsolutePath = QDir::cleanPath(currentPath() + foldersModel->getFolderPath(sourceIndex));

QList<ComicDB> comics;
collectComicsRecursively(libraryId, folder.id, comics);

if (comics.isEmpty()) {
QMessageBox::information(this, tr("Organize files"), tr("This folder does not contain any comics to organize."));
return;
}

if (runOrganizeFilesFlow(comics, folderAbsolutePath))
updateFolder(sourceIndex);
}

void LibraryWindow::organizeComicsFiles()
{
const QModelIndexList indexList = getSelectedComics();
if (indexList.isEmpty())
return;

const QList<ComicDB> comics = comicsModel->getComics(indexList);
if (comics.isEmpty())
return;

const QModelIndex folderIndex = getCurrentFolderIndex();
const QString folderAbsolutePath = folderIndex.isValid()
? QDir::cleanPath(currentPath() + foldersModel->getFolderPath(folderIndex))
: QDir::cleanPath(currentPath());

if (runOrganizeFilesFlow(comics, folderAbsolutePath)) {
if (folderIndex.isValid())
updateFolder(folderIndex);
else
reloadCurrentFolderComicsContent();
}
}

bool LibraryWindow::runOrganizeFilesFlow(const QList<ComicDB> &comics, const QString &cleanupPath)
{
const QString libraryRoot = QDir::cleanPath(currentPath());

OrganizeFilesDialog dialog(libraryRoot, cleanupPath, settings, this);
if (dialog.exec() != QDialog::Accepted)
return false;

const QString pattern = dialog.formatPattern();
if (pattern.trimmed().isEmpty())
return false;

using Move = OrganizeFilesPreviewDialog::Move;
QList<Move> moves;
QSet<QString> takenDestinations;
const QDir destinationRoot(dialog.relativeToRoot() ? libraryRoot : cleanupPath);

QHash<QString, int> seriesNumberWidth;
for (const ComicDB &comic : comics) {
const QString series = comic.info.series.toString().trimmed();
bool ok = false;
const int value = comic.info.number.toString().trimmed().toInt(&ok);
if (!ok)
continue;
const int width = QString::number(value).size();
int &current = seriesNumberWidth[series];
current = std::max(current, width);
}

for (const ComicDB &comic : comics) {
const QString source = QDir::cleanPath(libraryRoot + comic.path);
const QFileInfo sourceInfo(source);
if (!sourceInfo.exists())
continue;

const QString extension = sourceInfo.suffix().isEmpty() ? QString() : QStringLiteral(".") + sourceInfo.suffix();

const int numberPadding = seriesNumberWidth.value(comic.info.series.toString().trimmed(), 0);

const QString relative = OrganizeFilesDialog::buildRelativePath(pattern,
comic.info.publisher.toString(),
comic.info.series.toString(),
comic.info.number.toString(),
comic.info.title.toString(),
comic.info.volume.toString(),
comic.info.year.toString(),
extension,
numberPadding);

QString destination = QDir::cleanPath(destinationRoot.absoluteFilePath(relative));
if (destination == QDir::cleanPath(source))
continue;

destination = uniqueDestination(destination, takenDestinations);
takenDestinations.insert(destination);

moves.append({ source, destination });
}

if (moves.isEmpty()) {
QMessageBox::information(this, tr("Organize files"), tr("All files are already organized according to this format."));
return false;
}

OrganizeFilesPreviewDialog preview(destinationRoot.absolutePath(), libraryRoot, moves, this);
if (preview.exec() != QDialog::Accepted)
return false;

QList<Move> finalMoves;
QSet<QString> finalTaken;
for (const Move &move : preview.moves()) {
if (QDir::cleanPath(move.destination) == QDir::cleanPath(move.source))
continue;
const QString destination = uniqueDestination(move.destination, finalTaken);
finalTaken.insert(destination);
finalMoves.append({ move.source, destination });
}

if (finalMoves.isEmpty())
return false;

int moved = 0;
QStringList failures;
for (const Move &move : finalMoves) {
const QString targetDir = QFileInfo(move.destination).absolutePath();
if (!QDir().mkpath(targetDir)) {
failures << move.source;
continue;
}
if (QFile::rename(move.source, move.destination))
moved++;
else
failures << move.source;
}

removeEmptyDirs(cleanupPath);

if (!failures.isEmpty()) {
QMessageBox::warning(this, tr("Organize files"),
tr("%1 of %2 file(s) were moved. %3 file(s) could not be moved.")
.arg(moved)
.arg(finalMoves.size())
.arg(failures.size()));
}

return moved > 0;
}

void LibraryWindow::setFolderAsNotCompleted()
{
// foldersModel->updateFolderCompletedStatus(foldersView->selectionModel()->selectedRows(),false);
Expand Down Expand Up @@ -2983,6 +3187,7 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point)
QMenu menu;

menu.addAction(actions.openContainingFolderAction);
menu.addAction(actions.organizeFilesAction);
menu.addAction(actions.updateFolderAction);
menu.addSeparator(); //-------------------------------
menu.addAction(actions.rescanXMLFromCurrentFolderAction);
Expand Down
3 changes: 3 additions & 0 deletions YACReaderLibrary/library_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ public slots:
void startLibraryRepair(bool removeStaleLock);
// void deleteLibrary();
void openContainingFolder();
void organizeFiles();
void organizeComicsFiles();
void setFolderAsNotCompleted();
void setFolderAsCompleted();
void setFolderAsRead();
Expand Down Expand Up @@ -329,6 +331,7 @@ public slots:
void reloadCurrentFolderComicsContent();
void reloadAfterCopyMove(const QModelIndex &mi);
QModelIndex getCurrentFolderIndex();
bool runOrganizeFilesFlow(const QList<ComicDB> &comics, const QString &cleanupPath);
void enableNeededActions();
void disableComicsActions(bool disabled);
void addFolderToCurrentIndex();
Expand Down
14 changes: 14 additions & 0 deletions YACReaderLibrary/library_window_actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti
openContainingFolderAction->setData(OPEN_CONTAINING_FOLDER_ACTION_YL);
openContainingFolderAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(OPEN_CONTAINING_FOLDER_ACTION_YL));

organizeFilesAction = new QAction(window);
organizeFilesAction->setText(tr("Organize files"));

setFolderAsNotCompletedAction = new QAction(window);
setFolderAsNotCompletedAction->setText(tr("Set as uncompleted"));
setFolderAsNotCompletedAction->setData(SET_FOLDER_AS_NOT_COMPLETED_ACTION_YL);
Expand Down Expand Up @@ -290,6 +293,9 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti
openContainingFolderComicAction->setData(OPEN_CONTAINING_FOLDER_COMIC_ACTION_YL);
openContainingFolderComicAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(OPEN_CONTAINING_FOLDER_COMIC_ACTION_YL));

organizeComicsFilesAction = new QAction(window);
organizeComicsFilesAction->setText(tr("Organize files"));

resetComicRatingAction = new QAction(window);
resetComicRatingAction->setText(tr("Reset comic rating"));
resetComicRatingAction->setData(RESET_COMIC_RATING_ACTION_YL);
Expand Down Expand Up @@ -401,6 +407,7 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti
// actions not asigned to any widget
window->addAction(saveCoversToAction);
window->addAction(openContainingFolderAction);
window->addAction(organizeFilesAction);
window->addAction(updateCurrentFolderAction);
window->addAction(resetComicRatingAction);
window->addAction(setFolderAsCompletedAction);
Expand All @@ -417,6 +424,7 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti
window->addAction(deleteMetadataAction);
window->addAction(rescanXMLFromCurrentFolderAction);
window->addAction(openContainingFolderComicAction);
window->addAction(organizeComicsFilesAction);
#ifndef Q_OS_MACOS
window->addAction(toggleFullScreenAction);
#endif
Expand Down Expand Up @@ -476,11 +484,13 @@ void LibraryWindowActions::createConnections(

// ContextMenus
QObject::connect(openContainingFolderComicAction, &QAction::triggered, window, &LibraryWindow::openContainingFolderComic);
QObject::connect(organizeComicsFilesAction, &QAction::triggered, window, &LibraryWindow::organizeComicsFiles);
QObject::connect(setFolderAsNotCompletedAction, &QAction::triggered, window, &LibraryWindow::setFolderAsNotCompleted);
QObject::connect(setFolderAsCompletedAction, &QAction::triggered, window, &LibraryWindow::setFolderAsCompleted);
QObject::connect(setFolderAsReadAction, &QAction::triggered, window, &LibraryWindow::setFolderAsRead);
QObject::connect(setFolderAsUnreadAction, &QAction::triggered, window, &LibraryWindow::setFolderAsUnread);
QObject::connect(openContainingFolderAction, &QAction::triggered, window, &LibraryWindow::openContainingFolder);
QObject::connect(organizeFilesAction, &QAction::triggered, window, &LibraryWindow::organizeFiles);
QObject::connect(setFolderCoverAction, &QAction::triggered, window, &LibraryWindow::setFolderCover);
QObject::connect(deleteCustomFolderCoverAction, &QAction::triggered, window, &LibraryWindow::deleteCustomFolderCover);

Expand Down Expand Up @@ -592,6 +602,7 @@ void LibraryWindowActions::setUpShortcutsManagement(EditShortcutsDialog *editSho
<< setMangaAction
<< setNormalAction
<< openContainingFolderComicAction
<< organizeComicsFilesAction
<< resetComicRatingAction
<< selectAllComicsAction
<< editSelectedComicsAction
Expand All @@ -610,6 +621,7 @@ void LibraryWindowActions::setUpShortcutsManagement(EditShortcutsDialog *editSho
<< expandAllNodesAction
<< colapseAllNodesAction
<< openContainingFolderAction
<< organizeFilesAction
<< setFolderAsNotCompletedAction
<< setFolderAsCompletedAction
<< setFolderAsReadAction
Expand Down Expand Up @@ -703,6 +715,7 @@ void LibraryWindowActions::disableComicsActions(bool disabled)
deleteComicsAction->setDisabled(disabled);
// context menu
openContainingFolderComicAction->setDisabled(disabled);
organizeComicsFilesAction->setDisabled(disabled);
resetComicRatingAction->setDisabled(disabled);

getInfoAction->setDisabled(disabled);
Expand Down Expand Up @@ -743,6 +756,7 @@ void LibraryWindowActions::disableFoldersActions(bool disabled)
colapseAllNodesAction->setDisabled(disabled);

openContainingFolderAction->setDisabled(disabled);
organizeFilesAction->setDisabled(disabled);

updateFolderAction->setDisabled(disabled);
rescanXMLFromCurrentFolderAction->setDisabled(disabled);
Expand Down
2 changes: 2 additions & 0 deletions YACReaderLibrary/library_window_actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class LibraryWindowActions
QAction *colapseAllNodesAction;

QAction *openContainingFolderAction;
QAction *organizeFilesAction;
QAction *saveCoversToAction;
//--
QAction *setFolderAsNotCompletedAction;
Expand All @@ -81,6 +82,7 @@ class LibraryWindowActions
QAction *deleteCustomFolderCoverAction;

QAction *openContainingFolderComicAction;
QAction *organizeComicsFilesAction;
QAction *setAsReadAction;
QAction *setAsNonReadAction;

Expand Down
Loading