diff --git a/YUViewApp/src/yuviewapp.cpp b/YUViewApp/src/yuviewapp.cpp index 2eaf6e697..1a2473a92 100644 --- a/YUViewApp/src/yuviewapp.cpp +++ b/YUViewApp/src/yuviewapp.cpp @@ -34,6 +34,7 @@ #include "common/typedef.h" #include "ui/YUViewApplication.h" +#include "common/YUViewInstanceInfo.h" int main(int argc, char *argv[]) { @@ -45,7 +46,11 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents,false); qRegisterMetaType("recacheIndicator"); - + qRegisterMetaType("YUViewInstanceInfo"); + qRegisterMetaType("YUViewInstanceInfoList"); + qRegisterMetaTypeStreamOperators("YUViewInstanceInfo"); + qRegisterMetaTypeStreamOperators("YUViewInstanceInfoList"); + YUViewApplication app(argc, argv); return app.returnCode; diff --git a/YUViewLib/src/common/EnumMapper.h b/YUViewLib/src/common/EnumMapper.h index c58c3f4ce..4992a0081 100644 --- a/YUViewLib/src/common/EnumMapper.h +++ b/YUViewLib/src/common/EnumMapper.h @@ -36,6 +36,7 @@ #include #include #include +#include /* This class implement mapping of "enum class" values to and from names (string). */ diff --git a/YUViewLib/src/common/YUViewInstanceInfo.cpp b/YUViewLib/src/common/YUViewInstanceInfo.cpp new file mode 100644 index 000000000..a9f58099d --- /dev/null +++ b/YUViewLib/src/common/YUViewInstanceInfo.cpp @@ -0,0 +1,294 @@ +#include "YUViewInstanceInfo.h" +#include +#include +#include + + +const QString YUViewInstanceInfo::instanceInfoKey = QString("YUViewInstances"); + + +QDataStream& operator<<(QDataStream &ds, const YUViewInstanceInfo &inObj) +{ + ds << inObj.uuid; + // conversion to string should not be necessary. + // but without I get ambiguous overload for operator + ds << QString::number(inObj.pid); + ds << inObj.compressedPlaylist; + return ds; +} + +QDataStream& operator>>(QDataStream &ds, YUViewInstanceInfo &outObj) +{ + ds >> outObj.uuid; + QString tmp; + ds >> tmp; + outObj.pid = tmp.toLongLong(); + ds >> outObj.compressedPlaylist; + return ds; +} + +QDataStream& operator<<(QDataStream &ds, const YUViewInstanceInfoList &inObj) +{ + const qint64 list_length = inObj.length(); + // conversion to string should not be necessary. + // but without I get ambiguous overload for operator + ds << QString::number(list_length); + for( auto instance : inObj ) + { + ds << instance; + } + return ds; +} + +QDataStream& operator>>(QDataStream &ds, YUViewInstanceInfoList &outObj) +{ + outObj.clear(); + QString tmp; + ds >> tmp; + const qint64 list_length = tmp.toLongLong(); + for(unsigned i = 0; i> tmp; + outObj.push_back(tmp); + } + return ds; +} + +void YUViewInstanceInfo::initializeAsNewInstance() +{ + QSettings settings; + uuid = QUuid::createUuid(); + pid = QCoreApplication::applicationPid(); + // append instance to recorded ones + YUViewInstanceInfoList listOfQSettingInstances = YUViewInstanceInfo::getYUViewInstancesInQSettings(); + listOfQSettingInstances.append(*this); + settings.setValue(instanceInfoKey, QVariant::fromValue(listOfQSettingInstances)); +} + +void YUViewInstanceInfo::autoSavePlaylist(const QByteArray &newCompressedPlaylist) +{ + QSettings settings; + // set new playlist + this->compressedPlaylist = newCompressedPlaylist; + + // change the one stored + YUViewInstanceInfoList listOfQSettingInstances = YUViewInstanceInfo::getYUViewInstancesInQSettings(); + QMutableListIterator it(listOfQSettingInstances); + while (it.hasNext()) + { + YUViewInstanceInfo otherInstance = it.next(); + if(this->uuid == otherInstance.uuid && + this->pid == otherInstance.pid ) + { + // already had a instance, replace its playlist + it.setValue(*this); + settings.setValue(instanceInfoKey, QVariant::fromValue(listOfQSettingInstances)); + return; + } + } + + // no instance yet, save new one + // should never be reached, as we create the instance in initializeAsNewInstance, + // thus there always should be one + listOfQSettingInstances.append(*this); + settings.setValue(instanceInfoKey, QVariant::fromValue(listOfQSettingInstances)); + return; +} + +void YUViewInstanceInfo::dropAutosavedPlaylist() +{ + // replace saved on with emtpy playlist + this->autoSavePlaylist(QByteArray()); +} + +void YUViewInstanceInfo::removeInstanceFromQSettings() +{ + QSettings settings; + + // find and remove current instance from stored ones + YUViewInstanceInfoList listOfQSettingInstances = YUViewInstanceInfo::getYUViewInstancesInQSettings(); + QMutableListIterator it(listOfQSettingInstances); + while (it.hasNext()) + { + YUViewInstanceInfo otherInstance = it.next(); + if(this->uuid == otherInstance.uuid && + this->pid == otherInstance.pid ) + { + // found it, now remove it + it.remove(); + settings.setValue(instanceInfoKey, QVariant::fromValue(listOfQSettingInstances)); + return; + } + } +} + +YUViewInstanceInfo YUViewInstanceInfo::getAutosavedPlaylist() +{ + QSettings settings; + QList listOfPids = YUViewInstanceInfo::getRunningYUViewInstances(); + YUViewInstanceInfoList listOfQSettingInstances = YUViewInstanceInfo::getYUViewInstancesInQSettings(); + + YUViewInstanceInfo candidateForRestore; + QMutableListIterator it(listOfQSettingInstances); + while (it.hasNext()) + { + YUViewInstanceInfo instanceInSettings = it.next(); + bool still_running = false; + // get known instances, if instance no longer runs, remove it from QSettings + for( auto pid_running : listOfPids ) + { + if( pid_running == instanceInSettings.pid ) still_running = true; + } + if(!still_running) + { + // found an instance which is no longer running: candidate for restore + candidateForRestore = instanceInSettings; + it.remove(); + settings.setValue(instanceInfoKey, QVariant::fromValue(listOfQSettingInstances)); + break; // keep playlist from potential other crashed instances for other new instances + } + } + + return candidateForRestore; +} + +void YUViewInstanceInfo::cleanupRecordedInstances() +{ + QSettings settings; + QList listOfPids = YUViewInstanceInfo::getRunningYUViewInstances(); + YUViewInstanceInfoList listOfQSettingInstances = YUViewInstanceInfo::getYUViewInstancesInQSettings(); + + QMutableListIterator it(listOfQSettingInstances); + while (it.hasNext()) + { + YUViewInstanceInfo instanceInSettings = it.next(); + bool still_running = false; + // get known instances, if instance no longer runs, remove it from QSettings + for( auto pid_running : listOfPids ) + { + if( pid_running == instanceInSettings.pid ) still_running = true; + } + if(!still_running) + { + // can remove this one + it.remove(); + } + } + + settings.setValue(instanceInfoKey, QVariant::fromValue(listOfQSettingInstances)); +} + +bool YUViewInstanceInfo::isValid() +{ + return !uuid.isNull(); +} + +QList YUViewInstanceInfo::getRunningYUViewInstances() +{ + + QList listOfPids; + + // code for getting process ids adopted from https://www.qtcentre.org/threads/44489-Get-Process-ID-for-a-running-application + // also mentions checking /proc. However there is no proc on macos, while pgrep should be available. + +#if defined(Q_OS_WIN) + // Get the list of process identifiers. + DWORD aProcesses[1024], cbNeeded, cProcesses; + unsigned int i; + + if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) + { + return 0; + } + + // Calculate how many process identifiers were returned. + cProcesses = cbNeeded / sizeof(DWORD); + + // Search for a matching name for each process + for (i = 0; i < cProcesses; i++) + { + if (aProcesses[i] != 0) + { + char szProcessName[MAX_PATH] = {0}; + + DWORD processID = aProcesses[i]; + + // Get a handle to the process. + HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ, + FALSE, processID); + + // Get the process name + if (NULL != hProcess) + { + HMODULE hMod; + DWORD cbNeeded; + + if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) + { + GetModuleBaseNameA(hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(char)); + } + + // Release the handle to the process. + CloseHandle(hProcess); + + if (*szProcessName != 0 && strcmp(processName, szProcessName) == 0) + { + listOfPids.append(QString::number(processID)); + } + } + } + } + + return listOfPids.count(); + +#else + + // Run pgrep, which looks through the currently running processses and lists the process IDs + // which match the selection criteria to stdout. + QProcess process; + process.start("pgrep", QStringList() << "YUView"); + process.waitForReadyRead(); + + QByteArray bytes = process.readAllStandardOutput(); + + process.terminate(); + process.waitForFinished(); + process.kill(); + + // Output is something like "2472\n2323" for multiple instances + if (bytes.isEmpty()) + return QList(); + + // Remove trailing CR + if (bytes.endsWith("\n")) + bytes.resize(bytes.size() - 1); + + QStringList strListOfPids = QString(bytes).split("\n"); + for( auto strPid : strListOfPids ) + { + listOfPids.append(strPid.toLongLong()); + } +#endif + + return listOfPids; +} + +YUViewInstanceInfoList YUViewInstanceInfo::getYUViewInstancesInQSettings() +{ + QSettings settings; + YUViewInstanceInfoList instancesInQSettings; + if (settings.contains(instanceInfoKey)) + { + instancesInQSettings = settings.value(instanceInfoKey).value(); + } + return instancesInQSettings; +} + +QByteArray YUViewInstanceInfo::getCompressedPlaylist() const +{ + return compressedPlaylist; +} + + diff --git a/YUViewLib/src/common/YUViewInstanceInfo.h b/YUViewLib/src/common/YUViewInstanceInfo.h new file mode 100644 index 000000000..0f7cdf40f --- /dev/null +++ b/YUViewLib/src/common/YUViewInstanceInfo.h @@ -0,0 +1,52 @@ +#ifndef YUVIEWINSTANCEINFO_H +#define YUVIEWINSTANCEINFO_H + +#include +#include +#include +#include + +class YUViewInstanceInfo; +typedef QList YUViewInstanceInfoList; + +class YUViewInstanceInfo +{ +public: + YUViewInstanceInfo() = default; + ~YUViewInstanceInfo() = default; + YUViewInstanceInfo(const YUViewInstanceInfo &) = default; + YUViewInstanceInfo &operator=(const YUViewInstanceInfo &) = default; + + void initializeAsNewInstance(); + void autoSavePlaylist(const QByteArray &newCompressedPlaylist); + void dropAutosavedPlaylist(); + void removeInstanceFromQSettings(); + YUViewInstanceInfo getAutosavedPlaylist(); + void cleanupRecordedInstances(); + bool isValid(); + + QByteArray getCompressedPlaylist() const; + + friend QDataStream& (operator<<) (QDataStream &ds, const YUViewInstanceInfo &inObj); + friend QDataStream& (operator>>) (QDataStream &ds, YUViewInstanceInfo &outObj); +private: + static QList getRunningYUViewInstances(); + static YUViewInstanceInfoList getYUViewInstancesInQSettings(); + + qint64 pid; + QUuid uuid; + QByteArray compressedPlaylist; + + static const QString instanceInfoKey; // key used for QSettings +}; + +QDataStream& operator<<(QDataStream &ds, const YUViewInstanceInfo &inObj); +QDataStream& operator>>(QDataStream &ds, YUViewInstanceInfo &outObj); +QDataStream& operator<<(QDataStream &ds, const YUViewInstanceInfoList &inObj); +QDataStream& operator>>(QDataStream &ds, YUViewInstanceInfoList &outObj); + +Q_DECLARE_METATYPE(YUViewInstanceInfo); +Q_DECLARE_METATYPE(YUViewInstanceInfoList); + + +#endif // YUVIEWINSTANCEINFO_H diff --git a/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp b/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp index bc2d3ea30..77ad489da 100644 --- a/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp +++ b/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp @@ -395,7 +395,8 @@ playlistItemCompressedVideo::newPlaylistItemCompressedVideo(const YUViewDomEleme const QString &playlistFilePath) { // Parse the DOM element. It should have all values of a playlistItemRawCodedVideo - auto absolutePath = root.findChildValue("absolutePath"); + QUrl absoluteUrl = root.findChildValue("absolutePath"); + auto absolutePath = absoluteUrl.toLocalFile(); auto relativePath = root.findChildValue("relativePath"); int displaySignal = root.findChildValue("displayComponent").toInt(); diff --git a/YUViewLib/src/playlistitem/playlistItemImageFile.cpp b/YUViewLib/src/playlistitem/playlistItemImageFile.cpp index 6fd6476d8..fa64c7241 100644 --- a/YUViewLib/src/playlistitem/playlistItemImageFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemImageFile.cpp @@ -109,7 +109,8 @@ playlistItemImageFile::newplaylistItemImageFile(const YUViewDomElement &root, const QString & playlistFilePath) { // Parse the DOM element. It should have all values of a playlistItemImageFile - QString absolutePath = root.findChildValue("absolutePath"); + QUrl absoluteUrl = root.findChildValue("absolutePath"); + auto absolutePath = absoluteUrl.toLocalFile(); QString relativePath = root.findChildValue("relativePath"); // check if file with absolute path exists, otherwise check relative path diff --git a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp index b48adf277..c68c2d3ad 100644 --- a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp @@ -469,7 +469,8 @@ playlistItemRawFile *playlistItemRawFile::newplaylistItemRawFile(const YUViewDom const QString &playlistFilePath) { // Parse the DOM element. It should have all values of a playlistItemRawFile - auto absolutePath = root.findChildValue("absolutePath"); + QUrl absoluteUrl = root.findChildValue("absolutePath"); + auto absolutePath = absoluteUrl.toLocalFile(); auto relativePath = root.findChildValue("relativePath"); auto type = root.findChildValue("type"); diff --git a/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp b/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp index a0298a7d2..19da98b17 100644 --- a/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp @@ -104,7 +104,8 @@ playlistItemStatisticsFile *playlistItemStatisticsFile::newplaylistItemStatistic const YUViewDomElement &root, const QString &playlistFilePath, OpenMode openMode) { // Parse the DOM element. It should have all values of a playlistItemStatisticsFile - auto absolutePath = root.findChildValue("absolutePath"); + QUrl absoluteUrl = root.findChildValue("absolutePath"); + auto absolutePath = absoluteUrl.toLocalFile(); auto relativePath = root.findChildValue("relativePath"); // check if file with absolute path exists, otherwise check relative path diff --git a/YUViewLib/src/ui/mainwindow.cpp b/YUViewLib/src/ui/mainwindow.cpp index c373caf91..0ad1857ca 100644 --- a/YUViewLib/src/ui/mainwindow.cpp +++ b/YUViewLib/src/ui/mainwindow.cpp @@ -197,18 +197,22 @@ MainWindow::MainWindow(bool useAlternativeSources, QWidget *parent) : QMainWindo // Give the playlist a pointer to the state handler so it can save the states ti playlist ui.playlistTreeWidget->setViewStateHandler(&stateHandler); - if (ui.playlistTreeWidget->isAutosaveAvailable()) + QStringList crashedInstances; + YUViewInstanceInfo autosaveCandidateInstance = ui.playlistTreeWidget->getInstanceInfo().getAutosavedPlaylist(); + if( autosaveCandidateInstance.isValid()) { QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Restore Playlist", tr("It looks like YUView crashed the last time you used it. We are " "sorry about that. However, we have an autosave of the playlist " - "you were working with. Do you want to resotre this playlist?\n"), + "you were working with. Do you want to restore this playlist? If" + " you had more than one YUView instance, opening more instances " + "will allow you to restore other playlists as well.\n"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (resBtn == QMessageBox::Yes) - ui.playlistTreeWidget->loadAutosavedPlaylist(); + ui.playlistTreeWidget->loadAutosavedPlaylist(autosaveCandidateInstance); else ui.playlistTreeWidget->dropAutosavedPlaylist(); } diff --git a/YUViewLib/src/ui/widgets/PlaylistTreeWidget.cpp b/YUViewLib/src/ui/widgets/PlaylistTreeWidget.cpp index 384b56efa..da730b574 100644 --- a/YUViewLib/src/ui/widgets/PlaylistTreeWidget.cpp +++ b/YUViewLib/src/ui/widgets/PlaylistTreeWidget.cpp @@ -46,6 +46,8 @@ #include #include #include +#include +#include #include "playlistitem/playlistItemCompressedVideo.h" #include "playlistitem/playlistItemContainer.h" @@ -148,6 +150,8 @@ PlaylistTreeWidget::PlaylistTreeWidget(QWidget *parent) : QTreeWidget(parent) header()->setSectionResizeMode(1, QHeaderView::Fixed); header()->setSectionResizeMode(0, QHeaderView::Stretch); + instanceInfo.initializeAsNewInstance(); + // This does not work here. Don't know why. Setting it every time a new item is added, however, // works. // header()->resizeSection(1, 10); @@ -163,9 +167,7 @@ PlaylistTreeWidget::~PlaylistTreeWidget() { // This is a conventional quit. Remove the automatically saved playlist. autosaveTimer.stop(); - QSettings settings; - if (settings.contains("Autosaveplaylist")) - settings.remove("Autosaveplaylist"); + instanceInfo.removeInstanceFromQSettings(); } playlistItem *PlaylistTreeWidget::getDropTarget(const QPoint &pos) const @@ -1011,29 +1013,17 @@ void PlaylistTreeWidget::updateSettings() } } -bool PlaylistTreeWidget::isAutosaveAvailable() +void PlaylistTreeWidget::loadAutosavedPlaylist(const YUViewInstanceInfo &crashedInstance) { QSettings settings; - return settings.contains("Autosaveplaylist"); -} - -void PlaylistTreeWidget::loadAutosavedPlaylist() -{ - QSettings settings; - if (!settings.contains("Autosaveplaylist")) - return; - - QByteArray compressedPlaylist = settings.value("Autosaveplaylist").toByteArray(); + QByteArray compressedPlaylist= crashedInstance.getCompressedPlaylist(); QByteArray uncompressedPlaylist = qUncompress(compressedPlaylist); loadPlaylistFromByteArray(uncompressedPlaylist, QDir::current().absolutePath()); - - dropAutosavedPlaylist(); } void PlaylistTreeWidget::dropAutosavedPlaylist() { - QSettings settings; - settings.remove("Autosaveplaylist"); + instanceInfo.cleanupRecordedInstances(); } void PlaylistTreeWidget::duplicateSelectedItems() @@ -1081,17 +1071,21 @@ void PlaylistTreeWidget::autoSavePlaylist() if (topLevelItemCount() == 0) { // Empty playlist - if (settings.contains("Autosaveplaylist")) - settings.remove("Autosaveplaylist"); + instanceInfo.dropAutosavedPlaylist(); } else { QString playlistAsString = getPlaylistString(QDir::current()); QByteArray compressedPlaylist = qCompress(playlistAsString.toLatin1()); - settings.setValue("Autosaveplaylist", compressedPlaylist); + instanceInfo.autoSavePlaylist(compressedPlaylist); } } +YUViewInstanceInfo PlaylistTreeWidget::getInstanceInfo() const +{ + return instanceInfo; +} + void PlaylistTreeWidget::startAutosaveTimer() { autosaveTimer.setTimerType(Qt::VeryCoarseTimer); diff --git a/YUViewLib/src/ui/widgets/PlaylistTreeWidget.h b/YUViewLib/src/ui/widgets/PlaylistTreeWidget.h index 8a2277210..ee87faaaa 100644 --- a/YUViewLib/src/ui/widgets/PlaylistTreeWidget.h +++ b/YUViewLib/src/ui/widgets/PlaylistTreeWidget.h @@ -40,6 +40,7 @@ #include "playlistitem/playlistItem.h" #include "common/typedef.h" #include "ui/ViewStateHandler.h" +#include "common/YUViewInstanceInfo.h" class QDomElement; @@ -87,15 +88,16 @@ class PlaylistTreeWidget : public QTreeWidget // Update the caching status of all items void updateCachingStatus() { emit dataChanged(QModelIndex(), QModelIndex()); }; - bool isAutosaveAvailable(); - void loadAutosavedPlaylist(); + void loadAutosavedPlaylist(const YUViewInstanceInfo &crashedInstance); void dropAutosavedPlaylist(); void startAutosaveTimer(); + YUViewInstanceInfo getInstanceInfo() const; + public slots: void savePlaylistToFile(); - // Slots for going to the next item. WrapAround = if the current item is the last one in the list, + // Slots for going to the next item. WrapAround = if the current item is the last one in the list, // goto the first item in the list. Return if there is a next item. // callByPlayback is true if this call is caused by the playback function going to the next item. void onSelectNextItem(bool) { this->selectNextItem(false, false); } @@ -206,4 +208,6 @@ protected slots: void autoSavePlaylist(); QTimer autosaveTimer; + + YUViewInstanceInfo instanceInfo; };