123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- ########################################################################
- # Wiizard - A Wii games manager
- # Copyright (C) 2023 CYBERDEViL
- #
- # This file is part of Wiizard.
- #
- # Wiizard 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.
- #
- # Wiizard 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, see <https://www.gnu.org/licenses/>.
- #
- ########################################################################
- from PyQt5.QtWidgets import (
- QDialog,
- QVBoxLayout,
- QLabel,
- QListWidget,
- QPushButton,
- QTextEdit,
- QProgressBar
- )
- from PyQt5.QtGui import QBrush, QColor
- from PyQt5.QtCore import Qt, QVariant
- from wiizard.operations import (
- GameOperationsThread,
- GameOperation,
- GAME_OP_DELETE,
- GAME_OP_MOVE,
- GAME_OP_ADD
- )
- from wiizard.translations import _
- class ApplyChangedDialog(QDialog):
- def __init__(self, sourceModels, parent=None):
- QDialog.__init__(self, parent=parent)
- """
- There are {} operations pending.
- - {} are new games
- - {} are games marked for deletion
- Order of execution:
- - delete x from y
- - delete x from y
-
- """
- self.__sourceModels = sourceModels
- self.operations = []
- self.thread = None
- self.setWindowTitle(_("Apply changes"))
- self.setMinimumWidth(400)
- """
- PARTITION A | PARTITION B
- GAME_1 4GB | GAME_3 4GB
- GAME_2 4GB |
- |
- free: 0GB used: 8GB | free: 0GB used: 4GB
- If we where to swap PARTITION A with PARTITION B the operations list should
- look as follows:
- - ADD GAME_1 to PARTITION B
- - DEL GAME_1 from PARTITION A
- - ADD GAME_3 to PARTITION A
- - DEL GAME_3 from PARTITION B
- - ADD GAME_2 to PARTITION B
- - DEL GAME_2 from PARTITION A
- """
- # Create operations list
- # Stage 1: Delete games that are marked for deletion and don't have a
- # add operation pending.
- # Stage 2: Add games that need to be added but are marked for deletion.
- # - After adding a game, delete it from the other partition.
- # 1. We need a list of games that we can delete without consequences.
- # 2. We need a list of games that have conflicts and sort those.
- # Statistics gathering
- toDelete = []
- toAdd = []
- toMove = []
- for sourceModel in sourceModels:
- toDelete += sourceModel.pendingDelGames
- toAdd += sourceModel.pendingAddGames
- for game in list(toDelete):
- if game in toAdd:
- toDelete.remove(game)
- toAdd.remove(game)
- toMove.append(game)
- # Sort sourceModels so we start with the one with the lesser free space.
- sourceModels = sorted(sourceModels, key=lambda m: (m.getFree()))
- # Stage 1: Add games to the operation list that we can delete right a way
- # (these games do not also have a pending copy operation).
- for sourceModel in sourceModels:
- for game in sourceModel.pendingDelGames:
- if game in toDelete:
- self.operations.append(GameOperation(game, sourceModel, GAME_OP_DELETE))
- # Stage 2: Move games (First copy and then delete)
- for sourceModel in sourceModels:
- for game in sourceModel.pendingAddGames:
- if game in toMove:
- self.operations.append(GameOperation(game, sourceModel, GAME_OP_MOVE))
- # Stage 3: Add remaining games (Copy)
- for sourceModel in sourceModels:
- for game in sourceModel.pendingAddGames:
- if game in toAdd:
- self.operations.append(GameOperation(game, sourceModel, GAME_OP_ADD))
- totalOperations = len(self.operations)
- self.operationsLeft = totalOperations
- layout = QVBoxLayout(self)
- self.pendingLabel = QLabel(self)
- self.pendingLabel.setWordWrap(True)
- self.__reGenPendingLabel()
- self.operationsList = QListWidget(self)
- # TODO add icons
- for operation in self.operations:
- if operation.operation == GAME_OP_DELETE:
- self.operationsList.addItem(_("Delete \"{}\" from \"{}\"")
- .format(operation.game.title,
- operation.target.location))
- elif operation.operation == GAME_OP_ADD:
- self.operationsList.addItem(_("Add \"{}\" to \"{}\" from \"{}\"")
- .format(operation.game.title,
- operation.target.location,
- operation.game.sourceModel.location))
- elif operation.operation == GAME_OP_MOVE:
- self.operationsList.addItem(_("Move \"{}\" to \"{}\" from \"{}\"")
- .format(operation.game.title,
- operation.target.location,
- operation.game.sourceModel.location))
- self.logWidget = QTextEdit(self)
- self.logWidget.hide()
- self.applyButton = QPushButton(_("Apply"), self)
- self.cancelButton = QPushButton(_("Cancel"), self)
- self.cancelButton.hide()
- self.doneButton = QPushButton(_("Done"), self)
- self.doneButton.hide()
- self.progressBar = QProgressBar(self)
- self.progressBar.setFormat(("Busy please wait.. %v / %m %p%"))
- self.progressBar.setMaximum(totalOperations)
- self.progressBar.setValue(0)
- self.progressBar.hide()
- layout.addWidget(self.pendingLabel)
- layout.addWidget(self.operationsList)
- layout.addWidget(self.logWidget)
- layout.addWidget(self.progressBar)
- layout.addWidget(self.applyButton)
- layout.addWidget(self.cancelButton)
- layout.addWidget(self.doneButton)
- self.applyButton.clicked.connect(self.onApply)
- self.cancelButton.clicked.connect(self.onCancel)
- self.doneButton.clicked.connect(self.onDoneClicked)
- self.operationsList.currentRowChanged.connect(self.__setLogForOperation)
- def closeEvent(self, event):
- if self.thread is not None:
- self.onCancel()
- event.ignore()
- return
- QDialog.closeEvent(self, event)
- def onApply(self):
- self.logWidget.show()
- self.applyButton.hide()
- self.cancelButton.show()
- self.progressBar.show()
- self.thread = GameOperationsThread(self.operations)
- self.thread.progress.connect(self.__onProgressUpdate)
- self.thread.completed.connect(self.__onComplete)
- self.thread.start()
- def onCancel(self):
- self.progressBar.setFormat(_("Cancelling please wait on current "
- "operation.. %v / %m %p%"))
- self.thread.cancel()
- def onDoneClicked(self):
- self.accept()
- def __reGenPendingLabel(self):
- if self.operationsLeft == 1:
- self.pendingLabel.setText(_("There is 1 operation pending"))
- else:
- self.pendingLabel.setText(_("There are {} operations pending")
- .format(self.operationsLeft))
- def __onProgressUpdate(self, progress, errorMessage):
- self.progressBar.setValue(progress)
- index = progress - 1
- # https://doc.qt.io/qt-5/qlistwidgetitem.html
- item = self.operationsList.item(index)
- if errorMessage:
- item.setBackground(QBrush(QColor("#9C0000"), Qt.SolidPattern))
- item.setData(Qt.UserRole, QVariant(errorMessage))
- #txt = self.logWidget.toPlainText() + "\n" + errorMessage
- #self.logWidget.setPlainText(txt)
- else:
- item.setBackground(QBrush(QColor("#009C00"), Qt.SolidPattern))
- item.setData(Qt.UserRole, QVariant("OK"))
- self.operationsList.setCurrentRow(index)
- self.__setLogForOperation(index)
- self.operationsLeft -= 1
- self.__reGenPendingLabel()
- def __onComplete(self):
- self.cancelButton.hide()
- self.thread.wait()
- self.thread.quit()
- self.thread = None
- self.doneButton.show()
- self.progressBar.setFormat(_("Done %v / %m %p%"))
- for sourceModel in self.__sourceModels:
- sourceModel.startScan()
- def __setLogForOperation(self, index):
- item = self.operationsList.item(index)
- message = item.data(Qt.UserRole)
- self.logWidget.setPlainText(str(message))
|