/* * This file is part of nzbget * * Copyright (C) 2007 Andrei Prygounkov * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Revision$ * $Date$ * */ #ifdef HAVE_CONFIG_H #include #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #ifndef WIN32 #include #include #endif #include "nzbget.h" #include "DownloadInfo.h" #include "QueueEditor.h" #include "QueueCoordinator.h" #include "DiskState.h" #include "Options.h" #include "Log.h" #include "Util.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; extern DiskState* g_pDiskState; const int MAX_ID = 100000000; QueueEditor::EditItem::EditItem(FileInfo* pFileInfo, int iOffset) { m_pFileInfo = pFileInfo; m_iOffset = iOffset; } QueueEditor::QueueEditor() { debug("Creating QueueEditor"); } QueueEditor::~QueueEditor() { debug("Destroying QueueEditor"); } FileInfo* QueueEditor::FindFileInfo(DownloadQueue* pDownloadQueue, int iID) { for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetID() == iID) { return pFileInfo; } } return NULL; } int QueueEditor::FindFileInfoEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo) { int iEntry = 0; for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) { FileInfo* pFileInfo2 = *it; if (pFileInfo2 == pFileInfo) { return iEntry; } iEntry ++; } return -1; } /* * Set the pause flag of the specific entry in the queue * returns true if successful, false if operation is not possible */ void QueueEditor::PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause) { pFileInfo->SetPaused(bPause); } /* * Removes entry with index iEntry * returns true if successful, false if operation is not possible */ void QueueEditor::DeleteEntry(FileInfo* pFileInfo) { info("Deleting file %s from download queue", pFileInfo->GetFilename()); g_pQueueCoordinator->DeleteQueueEntry(pFileInfo); } /* * Moves entry identified with iID in the queue * returns true if successful, false if operation is not possible */ void QueueEditor::MoveEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, int iOffset) { int iEntry = FindFileInfoEntry(pDownloadQueue, pFileInfo); if (iEntry > -1) { int iNewEntry = iEntry + iOffset; if (iNewEntry < 0) { iNewEntry = 0; } if ((unsigned int)iNewEntry > pDownloadQueue->size() - 1) { iNewEntry = (int)pDownloadQueue->size() - 1; } if (iNewEntry >= 0 && (unsigned int)iNewEntry <= pDownloadQueue->size() - 1) { FileInfo* fi = (*pDownloadQueue)[iEntry]; pDownloadQueue->erase(pDownloadQueue->begin() + iEntry); pDownloadQueue->insert(pDownloadQueue->begin() + iNewEntry, fi); } } } bool QueueEditor::EditEntry(int ID, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { IDList cIDList; cIDList.clear(); cIDList.push_back(ID); return EditList(&cIDList, bSmartOrder, eAction, iOffset, szText); } bool QueueEditor::LockedEditEntry(DownloadQueue* pDownloadQueue, int ID, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { IDList cIDList; cIDList.clear(); cIDList.push_back(ID); return InternEditList(pDownloadQueue, &cIDList, bSmartOrder, eAction, iOffset, szText); } bool QueueEditor::EditList(IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); bool bOK = InternEditList(pDownloadQueue, pIDList, bSmartOrder, eAction, iOffset, szText); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); return bOK; } bool QueueEditor::InternEditList(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { if (eAction == eaGroupMoveOffset) { AlignAffectedGroups(pDownloadQueue, pIDList, bSmartOrder, iOffset); } ItemList cItemList; PrepareList(pDownloadQueue, &cItemList, pIDList, bSmartOrder, eAction, iOffset); if (eAction == eaFilePauseAllPars || eAction == eaFilePauseExtraPars) { PauseParsInGroups(&cItemList, eAction == eaFilePauseExtraPars); } else { for (ItemList::iterator it = cItemList.begin(); it != cItemList.end(); it++) { EditItem* pItem = *it; switch (eAction) { case eaFilePause: PauseUnpauseEntry(pItem->m_pFileInfo, true); break; case eaFileResume: PauseUnpauseEntry(pItem->m_pFileInfo, false); break; case eaFileMoveOffset: case eaFileMoveTop: case eaFileMoveBottom: MoveEntry(pDownloadQueue, pItem->m_pFileInfo, pItem->m_iOffset); break; case eaFileDelete: DeleteEntry(pItem->m_pFileInfo); break; case eaFilePauseAllPars: case eaFilePauseExtraPars: // remove compiler warning "enumeration not handled in switch" break; case eaGroupSetCategory: SetNZBCategory(pItem->m_pFileInfo->GetNZBInfo(), szText); break; case eaGroupPause: case eaGroupResume: case eaGroupDelete: case eaGroupMoveTop: case eaGroupMoveBottom: case eaGroupMoveOffset: case eaGroupPauseAllPars: case eaGroupPauseExtraPars: EditGroup(pDownloadQueue, pItem->m_pFileInfo, eAction, iOffset); break; } delete pItem; } } return cItemList.size() > 0; } void QueueEditor::PrepareList(DownloadQueue* pDownloadQueue, ItemList* pItemList, IDList* pIDList, bool bSmartOrder, EEditAction EEditAction, int iOffset) { if (EEditAction == eaFileMoveTop) { iOffset = -MAX_ID; } else if (EEditAction == eaFileMoveBottom) { iOffset = MAX_ID; } pItemList->reserve(pIDList->size()); if (bSmartOrder && iOffset != 0 && (EEditAction == eaFileMoveOffset || EEditAction == eaFileMoveTop || EEditAction == eaFileMoveBottom)) { //add IDs to list in order they currently have in download queue int iLastDestPos = -1; int iStart, iEnd, iStep; if (iOffset < 0) { iStart = 0; iEnd = pDownloadQueue->size(); iStep = 1; } else { iStart = pDownloadQueue->size() - 1; iEnd = -1; iStep = -1; } for (int iIndex = iStart; iIndex != iEnd; iIndex += iStep) { FileInfo* pFileInfo = (*pDownloadQueue)[iIndex]; int iID = pFileInfo->GetID(); for (IDList::iterator it = pIDList->begin(); it != pIDList->end(); it++) { if (iID == *it) { int iWorkOffset = iOffset; int iDestPos = iIndex + iWorkOffset; if (iLastDestPos == -1) { if (iDestPos < 0) { iWorkOffset = -iIndex; } else if (iDestPos > int(pDownloadQueue->size()) - 1) { iWorkOffset = int(pDownloadQueue->size()) - 1 - iIndex; } } else { if (iWorkOffset < 0 && iDestPos <= iLastDestPos) { iWorkOffset = iLastDestPos - iIndex + 1; } else if (iWorkOffset > 0 && iDestPos >= iLastDestPos) { iWorkOffset = iLastDestPos - iIndex - 1; } } iLastDestPos = iIndex + iWorkOffset; pItemList->push_back(new EditItem(pFileInfo, iWorkOffset)); break; } } } } else { // check ID range int iMaxID = 0; int iMinID = MAX_ID; for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) { FileInfo* pFileInfo = *it; int ID = pFileInfo->GetID(); if (ID > iMaxID) { iMaxID = ID; } if (ID < iMinID) { iMinID = ID; } } //add IDs to list in order they were transmitted in command for (IDList::iterator it = pIDList->begin(); it != pIDList->end(); it++) { int iID = *it; if (iMinID <= iID && iID <= iMaxID) { FileInfo* pFileInfo = FindFileInfo(pDownloadQueue, iID); if (pFileInfo) { pItemList->push_back(new EditItem(pFileInfo, iOffset)); } } } } } bool QueueEditor::EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, EEditAction eAction, int iOffset) { IDList cIDList; cIDList.clear(); // collecting files belonging to group for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) { FileInfo* pFileInfo2 = *it; if (pFileInfo2->GetNZBInfo() == pFileInfo->GetNZBInfo()) { cIDList.push_back(pFileInfo2->GetID()); } } if (eAction == eaGroupMoveOffset) { // calculating offset in terms of files FileList cGroupList; BuildGroupList(pDownloadQueue, &cGroupList); unsigned int iNum = 0; for (FileList::iterator it = cGroupList.begin(); it != cGroupList.end(); it++, iNum++) { FileInfo* pGroupInfo = *it; if (pGroupInfo->GetNZBInfo() == pFileInfo->GetNZBInfo()) { break; } } int iFileOffset = 0; if (iOffset > 0) { if (iNum + iOffset >= cGroupList.size() - 1) { eAction = eaGroupMoveBottom; } else { for (unsigned int i = iNum + 2; i < cGroupList.size() && iOffset > 0; i++, iOffset--) { iFileOffset += FindFileInfoEntry(pDownloadQueue, cGroupList[i]) - FindFileInfoEntry(pDownloadQueue, cGroupList[i-1]); } } } else { if (iNum + iOffset <= 0) { eAction = eaGroupMoveTop; } else { for (unsigned int i = iNum; i > 0 && iOffset < 0; i--, iOffset++) { iFileOffset -= FindFileInfoEntry(pDownloadQueue, cGroupList[i]) - FindFileInfoEntry(pDownloadQueue, cGroupList[i-1]); } } } iOffset = iFileOffset; } EEditAction GroupToFileMap[] = { (EEditAction)0, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars }; return InternEditList(pDownloadQueue, &cIDList, true, GroupToFileMap[eAction], iOffset, NULL); } void QueueEditor::BuildGroupList(DownloadQueue* pDownloadQueue, FileList* pGroupList) { pGroupList->clear(); for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) { FileInfo* pFileInfo = *it; FileInfo* pGroupInfo = NULL; for (FileList::iterator itg = pGroupList->begin(); itg != pGroupList->end(); itg++) { FileInfo* pGroupInfo1 = *itg; if (pGroupInfo1->GetNZBInfo() == pFileInfo->GetNZBInfo()) { pGroupInfo = pGroupInfo1; break; } } if (!pGroupInfo) { pGroupList->push_back(pFileInfo); } } } bool QueueEditor::ItemExists(FileList* pFileList, FileInfo* pFileInfo) { for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { if (*it == pFileInfo) { return true; } } return false; } void QueueEditor::AlignAffectedGroups(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, int iOffset) { // Build list of all groups; List contains first file of each group FileList cGroupList; BuildGroupList(pDownloadQueue, &cGroupList); // Find affected groups. It includes groups being moved and groups directly // above or under of these groups (those order is also changed) FileList cAffectedGroupList; cAffectedGroupList.clear(); ItemList cItemList; PrepareList(pDownloadQueue, &cItemList, pIDList, bSmartOrder, eaFileMoveOffset, iOffset); for (ItemList::iterator it = cItemList.begin(); it != cItemList.end(); it++) { EditItem* pItem = *it; unsigned int iNum = 0; for (FileList::iterator it = cGroupList.begin(); it != cGroupList.end(); it++, iNum++) { FileInfo* pFileInfo = *it; if (pItem->m_pFileInfo->GetNZBInfo() == pFileInfo->GetNZBInfo()) { if (!ItemExists(&cAffectedGroupList, pFileInfo)) { cAffectedGroupList.push_back(pFileInfo); } if (iOffset < 0) { for (int i = iNum - 1; i >= -iOffset-1; i--) { if (!ItemExists(&cAffectedGroupList, cGroupList[i])) { cAffectedGroupList.push_back(cGroupList[i]); } } } if (iOffset > 0) { for (unsigned int i = iNum + 1; i <= cGroupList.size() - iOffset; i++) { if (!ItemExists(&cAffectedGroupList, cGroupList[i])) { cAffectedGroupList.push_back(cGroupList[i]); } } if (iNum + 1 < cGroupList.size()) { cAffectedGroupList.push_back(cGroupList[iNum + 1]); } } break; } } delete pItem; } cGroupList.clear(); // Aligning groups for (FileList::iterator it = cAffectedGroupList.begin(); it != cAffectedGroupList.end(); it++) { FileInfo* pFileInfo = *it; AlignGroup(pDownloadQueue, pFileInfo); } } void QueueEditor::AlignGroup(DownloadQueue* pDownloadQueue, FileInfo* pFirstFileInfo) { FileInfo* pLastFileInfo = NULL; unsigned int iLastNum = 0; unsigned int iNum = 0; while (iNum < pDownloadQueue->size()) { FileInfo* pFileInfo = (*pDownloadQueue)[iNum]; if (pFirstFileInfo->GetNZBInfo() == pFileInfo->GetNZBInfo()) { if (pLastFileInfo && iNum - iLastNum > 1) { pDownloadQueue->erase(pDownloadQueue->begin() + iNum); pDownloadQueue->insert(pDownloadQueue->begin() + iLastNum + 1, pFileInfo); iLastNum++; } else { iLastNum = iNum; } pLastFileInfo = pFileInfo; } iNum++; } } void QueueEditor::PauseParsInGroups(ItemList* pItemList, bool bExtraParsOnly) { while (true) { FileList GroupFileList; GroupFileList.clear(); FileInfo* pFirstFileInfo = NULL; for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); ) { EditItem* pItem = *it; if (!pFirstFileInfo || (pFirstFileInfo->GetNZBInfo() == pItem->m_pFileInfo->GetNZBInfo())) { GroupFileList.push_back(pItem->m_pFileInfo); if (!pFirstFileInfo) { pFirstFileInfo = pItem->m_pFileInfo; } delete pItem; pItemList->erase(it); it = pItemList->begin(); continue; } it++; } if (!GroupFileList.empty()) { PausePars(&GroupFileList, bExtraParsOnly); } else { break; } } } /** * If the parameter "bExtraParsOnly" is set to "false", then we pause all par2-files. * If the parameter "bExtraParsOnly" is set to "true", we use the following strategy: * At first we find all par-files, which do not have "vol" in their names, then we pause * all vols and do not affect all just-pars. * In a case, if there are no just-pars, but only vols, we find the smallest vol-file * and do not affect it, but pause all other pars. */ void QueueEditor::PausePars(FileList* pFileList, bool bExtraParsOnly) { debug("QueueEditor: Pausing pars"); FileList Pars, Vols; Pars.clear(); Vols.clear(); for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; char szLoFileName[1024]; strncpy(szLoFileName, pFileInfo->GetFilename(), 1024); szLoFileName[1024-1] = '\0'; for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase if (strstr(szLoFileName, ".par2")) { if (!bExtraParsOnly) { pFileInfo->SetPaused(true); } else { if (strstr(szLoFileName, ".vol")) { Vols.push_back(pFileInfo); } else { Pars.push_back(pFileInfo); } } } } if (bExtraParsOnly) { if (!Pars.empty()) { for (FileList::iterator it = Vols.begin(); it != Vols.end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetPaused(true); } } else { // pausing all Vol-files except the smallest one FileInfo* pSmallest = NULL; for (FileList::iterator it = Vols.begin(); it != Vols.end(); it++) { FileInfo* pFileInfo = *it; if (!pSmallest) { pSmallest = pFileInfo; } else if (pSmallest->GetSize() > pFileInfo->GetSize()) { pSmallest->SetPaused(true); pSmallest = pFileInfo; } else { pFileInfo->SetPaused(true); } } } } } void QueueEditor::SetNZBCategory(NZBInfo* pNZBInfo, const char* szCategory) { debug("QueueEditor: setting category '%s' for '%s'", szCategory, Util::BaseFileName(pNZBInfo->GetFilename())); g_pQueueCoordinator->SetQueueEntryNZBCategory(pNZBInfo, szCategory); }