/* Copyright (C) 2012 Sebastian Herbord. All rights reserved. This file is part of Mod Organizer. Mod Organizer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Mod Organizer 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ #ifndef DOWNLOADMANAGER_H #define DOWNLOADMANAGER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MOBase { class IPluginGame; } class NexusInterface; /*! * \brief manages downloading of files and provides progress information for gui elements **/ class DownloadManager : public MOBase::IDownloadManager { Q_OBJECT public: enum DownloadState { STATE_STARTED = 0, STATE_DOWNLOADING, STATE_CANCELING, STATE_PAUSING, STATE_CANCELED, STATE_PAUSED, STATE_ERROR, STATE_FETCHINGMODINFO, STATE_FETCHINGFILEINFO, STATE_NOFETCH, STATE_READY, STATE_INSTALLED, STATE_UNINSTALLED }; private: struct DownloadInfo { ~DownloadInfo() { delete m_FileInfo; } unsigned int m_DownloadID; QString m_FileName; QFile m_Output; QNetworkReply *m_Reply; QTime m_StartTime; qint64 m_PreResumeSize; int m_Progress; DownloadState m_State; int m_CurrentUrl; QStringList m_Urls; qint64 m_ResumePos; qint64 m_TotalSize; QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be valid elsewhere int m_Tries; bool m_ReQueried; quint32 m_TaskProgressId; MOBase::ModRepositoryFileInfo *m_FileInfo { nullptr }; bool m_Hidden; static DownloadInfo *createNew(const MOBase::ModRepositoryFileInfo *fileInfo, const QStringList &URLs); static DownloadInfo *createFromMeta(const QString &filePath, bool showHidden); /** * @brief rename the file * this will change the file name as well as the display name. It will automatically * append .unfinished to the name if this file is still being downloaded * @param newName the new name to setName * @param renameFile if true, the file is assumed to exist and renamed. If the file does not * yet exist, set this to false **/ void setName(QString newName, bool renameFile); unsigned int downloadID() { return m_DownloadID; } bool isPausedState(); QString currentURL(); private: static unsigned int s_NextDownloadID; private: DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false) {} }; public: /** * @brief constructor * * @param nexusInterface interface to use to retrieve information from the relevant nexus page * @param parent parent object **/ explicit DownloadManager(NexusInterface *nexusInterface, QObject *parent); ~DownloadManager(); /** * @brief determine if a download is currently in progress * * @return true if there is currently a download in progress **/ bool downloadsInProgress(); /** * @brief set the output directory to write to * * @param outputDirectory the new output directory **/ void setOutputDirectory(const QString &outputDirectory); /** * @return current download directory **/ QString getOutputDirectory() const { return m_OutputDirectory; } /** * @brief setPreferredServers set the list of preferred servers */ void setPreferredServers(const std::map &preferredServers); /** * @brief set the list of supported extensions * @param extensions list of supported extensions */ void setSupportedExtensions(const QStringList &extensions); /** * @brief sets whether hidden files are to be shown after all */ void setShowHidden(bool showHidden); /** * @brief download from an already open network connection * * @param reply the network reply to download from * @param fileInfo information about the file, like mod id, file id, version, ... * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again **/ bool addDownload(QNetworkReply *reply, const MOBase::ModRepositoryFileInfo *fileInfo); /** * @brief download from an already open network connection * * @param reply the network reply to download from * @param fileName the name to use for the file. This may be overridden by the name in the fileInfo-structure or if the http stream specifies a name * @param fileInfo information previously retrieved from the nexus network * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again **/ bool addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, int modID, int fileID = 0, const MOBase::ModRepositoryFileInfo *fileInfo = new MOBase::ModRepositoryFileInfo()); /** * @brief start a download using a nxm-link * * starts a download using a nxm-link. The download manager will first query the nexus * page for file information. * @param url a nxm link looking like this: nxm://skyrim/mods/1234/files/4711 * @todo the game name encoded into the link is currently ignored, all downloads are incorrectly assumed to be for the identified game **/ void addNXMDownload(const QString &url); /** * @brief retrieve the total number of downloads, both finished and unfinished including downloads from previous sessions * * @return total number of downloads **/ int numTotalDownloads() const; /** * @brief retrieve number of pending downloads (nexus downloads for which we don't know the name and url yet) * @return number of pending downloads */ int numPendingDownloads() const; /** * @brief retrieve the info of a pending download * @param index index of the pending download (index in the range [0, numPendingDownloads()[) * @return pair of modid, fileid */ std::pair getPendingDownload(int index); /** * @brief retrieve the full path to the download specified by index * * @param index the index to look up * @return absolute path of the file **/ QString getFilePath(int index) const; /** * @brief retrieve a descriptive name of the download specified by index * * @param index index of the file to look up * @return display name of the file **/ QString getDisplayName(int index) const; /** * @brief retrieve the filename of the download specified by index * * @param index index of the file to look up * @return name of the file **/ QString getFileName(int index) const; /** * @brief retrieve the file size of the download specified by index * * @param index index of the file to look up * @return size of the file (total size during download) */ qint64 getFileSize(int index) const; /** * @brief retrieve the creation time of the download specified by index * @param index index of the file to look up * @return size of the file (total size during download) */ QDateTime getFileTime(int index) const; /** * @brief retrieve the current progress of the download specified by index * * @param index index of the file to look up * @return progress of the download in percent (integer) **/ int getProgress(int index) const; /** * @brief retrieve the current state of the download * * retrieve the current state of the download. A download usually goes through * the following states: * started -> downloading -> fetching mod info -> fetching file info -> done * in case of downloads started via nxm-link, file information is fetched first * * @param index index of the file to look up * @return the download state **/ DownloadState getState(int index) const; /** * @param index index of the file to look up * @return true if the nexus information for this download is not complete **/ bool isInfoIncomplete(int index) const; /** * @brief retrieve the nexus mod id of the download specified by index * * @param index index of the file to look up * @return the nexus mod id **/ int getModID(int index) const; /** * @brief determine if the specified file is supposed to be hidden * @param index index of the file to look up * @return true if the specified file is supposed to be hidden */ bool isHidden(int index) const; /** * @brief retrieve all nexus info of the download specified by index * * @param index index of the file to look up * @return the nexus mod information **/ const MOBase::ModRepositoryFileInfo *getFileInfo(int index) const; /** * @brief mark a download as installed * * @param index index of the file to mark installed */ void markInstalled(int index); /** * @brief mark a download as uninstalled * * @param index index of the file to mark uninstalled */ void markUninstalled(int index); /** * @brief refreshes the list of downloads */ void refreshList(); /** * @brief Sort function for download servers * @param LHS * @param RHS * @return */ static bool ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS); virtual int startDownloadURLs(const QStringList &urls); virtual int startDownloadNexusFile(int modID, int fileID); virtual QString downloadPath(int id); /** * @brief retrieve a download index from the filename * @param fileName file to look up * @return index of that download or -1 if it wasn't found */ int indexByName(const QString &fileName) const; void pauseAll(); signals: void aboutToUpdate(); /** * @brief signals that the specified download has changed * * @param row the row that changed. This corresponds to the download index **/ void update(int row); /** * @brief signals the ui that a message should be displayed * * @param message the message to display **/ void showMessage(const QString &message); /** * @brief emitted whenever the state of a download changes * @param row the row that changed * @param state the new state */ void stateChanged(int row, DownloadManager::DownloadState state); /** * @brief emitted whenever a download completes successfully, reporting the download speed for the server used */ void downloadSpeed(const QString &serverName, int bytesPerSecond); /** * @brief emitted whenever a new download is added to the list */ void downloadAdded(); public slots: /** * @brief removes the specified download * * @param index index of the download to remove * @param deleteFile if true, the file will also be deleted from disc, otherwise it is only marked as hidden. **/ void removeDownload(int index, bool deleteFile); /** * @brief restores the specified download to view (which was previously hidden * @param index index of the download to restore */ void restoreDownload(int index); /** * @brief cancel the specified download. This will lead to the corresponding file to be deleted * * @param index index of the download to cancel **/ void cancelDownload(int index); void pauseDownload(int index); void resumeDownload(int index); void queryInfo(int index); void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); void managedGameChanged(MOBase::IPluginGame const *gamePlugin); private slots: void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); void downloadReadyRead(); void downloadFinished(); void downloadError(QNetworkReply::NetworkError error); void metaDataChanged(); void directoryChanged(const QString &dirctory); private: void createMetaFile(DownloadInfo *info); public: /** Get a unique filename for a download. * * This allows you multiple versions of download files, useful if the file * comes from a web site with no version control * * @param basename: Name of the file * * @return Unique(ish) name */ QString getDownloadFileName(const QString &baseName) const; private: void startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume); void resumeDownloadInt(int index); /** * @brief start a download from a url * * @param url the url to download from * @param fileInfo information previously retrieved from the mod page * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again **/ bool addDownload(const QStringList &URLs, int modID, int fileID, const MOBase::ModRepositoryFileInfo *fileInfo); // important: the caller has to lock the list-mutex, otherwise the DownloadInfo-pointer might get invalidated at any time DownloadInfo *findDownload(QObject *reply, int *index = nullptr) const; void removeFile(int index, bool deleteFile); void refreshAlphabeticalTranslation(); bool ByName(int LHS, int RHS); QString getFileNameFromNetworkReply(QNetworkReply *reply); void setState(DownloadInfo *info, DownloadManager::DownloadState state); DownloadInfo *downloadInfoByID(unsigned int id); QDateTime matchDate(const QString &timeString); void removePending(int modID, int fileID); static QString getFileTypeString(int fileType); private: static const int AUTOMATIC_RETRIES = 3; private: NexusInterface *m_NexusInterface; QVector > m_PendingDownloads; QVector m_ActiveDownloads; QString m_OutputDirectory; std::map m_PreferredServers; QStringList m_SupportedExtensions; std::set m_RequestIDs; QVector m_AlphabeticalTranslation; QFileSystemWatcher m_DirWatcher; std::map m_DownloadFails; bool m_ShowHidden; QRegExp m_DateExpression; MOBase::IPluginGame const *m_ManagedGame; }; #endif // DOWNLOADMANAGER_H