class TestWeakMethodSlot(object): def setup_method(self, method): class MyObject(object): def __init__(self): self.called = False def slot(self, **kwargs): self.called = True self.obj_ref = MyObject() self.slot = Slot(self.obj_ref.slot, weak=True) self.signal = Signal() self.signal.connect(self.slot) def test_alive(self): assert self.slot.is_alive def test_call(self): self.signal.emit(testing=1234) assert self.obj_ref.called def test_gc(self): self.obj_ref = None assert not self.slot.is_alive self.signal.emit(testing=1234)
class Controller(object): def __init__(self, floors=[], elevators=[]): self.__floors = floors # absolute coords self.__floor_coords = [0] * len(self.__floors) for i in range(1, len(self.__floor_coords)): self.__floor_coords[i] += self.__floor_coords[i-1] + self.__floors[i-1].height self.__elevators = {e.id : e for e in elevators} # indicates a command to an elevator to change velocity self.__signal_change_velocity = Signal(args=['elevator_id', 'velocity']) self.__signal_stop = Signal(args=['elevator_id']) @property def signal_change_velocity(self): return self.__signal_change_velocity @property def signal_stop(self): return self.__signal_stop def elevator_requested(self, from_floor, to_floor, **kwargs): print "Controller::elevator_requested>", from_floor, '->', to_floor for e in self.__elevators.values(): if e.velocity == 0: self.signal_change_velocity.emit(elevator_id=e.id, velocity=2) pass def elevator_position_changed(self, elevator_id, x, y, **kwargs): FORCED_STOP_DIST = .33 # 10cm range for allowed stop # if about to hit ground/roof -> stop if abs(y - self.__floor_coords[-1]) < FORCED_STOP_DIST and self.__elevators[elevator_id].velocity > 0: self.__signal_stop.emit(elevator_id=elevator_id, floor_num=len(self.__floors)) elif abs(y - self.__floor_coords[0]) < FORCED_STOP_DIST and self.__elevators[elevator_id].velocity < 0: self.__signal_stop.emit(elevator_id=elevator_id, floor_num=1) else: pass # if near the destination floor -> stop # assert y <= self.__floor_coords[-1], 'elevator hit the roof' # print y, self.__floor_coords # calculate position, and if at floor, and floor is in dest list, then signal stop pass def elevator_door_closed(self, **kwargs): pass
class CommandConsole(cmd.Cmd): def __init__(self): cmd.Cmd.__init__(self) self.save_command = Signal(['output_dir']) self.quit_command = Signal() def do_save(self, line): self.save_command.emit(output_dir=line) def do_EOF(self, line): return self.do_quit(line) def do_quit(self, line): self.quit_command.emit() return True
class TestException(object): def setup_method(self, method): self.signal = Signal(threadsafe=False) self.seen_exception = False def failing_slot(**args): raise MyTestError('die!') self.signal.connect(failing_slot) def test_emit_exception(self): try: self.signal.emit() except MyTestError: self.seen_exception = True assert self.seen_exception
class Button(TextBox): def __init__(self, x, y, width, height, text='Button', text_size=25): self.button_down = False self.clicked = Signal() super().__init__(x, y, width, height, text=text, text_size=text_size) self.hold_function = self.hold self.release_function = self.release self.redraw() def redraw(self): if self.button_down: self.background.fill((255, 255, 255)) else: super().redraw() #if self.visible: outline = (0, 0, self.rect.w, self.rect.h) pygame.draw.rect(self.background, self.outline_colour, outline, self.outline_thickness) rendered_text = self.font.render(self.text, True, self.text_colour).convert_alpha() rect_center = self.background.get_rect().center text_rect = rendered_text.get_rect(center=rect_center) self.background.blit(rendered_text, text_rect) def hold(self, *args): if not self.button_down: self.button_down = True self.redraw() def release(self, *args): if self.button_down: self.button_down = False self.clicked.emit() self.redraw()
class WAEventHandler(object): def __init__(self, conn): self.connecting = Signal() self.connected = Signal() self.sleeping = Signal() self.disconnected = Signal() self.reconnecting = Signal() '''(remote)''' self.typing = Signal() '''(remote)''' self.typingPaused = Signal() '''(remote)''' self.available = Signal() '''(remote)''' self.unavailable = Signal() self.messageSent = Signal() self.messageDelivered = Signal() '''(fmsg)''' self.messageReceived = Signal() '''messageReceived with fmsg.author''' self.groupMessageReceived = Signal() ''' (fmsg) -> media_type: image, audio, video, location, vcard -> data image: -> url -> preview audio, video: -> url location: -> latitude -> longitude -> preview vcard: -> contact (vcard format) ''' self.mediaReceived = Signal() '''mediaReceived with fmsg.author''' self.groupMediaReceived = Signal() '''(group, author, subject)''' self.newGroupSubject = Signal() '''(group, who)''' self.groupAdd = Signal() '''(group, who)''' self.groupRemove = Signal() ''' (groups: [{subject, id, owner}]) ''' self.groupsReceived = Signal() self.groupListReceived = Signal() self.lastSeenUpdated = Signal() self.sendTyping = Signal() self.sendPaused = Signal() self.getLastOnline = Signal() self.disconnectRequested = Signal() self.disconnectRequested.connect(self.onDisconnectRequested) self.loginFailed = Signal() self.loginSuccess = Signal() self.connectionError = Signal() self.conn = conn self.sendTyping.connect(self.conn.sendTyping) self.sendPaused.connect(self.conn.sendPaused) self.getLastOnline.connect(self.conn.getLastOnline) self.startPingTimer() def onDirty(self, categories): '''Receive groups??''' pass def onAccountChanged(self, account_kind, expire): pass def onRelayRequest( self, pin, timeoutSeconds, idx, ): pass def sendPing(self): self.startPingTimer() self.conn.sendPing() def startPingTimer(self): self.pingTimer = threading.Timer(180, self.sendPing) self.pingTimer.start() def onDisconnectRequested(self): self.pingTimer.cancel() def onPing(self, idx): self.conn.sendPong(idx) def networkAvailable(self): pass def networkDisconnected(self): self.sleeping.emit() def networkUnavailable(self): self.disconnected.emit() def onUnavailable(self): self.conn.sendUnavailable() def conversationOpened(self, jid): pass def onAvailable(self): self.conn.sendAvailable() def message_received(self, fmsg): if hasattr(fmsg, 'type'): if fmsg.type == "chat": if fmsg.remote.endswith('@g.us'): self.groupMessageReceived.emit(fmsg) else: self.messageReceived.emit(fmsg) elif fmsg.type == "media": if fmsg.remote.endswith('@g.us'): self.groupMediaReceived.emit(fmsg) else: self.mediaReceived.emit(fmsg) if fmsg.wants_receipt: self.conn.sendMessageReceived(fmsg) def subjectReceiptRequested(self, to, idx): self.conn.sendSubjectReceived(to, idx) def presence_available_received(self, remote): if remote == self.conn.jid: return self.available.emit(remote) def presence_unavailable_received(self, remote): if remote == self.conn.jid: return self.unavailable.emit(remote) def typing_received(self, remote): self.typing.emit(remote) def paused_received(self, remote): self.typingPaused.emit(remote) def message_error(self, fmsg, errorCode): pass def message_status_update(self, fmsg): pass
class Elevator(object): def __init__(self, id, size=10, height=10): """ people - list of people currently in the elevator size - elevator capacity in terms of people count velocity - velocity along y axis position - current y coordinate door_opening_time - time taken to open doors in seconds door_closing_time - time taken to close doors in seconds """ self.__id = id self.__people = [] self.__signal_person_inside = Signal(args=['person']) self.__signal_position_change = Signal(args=['elevator_id', 'x', 'y']) # direction: True = up, Down = False self.__signal_door_opened = Signal(args=['elevator_id', 'direction', 'people_inside', 'available_capacity', 'floor_num']) self.__signal_door_closed = Signal(args=['elevator_id', 'people_inside']) self.__size = size self.__velocity = 0 self.__position = (0, 0) self.__height = height self.__door_opening_time = 1.5 self.__door_closing_time = 2.5 def move(self, timedelta): # print '[%s] speed = %s/%s' % (self.id, self.velocity, self.__velocity) self.__position = Vector(0, self.__velocity) + self.__position self.__signal_position_change.emit(elevator_id=self.__id, x=self.__position[0], y=self.__position[1]) def set_velocity(self, elevator_id, velocity, **kwargs): if self.id == elevator_id: self.__velocity = velocity def stop(self, elevator_id, floor_num, **kwargs): if self.id != elevator_id: return going_up = self.__velocity > 0 self.__velocity = 0 # open door # time.sleep(self.__door_opening_time) timer.timeout(door_openning_time * interval, callback) def callback(): self.signal_door_opened.emit(elevator_id=self.id, direction=going_up, available_capacity=self.size - len(self.people), people_inside=self.people[:], floor_num=floor_num) def go_to(floor_no=1): # determine current pos # change velocity # go pass @property def id(self): return self.__id @property def size(self): return self.__size @property def velocity(self): return self.__velocity @property def position(self): """ Simplified for elevator case, as it moves up/down Return y coordinate """ return self.__position[1] @property def people(self): return self.__people @property def signal_person_inside(self): return self.__signal_person_inside @property def signal_position_change(self): return self.__signal_position_change @property def height(self): return self.__height @property def signal_door_opened(self): print 'signaling door opened', self.id return self.__signal_door_opened @property def signal_door_closed(self): return self.__signal_door_closed def people_boarded(self, elevator_id, people, **kwargs): if self.id != elevator_id: return self.people.extend(people[:]) # close door # time.sleep(self.__door_closing_time) self.signal_door_closed.emit(elevator_id=self.id, people_inside=self.people[:])
class FileBrowser(QDialog, pytson.Translatable): """ Dialog to display files contained on a TS3 filepath. """ def __init__(self, schid, cid, password='', path='/', parent=None, *, staticpath=False, readonly=False, downloaddir=None, iconpack=None): """ Instantiates a new object. @param schid: the id of the serverconnection handler @type schid: int @param cid: the id of the channel @type cid: int @param password: password to the channel, defaults to an empty string @type password: str @param path: path to display, defaults to the root path @type path: str @param parent: parent of the dialog; optional keyword arg; defaults to None @type parent: QWidget @param staticpath: if set to True, the initial path can't be changed by the user; optional keyword arg; defaults to False @type staticpath: bool @param readonly: if set to True, the user can't download, upload or delete files, or create new directories; optional keyword arg; defaults to False @type readonly: bool @param downloaddir: directory to download files to; optional keyword arg; defaults to None; if set to None, the TS3 client's download directory is used @type downloaddir: str @param iconpack: iconpack to load icons from; optional keyword arg; defaults to None; if set to None, the current iconpack is used @type iconpack: ts3client.IconPack """ super(QDialog, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) iconpackopened = False if not iconpack: try: iconpack = ts3client.IconPack.current() iconpack.open() iconpackopened = True except Exception as e: self.delete() raise e try: setupUi(self, pytson.getPluginPath("ressources", "filebrowser.ui"), iconpack=iconpack) self.statusbar = SmartStatusBar(self) self.layout().addWidget(self.statusbar) self.statusbar.hide() except Exception as e: self.delete() raise e err, cname = ts3lib.getChannelVariableAsString(schid, cid, ChannelProperties. CHANNEL_NAME) if err == ERROR_ok: self.setWindowTitle(self._tr("File Browser - {cname}").format( cname=cname)) else: self.setWindowTitle(self._tr("File Browser")) self.schid = schid self.cid = cid self.password = password self.path = None self.staticpath = staticpath self.readonly = readonly self.createretcode = None self.delretcode = None if not self.readonly and not downloaddir: cfg = ts3client.Config() q = cfg.query("SELECT value FROM filetransfer " "WHERE key='DownloadDir'") del cfg if q.next(): self.downloaddir = q.value("value") else: self.delete() raise Exception("Error getting DownloadDir from config") else: self.downloaddir = downloaddir if not self.readonly: menu = self.menu = QMenu(self) self.openAction = menu.addAction(QIcon(iconpack.icon("FILE_UP")), self._tr("Open")) self.openAction.connect("triggered()", self.on_openAction_triggered) self.downAction = menu.addAction(QIcon(iconpack.icon("DOWN")), self._tr("Download")) self.downAction.connect("triggered()", self.downloadFiles) self.renameAction = menu.addAction(QIcon(iconpack.icon("EDIT")), self._tr("Rename")) self.renameAction.connect("triggered()", self.on_renameAction_triggered) self.copyAction = menu.addAction(QIcon(iconpack.icon("COPY")), self._tr("Copy URL")) self.copyAction.connect("triggered()", self.on_copyAction_triggered) self.delAction = menu.addAction(QIcon(iconpack.icon("DELETE")), self._tr("Delete")) self.delAction.connect("triggered()", self.deleteFiles) self.upAction = menu.addAction(QIcon(iconpack.icon("UP")), self._tr("Upload files")) self.upAction.connect("triggered()", self.uploadFiles) self.createAction = menu.addAction(QIcon.fromTheme("folder"), self._tr("Create Folder")) self.createAction.connect("triggered()", self.createFolder) self.refreshAction = menu.addAction(QIcon(iconpack.icon( "FILE_REFRESH")), self._tr("Refresh")) self.refreshAction.connect("triggered()", self.refresh) self.allactions = [self.openAction, self.downAction, self.renameAction, self.copyAction, self.delAction, self.upAction, self.createAction, self.refreshAction] self.collector = FileCollector(schid, cid, password, self.downloaddir) self.collector.collectionFinished.connect(self._startDownload) self.collector.collectionError.connect(self.showError) self.fileDoubleClicked = Signal() self.contextMenuRequested = Signal() self.transdlg = None self.listmodel = FileListModel(schid, cid, password, self, readonly=readonly) self.listmodel.pathChanged.connect(self.onPathChanged) self.listmodel.error.connect(self.showError) self.proxy = QSortFilterProxyModel(self) self.proxy.setSortRole(Qt.UserRole) self.proxy.setSortCaseSensitivity(Qt.CaseInsensitive) self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxy.setSourceModel(self.listmodel) self.listmodel.path = path self._adjustUi() if iconpackopened: iconpack.close() PluginHost.registerCallbackProxy(self) def __del__(self): PluginHost.unregisterCallbackProxy(self) def _enableMenus(self, actlist): for act in self.allactions: act.setVisible(act in actlist) def _adjustMenu(self): selfiles = self.selectedFiles() cur = self.listmodel.fileByIndex(self.currentItem()) if len(selfiles) == 0: self._enableMenus([self.upAction, self.createAction, self.refreshAction]) elif cur.isDirectory: self._enableMenus([self.openAction, self.downAction, self.renameAction, self.copyAction, self.delAction]) else: self._enableMenus([self.downAction, self.renameAction, self.copyAction, self.delAction]) def _adjustUi(self): self.filterFrame.hide() self.filecountLabel.hide() self.downloaddirButton.setText(self.downloaddir) self.iconButton.setChecked(True) self.stack.setCurrentWidget(self.listPage) self.list.setModel(self.proxy) self.table.setModel(self.proxy) self.table.sortByColumn(0, Qt.AscendingOrder) if self.staticpath: self.upButton.hide() self.homeButton.hide() if self.readonly: self.uploadButton.hide() self.downloadButton.hide() self.directoryButton.hide() self.deleteButton.hide() self.downloaddirLabel.hide() self.downloaddirButton.hide() header = self.table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) for i in range(1, header.count()): header.setSectionResizeMode(i, QHeaderView.ResizeToContents) self.refreshButton.connect("clicked()", self.refresh) self.uploadButton.connect("clicked()", self.uploadFiles) self.downloadButton.connect("clicked()", self.downloadFiles) self.deleteButton.connect("clicked()", self.deleteFiles) self.directoryButton.connect("clicked()", self.createFolder) self.list.connect("doubleClicked(QModelIndex)", self.viewDoubleClicked) self.table.connect("doubleClicked(QModelIndex)", self.viewDoubleClicked) def _showTransfers(self): if not self.transdlg: self.transdlg = FileTransferDialog(self.schid, self.cid, self.password, self) self.transdlg.show() def onPathChanged(self, newpath): self.path = newpath self.pathEdit.setText(newpath) inroot = newpath == "/" self.upButton.setEnabled(not inroot) self.homeButton.setEnabled(not inroot) files = self.listmodel.currentFiles if not files: self.filecountLabel.hide() else: self.filecountLabel.show() fcount = 0 dcount = 0 for f in files: if f.isDirectory: dcount += 1 else: fcount += 1 fstr = self._tr("{filecount} file(s)", n=fcount).format( filecount=fcount) dstr = self._tr("{dircount} directory(s)", n=dcount).format( dircount=dcount) if dcount == 0: self.filecountLabel.setText(fstr) elif fcount == 0: self.filecountLabel.setText(dstr) else: cstr = self._tr("{dircountstr} and {fcountstr}").format( dircountstr=dstr, fcountstr=fstr) self.filecountLabel.setText(cstr) def on_pathEdit_returnPressed(self): oldpath = self.listmodel.path if not self.readonly: self.listmodel.path = self.pathEdit.text self.pathEdit.text = oldpath or "" def on_iconButton_toggled(self, act): if act: self.stack.setCurrentWidget(self.listPage) def on_detailedButton_toggled(self, act): if act: self.stack.setCurrentWidget(self.tablePage) def on_filterButton_clicked(self): self.filterFrame.show() def on_clearButton_clicked(self): self.filterEdit.clear() self.filterFrame.hide() def on_filterEdit_textChanged(self, newtext): self.proxy.setFilterRegExp(newtext) def on_upButton_clicked(self): if self.staticpath: return if self.path == "/": return self.listmodel.path = joinpath(*splitpath(self.path)[:-1]) def on_homeButton_clicked(self): if self.staticpath: return self.listmodel.path = "/" def refresh(self): self.listmodel.path = self.listmodel.path def on_downloaddirButton_clicked(self): QDesktopServices.openUrl(QUrl(self.downloaddir)) def showError(self, prefix, errcode, msg=None): if not msg: err, msg = ts3lib.getErrorMessage(errcode) else: err = ERROR_ok if err != ERROR_ok: self.statusbar.showMessage("%s: %s" % (prefix, errcode)) else: self.statusbar.showMessage("%s: %s" % (prefix, msg)) def uploadFiles(self): if self.readonly: return files = QFileDialog.getOpenFileNames(self, self._tr("Upload files"), self.downloaddir) fca = FileCollisionAction.overwrite curfiles = {f.name: f for f in self.listmodel.currentFiles} for f in files: fname = os.path.split(f)[-1] if fname in curfiles: if not fca & FileCollisionAction.toall: fca = FileCollisionDialog.getAction(f, curfiles[fname], False, len(files) > 1, self) if fca == 0: return if fca & FileCollisionAction.skip: if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite break self._showTransfers() self.transdlg.addUpload(self.path, f, fca & FileCollisionAction.overwrite, fca & FileCollisionAction.resume) if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite def onServerErrorEvent(self, schid, errorMessage, error, returnCode, extraMessage): if schid != self.schid: return if returnCode == self.createretcode: if error == ERROR_ok: self.listmodel.path = self.path else: self.showError(self._tr("Error creating directory"), error, errorMessage) elif returnCode == self.delretcode: if error == ERROR_ok: self.listmodel.path = self.path else: self.showError(self._tr("Error deleting files"), error, errorMessage) def selectedFiles(self): if self.stack.currentWidget() == self.listPage: view = self.list else: view = self.table return [self.listmodel.fileByIndex(self.proxy.mapToSource(x)) for x in view.selectionModel().selectedIndexes] def currentItem(self, source=True): if self.stack.currentWidget() == self.listPage: view = self.list else: view = self.table if source: return self.proxy.mapToSource(view.currentIndex()) else: return view.currentIndex() def _startDownload(self, collection): """ @param collection: list of tuples containing the download directory and the list of files to download to that directory @type collection: list[tuple(str, list[File])] """ if not collection: return fca = FileCollisionAction.overwrite for (downdir, files) in collection: for f in files: multi = len(files) + len(collection) > 2 fname = os.path.join(downdir, f.name) if os.path.isfile(fname): if not fca & FileCollisionAction.toall: fca = FileCollisionDialog.getAction(fname, f, True, multi, self) if fca == 0: return if fca & FileCollisionAction.skip: if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite break self._showTransfers() self.transdlg.addDownload(f, downdir, fca & FileCollisionAction.overwrite, fca & FileCollisionAction.resume) if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite def downloadFiles(self, files=None): if self.readonly: return if not files: selfiles = self.selectedFiles() else: selfiles = files if not selfiles: return downfiles = [] downdirs = [] for f in selfiles: if f.isDirectory: downdirs.append(f) else: downfiles.append(f) if not downdirs: self._startDownload([(self.downloaddir, downfiles)]) else: if downfiles: self.collector.addFiles(downfiles) self.collector.collect(downdirs) def createFolder(self): if self.readonly: return ok = BoolResult() dirname = QInputDialog.getText(self, self._tr("Create Folder"), self._tr("Folder name:"), QLineEdit.Normal, "", ok) if not ok or dirname == "": return self.createretcode = ts3lib.createReturnCode() err = ts3lib.requestCreateDirectory(self.schid, self.cid, self.password, joinpath(self.path, dirname), self.createretcode) if err != ERROR_ok: self.showError(self._tr("Error creating directory"), err) def deleteFiles(self, files=None): if self.readonly: return if not files: selfiles = self.selectedFiles() else: selfiles = files if not selfiles: return if QMessageBox.question(self, self._tr("Delete files"), self._tr("Do you really want to delete all " "selected files?")) == QMessageBox.No: return pathes = [f.fullpath for f in selfiles] self.delretcode = ts3lib.createReturnCode() err = ts3lib.requestDeleteFile(self.schid, self.cid, self.password, pathes, self.delretcode) if err != ERROR_ok: self.showError(self._tr("Error deleting files"), err) def on_table_customContextMenuRequested(self, pos): selfiles = self.selectedFiles() globpos = self.table.mapToGlobal(pos) if self.readonly: self.contextMenuRequested.emit(selfiles, globpos) else: self._adjustMenu() self.menu.popup(globpos) def on_list_customContextMenuRequested(self, pos): selfiles = self.selectedFiles() globpos = self.list.mapToGlobal(pos) if self.readonly: self.contextMenuRequested.emit(selfiles, globpos) else: self._adjustMenu() self.menu.popup(globpos) def viewDoubleClicked(self, idx): if not idx.isValid(): return f = self.listmodel.fileByIndex(self.proxy.mapToSource(idx)) if f.isDirectory: if self.staticpath: self.fileDoubleClicked.emit(f) else: self.listmodel.path = f.fullpath else: if self.readonly: self.fileDoubleClicked.emit(f) else: self.downloadFiles([f]) def on_openAction_triggered(self): cur = self.listmodel.fileByIndex(self.currentItem()) if not cur or not cur.isDirectory: return self.listmodel.path = cur.fullpath def on_renameAction_triggered(self): if self.stack.currentWidget() == self.listPage: view = self.list else: view = self.table view.edit(self.currentItem(False)) def on_copyAction_triggered(self): cur = self.listmodel.fileByIndex(self.currentItem()) if not cur: return err, host, port, _ = ts3lib.getServerConnectInfo(self.schid) if err == ERROR_ok: url = ("[URL=ts3file://{address}?port={port}&channel={cid}&" "path={path}&filename={fname}&isDir={isdir}&" "size={size}&fileDateTime={date}]{fname}[/URL]").format( address=host, port=port, cid=self.cid, path=QUrl.toPercentEncoding(cur.path), fname=cur.name, isdir=1 if cur.isDirectory else 0, size=cur.size, date=int(cur.datetime.timestamp())) QApplication.clipboard().setText(url) else: self.showError(self._tr("Error getting server connection info"), err)
class FileListModel(QAbstractItemModel, pytson.Translatable): """ Itemmodel to abstract the files contained on a TS3 filepath. """ def __init__(self, schid, cid, password, parent=None, *, readonly=False): super(QAbstractItemModel, self).__init__(parent) self.schid = schid self.cid = cid self.password = password self.readonly = readonly self.pathChanged = Signal() self.error = Signal() self._path = None self.newpath = None self.files = [] self.newfiles = [] self.retcode = None self.renretcode = None self.renfile = () self.titles = [ self._tr("Name"), self._tr("Size"), self._tr("Type"), self._tr("Last Changed") ] PluginHost.registerCallbackProxy(self) def __del__(self): PluginHost.unregisterCallbackProxy(self) @property def path(self): return self._path @path.setter def path(self, val): self._reload(val) @property def currentFiles(self): return self.files def _reload(self, path=None): if path: self.newpath = path else: self.newpath = self._path self.retcode = ts3lib.createReturnCode() err = ts3lib.requestFileList(self.schid, self.cid, self.password, self.newpath, self.retcode) if err != ERROR_ok: _errprint(self._tr("Error requesting filelist"), err, self.schid, self.cid) def onFileListEvent(self, schid, channelID, path, name, size, date, atype, incompletesize, returnCode): if (schid != self.schid or channelID != self.cid or returnCode != self.retcode): return self.newfiles.append( File(path, name, size, date, atype, incompletesize)) def onFileListFinishedEvent(self, schid, channelID, path): if (schid != self.schid or channelID != self.cid or path != self.newpath): return # might be unneeded (event is catched in onServerErrorEvent) def onServerErrorEvent(self, schid, errorMessage, error, returnCode, extraMessage): if schid != self.schid: return if returnCode == self.retcode: if error in [ERROR_ok, ERROR_database_empty_result]: self.beginResetModel() self.files = self.newfiles self.newfiles = [] self.endResetModel() if self._path != self.newpath: self._path = self.newpath self.pathChanged.emit(self._path) else: self.error.emit(self._tr("Error requesting filelist"), error, errorMessage) elif returnCode == self.renretcode: if error != ERROR_ok: self.renfile[0].name = self.renfile[1] self.error.emit(self._tr("Error renaming file"), error, errorMessage) self.renfile = () def onServerPermissionErrorEvent(self, schid, errorMessage, error, returnCode, failedPermissionID): if schid != self.schid or returnCode != self.retcode: return if returnCode == self.retcode: if error != ERROR_ok: self.error.emit(self._tr("Error requesting filelist"), error, errorMessage) elif returnCode == self.renretcode: if error != ERROR_ok: self.renfile[0].name = self.renfile[1] self.error.emit(self._tr("Error renaming file"), error, errorMessage) self.renfile = () def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole and orientation == Qt.Horizontal: return self.titles[section] return None def flags(self, idx): f = Qt.ItemIsEnabled | Qt.ItemIsSelectable if not self.readonly: return f | Qt.ItemIsEditable else: return f def index(self, row, column, parent=QModelIndex()): if parent.isValid(): return QModelIndex() return self.createIndex(row, column) def parent(self, idx): return QModelIndex() def rowCount(self, parent=QModelIndex()): if parent.isValid(): return 0 return len(self.files) def columnCount(self, parent=QModelIndex()): return len(self.titles) def data(self, idx, role=Qt.DisplayRole): if not idx.isValid(): return None f = self.files[idx.row()] if idx.column() == 0: if role == Qt.DisplayRole: return f.name elif role == Qt.DecorationRole: return f.icon elif role == Qt.EditRole and not self.readonly: return f.name elif role == Qt.UserRole: if f.isDirectory: return "a%s" % f.name else: return "b%s" % f.name elif role == Qt.DisplayRole: if idx.column() == 1 and not f.isDirectory: return bytesToStr(f.size) elif idx.column() == 2: if f.isDirectory: return self._tr("Directory") else: return self._tr("File") elif idx.column() == 3: return f.datetime.strftime( pytson.tr("filetransfer", "%Y-%m-%d %H:%M:%S")) return None def setData(self, idx, value, role=Qt.EditRole): if not idx.isValid(): return False f = self.fileByIndex(idx) if value == f.name: return self.renretcode = ts3lib.createReturnCode() self.renfile = (f, f.name) err = ts3lib.requestRenameFile(self.schid, self.cid, self.password, 0, "", f.fullpath, joinpath(f.path, value), self.renretcode) if err == ERROR_ok: f.name = value return True def fileByIndex(self, idx): if idx.isValid(): return self.files[idx.row()] return None
class DistantIO: def __init__(self): # Init logging facility # From : http://sametmax.com/ecrire-des-logs-en-python/ logger = logging.getLogger() logger.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s :: %(levelname)s :: %(message)s") file_handler = RotatingFileHandler("api_log.log", "a", 1000000, 1) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) logger.addHandler(file_handler) stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.DEBUG) logger.addHandler(stream_handler) # Signals self.signal_MCU_state_changed = Signal(args=["alive"]) self.signal_received_descriptor = Signal(args=["var_id", "var_type", "var_name", "var_writeable", "group_id"]) self.signal_received_group_descriptor = Signal(args=["group_id", "group_name"]) self.signal_received_value = Signal(args=["var_id"]) self.distantio = distantio_protocol() self.protocol = Protocol(self.unused) # Queue holding received characters to be processed by worker process self.input_queue = mp.Queue() # Queue holding decoded frames self.output_queue = mp.Queue() # Conditions for controlling run process self.condition_new_rx_data = mp.Event() self.condition_new_rx_data.clear() self.condition_run_process = mp.Event() self.condition_run_process.clear() # Worker process for decoding characters self.producer_conn, self.consumer_conn = mp.Pipe() self.worker = Worker( self.input_queue, self.producer_conn, self.condition_new_rx_data, self.condition_run_process ) self.worker.start() # Array containing buffers with MCU variables values self.variables_values = dict() # max size of the buffers self.buffer_length = 128 # Array containing last time each individual variable was updated self.last_variables_update = dict() # Min delay in seconds between two emit value received signal self.emit_signal_delay = 0.1 self.time_start = time.time() # Timer for monitoring MCU alive self.mcu_died_delay = 2.0 self.mcu_alive_timer = threading.Timer(self.mcu_died_delay, self.on_mcu_lost_connection) self.variable_list = dict() self.connected = False self.datalogger = Datalogger() # Start MCU timer self.mcu_alive_timer = threading.Timer(self.mcu_died_delay, self.on_mcu_lost_connection) self.mcu_alive_timer.start() logging.info("DistantIO API initialized successfully.") def decode_rx_data(self, data): self.input_queue.put(data) def export_data(self): self.signal_MCU_state_changed.emit(alive=False) self.connected = False logging.info("Disconnected successfully.") # Write emergency data to file self.datalogger.export() def terminate(self): self.mcu_alive_timer.cancel() if self.mcu_alive_timer.isAlive(): self.mcu_alive_timer.join() logging.info("Sending terminate signal to all threads.") self.condition_new_rx_data.set() self.condition_run_process.set() self.worker.join() logging.info("Worker process joined.") logging.info("Active thread count :" + str(threading.active_count())) for t in threading.enumerate(): logging.info("Thread :" + str(t)) logging.info("API terminated successfully.") ### Distant IO calls to MCU # Ask the MCU to return all descriptors def request_descriptors(self): logging.info("requested all descriptors to MCU.") frame = self.distantio.get_descriptors_frame() frame = self.protocol.encode(frame) return frame # Ask the MCU to write a variable def request_write(self, variable_id, data): if not variable_id in self.variable_list: logging.error("variable id provided " + str(variable_id) + " not found.") return # Check data is number try: # Cast to float and see if that fails dummy = float(data) except ValueError: logging.error("value provided " + str(data) + " not correct.") return logging.info("requested MCU to write " + str(data) + " to var id " + str(variable_id) + ".") frame = self.distantio.get_write_value_frame(variable_id, self.variable_list[variable_id]["type"], data) frame = self.protocol.encode(frame) return frame # Ask the MCU to read all variables def request_read_all(self): for key in self.variable_list: logging.info("requested to receive readings of var id " + str(key) + ".") frame = self.distantio.get_start_reading_frame(key, self.variable_list[key]["type"]) frame = self.protocol.encode(frame) yield frame def update(self): # Check new decoded data is available available = self.consumer_conn.poll() if not available: return None instruction = self.consumer_conn.recv(False) # If distantio received a alive signal if instruction["type"] == "alive-signal": # Restart the timer self.mcu_alive_timer.cancel() self.mcu_alive_timer.join() self.mcu_alive_timer = threading.Timer(self.mcu_died_delay, self.on_mcu_lost_connection) self.mcu_alive_timer.start() self.signal_MCU_state_changed.emit(alive=True) # if returned-value elif instruction["type"] == "returned-value": # Check var id is known, otherwise create a buffer for it if not instruction["var-id"] in self.variables_values: self.variables_values[instruction["var-id"]] = ValuesXY(self.buffer_length) # Store value and time in sbuffer self.variables_values[instruction["var-id"]].append(instruction["var-time"], instruction["var-value"]) if not instruction["var-id"] in self.last_variables_update: self.last_variables_update[instruction["var-id"]] = 0 current_time = time.time() elapsed_time = current_time - self.last_variables_update[instruction["var-id"]] # Make sure the received-value signal was not emitted too ofter if elapsed_time > self.emit_signal_delay: self.last_variables_update[instruction["var-id"]] = current_time self.signal_received_value.emit(var_id=instruction["var-id"]) # if returned-descriptor elif instruction["type"] == "returned-descriptor": self.variable_list[instruction["var-id"]] = dict() self.variable_list[instruction["var-id"]]["type"] = instruction["var-type"] self.variable_list[instruction["var-id"]]["name"] = ["var-name"] self.variable_list[instruction["var-id"]]["writeable"] = ["var-writeable"] logging.info("Received MCU variable descriptor with id " + str(instruction["var-id"])) if not instruction["var-id"] in self.variables_values: self.variables_values[instruction["var-id"]] = ValuesXY(self.buffer_length) if not instruction["var-id"] in self.last_variables_update: self.last_variables_update[instruction["var-id"]] = 0 self.signal_received_descriptor.emit( var_id=instruction["var-id"], var_type=instruction["var-type"], var_name=instruction["var-name"], var_writeable=instruction["var-writeable"], group_id=instruction["var-group"], ) elif instruction["type"] == "returned-group-descriptor": self.signal_received_group_descriptor.emit( group_id=instruction["group-id"], group_name=instruction["group-name"] ) elif instruction["type"] == "emergency-send": logging.warning("Received emergency data with user id " + str(instruction["data-id"])) self.datalogger.append( instruction["data-id"], instruction["data-time"], instruction["data-index"], instruction["data-value"] ) else: logging.error("Unknown instruction :" + str(instruction)) if count == maxamount and self.output_queue.qsize() > 200: logging.warning( "Instruction queue not processed fast enough. Current size :" + str(self.output_queue.qsize()) ) ## Callbacks def on_mcu_lost_connection(self): self.signal_MCU_state_changed.emit(alive=False) def unused(self, frame): logging.error("Local protocol decoded frame " + str(frame) + " instead of Worker") # Getters setters def get_last_value(self, var_id): if not var_id in self.variables_values: raise IndexError("Variable ID " + str(instruction["var-id"]) + " not found.") else: return self.variables_values[var_id].y[-1] def get_buffers_value(self, var_id): if not var_id in self.variables_values: raise IndexError("Variable ID " + str(instruction["var-id"]) + " not found.") else: return self.variables_values[var_id]
class Table: """ A Table is the place where all actions takes place. It is essentially a FSM, doing different routines at each state. It needs to keep track of the score, roles, the rules, etc. It needs to ask each player for decisions and respond to them accordingly. The table will also need to inform any decision to the Main Screen so that it can update the screen to reflect that change through the use of callbacks (Signal and Slot). This call should be minimised by making all the changes before calling to update the screen in one go. FSM cycles --- Preloop - Prepare the cards once - Initiate Players and connect them to the Table 1. Shuffle and Deal out cards to Players. 2a. Detect weak hands and ask for reshuffle. 2b. Return to (1) if any reshuffle occurs, otherwise proceed. 3. Bidding round. Randomly pick a starting player, in clockwise manner ask for a bid until it is valid. 3b. Proceed only if 3 consecutive skips are detected. 3c. Ask the winner of the bid a card not in their hand. 3d. Set up the player roles, trump suit, rounds to win for both side 3e. Play the game. Start with bid winner if NO TRUMP, otherwise Starting next to the bid winner. 4a. With the first player, ask for any card, excluding trump suits if trump is not broken 4b. With subsequent players, ask for cards that follow the suit of the first player , include trump suit if trump is broken. Ask for any card if the player cannot follow suit. 4c. Once all 4 players has made valid plays, announce results, update scoring. Announce player roles if the partner card is played. Break trump if trump is played. 4d. Repeat 4 until 13 rounds are made. Maybe add early win if confirmed one side wins 5. Ask for a new game. Go back to 1 if true. All played cards go into a hidden discard pile. """ def __init__(self, x, y, width, height, clear_colour, autoplay=False, view_all_cards=False, terminal=False): # TODO: Reduce the amount of update_table call self.update_table = Signal() self.x = x self.y = y self.width = width self.height = height self.table_font = pygame.font.SysFont("None", 25) self.player_font = pygame.font.SysFont("None", 25) # For gameplay self.game_state = GameState.DEALING self.reshuffling_players = [] self.current_round = 0 self.passes = 0 self.current_player = 0 self.first_player = False # This is for bidding purposes self.players = [] self.players_playzone = [] # Table status will be made known to the player by reference self.table_status = { 'played cards': [0, 0, 0, 0], 'leading player': 0, 'trump suit': 1, 'trump broken': False, 'round history': [], 'bid': 0, 'partner': 0, 'partner reveal': False, 'defender': { 'target': 0, 'wins': 0 }, 'attacker': { 'target': 0, 'wins': 0 } } # Prepare the surfaces for displaying self.background = pygame.Surface((self.width, self.height)) self.background.fill(clear_colour) self.background = self.background.convert() # TODO: Update the drawing of the table? # Prepare the card with dimensions w_deck = min(self.height, self.width) * 0.18 l_deck = min(self.width, self.height) * 0.7 # This is not a deck as it will never be drawn self.discard_deck = cards.prepare_playing_cards( int(w_deck * 0.6), int(w_deck * 0.6 * 97 / 71)) game_margins = 5 # Players' deck positioning playerx = ((self.width - l_deck) // 2, game_margins, (self.width - l_deck) // 2, self.width - w_deck - game_margins) playery = (self.height - w_deck - game_margins, (self.height - l_deck) // 2, game_margins, (self.height - l_deck) // 2) h_spacing = 20 v_spacing = 25 # Middle playfield for announcer and player playing deck positioning playfield_margins = 5 margins_with_w_deck = w_deck + playfield_margins + game_margins playfield_x = margins_with_w_deck playfield_y = margins_with_w_deck playfield_width = self.width - margins_with_w_deck * 2 playfield_height = self.height - margins_with_w_deck * 2 playdeckx = (playfield_x + (playfield_width - w_deck) / 2, playfield_x, playfield_x + (playfield_width - w_deck) / 2, playfield_x + playfield_width - w_deck) playdecky = (playfield_y + playfield_height - w_deck, playfield_y + (playfield_height - w_deck) / 2, playfield_y, playfield_y + (playfield_height - w_deck) / 2) # Player stats positioning stats_width = 100 self.stats_height = 100 stats_spacing = 10 self.player_stats_x = (playdeckx[0] - stats_width - stats_spacing, playdeckx[1], playdeckx[2] + w_deck + stats_spacing, playdeckx[3]) self.player_stats_y = (playdecky[0] + w_deck - self.stats_height, playdecky[1] - self.stats_height - stats_spacing, playdecky[2], playdecky[3] - w_deck - stats_spacing) self.player_stats = [[], [], [], []] # TODO: change surface to use colorkey, maybe, if the performance is tanked # Prepare all the player surfaces for i in range(NUM_OF_PLAYERS): vert = i % 2 == 1 spacing = h_spacing if vert: spacing = v_spacing reveal_mode = cards.DeckReveal.HIDE_ALL if i == 0 or view_all_cards: reveal_mode = cards.DeckReveal.SHOW_ALL if i == 0: player_class = players.MainPlayer if terminal: player_class = players.Player self.players.append( player_class(playerx[i], playery[i], l_deck, w_deck, spacing, vert_orientation=vert, deck_reveal=reveal_mode)) else: self.players.append( players.Player(playerx[i], playery[i], l_deck, w_deck, spacing, vert_orientation=vert, deck_reveal=reveal_mode, flip=(i == 1 or i == 2), draw_from_last=(i == 2 or i == 3))) self.players[i].connect_to_table(self.table_status) if i > 0: self.players[i].add_ai(ai.VivianAI(self.table_status)) self.players_playzone.append( cards.Deck(playdeckx[i], playdecky[i], w_deck, w_deck, 0)) for j in range(3): surf = pygame.Surface((stats_width, self.stats_height / 3), pygame.SRCALPHA) rendered_text = self.player_font.render( "Player {0:d}".format(i), True, (255, 0, 255)).convert_alpha() self.center_text_on_surface( surf, rendered_text, (255, 255, 255, 255 * VIEW_TRANSPARENT)) self.player_stats[i].append(surf) if autoplay: self.players[0].add_ai(ai.VivianAI(self.table_status)) # Announcer positioning and surface creation announcer_margins = 5 announcer_spacing = announcer_margins + w_deck self.announcer_x = playfield_x + announcer_spacing self.announcer_y = playfield_y + announcer_spacing self.announcer_width = playfield_width - 2 * announcer_spacing self.announcer_height = playfield_height - 2 * announcer_spacing self.announcer_line = [] for i in range(3): surf = pygame.Surface( (self.announcer_width, self.announcer_height / 3), pygame.SRCALPHA) self.announcer_line.append(surf) self.update_all_players(role=True, wins=True, clear_wins=True) self.write_message("Press P to play!") self.ongoing = False self.require_player_input = False self.terminal_play = terminal self.calling_panel = UI.CallPanel(playdeckx[0] + w_deck + 5, playdecky[0] + w_deck - 100, 220, 100) self.calling_panel.parent = self self.calling_panel.visible = False self.parent = None self.calling_panel.confirm_output.connect(self.emit_call) self.yes_button = UI.Button(playdeckx[0] + w_deck + 5, playdecky[0], 50, 25, text='yes') self.yes_button.visible = False self.yes_button.clicked.connect(lambda **z: pygame.event.post( pygame.event.Event(CALL_EVENT, call=True))) self.no_button = UI.Button(playdeckx[0] + w_deck + 5, playdecky[0] + 25 + 25, 50, 25, text='no') self.no_button.clicked.connect(lambda **z: pygame.event.post( pygame.event.Event(CALL_EVENT, call=False))) self.no_button.visible = False self.UI_elements = [ self.calling_panel, self.yes_button, self.no_button ] def emit_call(self, output, **kwargs): pygame.event.post(pygame.event.Event(CALL_EVENT, call=output)) def get_offset_pos(self): x, y = 0, 0 if self.parent: x, y = self.parent.get_offset_pos() return x + self.x, y + self.y def center_text_on_surface(self, surf, rendered_text, clear_colour): """ Blit the text centered in the surface rect box :param surf: :param rendered_text: :param clear_colour: :return: """ line_center = surf.get_rect().center text_rect = rendered_text.get_rect(center=line_center) surf.fill(clear_colour) surf.blit(rendered_text, text_rect) def get_pos(self): return self.x, self.y def process_UI(self, event): draw_update = False #if event.type == pygame.KEYUP: # if event.key == pygame.K_o: # self.calling_panel.visible = not self.calling_panel.visible # draw_update = True for element in self.UI_elements: if element.visible and \ element.process_events(event): draw_update = True if draw_update: self.update_table.emit() def continue_game(self, game_events): """ This is where the FSM is. State transition should occur here. What takes place in the state should be in a function. :return: None """ # TODO: Adjust the timing of sleep if self.game_state == GameState.DEALING: self.shuffle_and_deal() self.write_message("Shuffle Complete!") self.reshuffling_players = [] for i, player in enumerate(self.players): if player.get_card_points() < 4: self.write_message( "Low points detected in Player {0:d}! ".format(i)) self.reshuffling_players.append(i) if not self.reshuffling_players: self.write_message('No Reshuffle needed!') self.game_state = GameState.BIDDING self.write_message("Start to Bid") self.prepare_bidding() else: self.current_player = self.reshuffling_players[0] self.game_state = GameState.POINT_CHECK elif self.game_state == GameState.POINT_CHECK: reshuffle = self.check_reshuffle(game_events) if reshuffle is None: return if reshuffle is False and not self.current_player == self.reshuffling_players[ -1]: return else: if reshuffle: self.write_message('Reshuffle Initiated!', line=1) self.game_state = GameState.ENDING else: self.write_message('No Reshuffle needed!') self.game_state = GameState.BIDDING self.write_message("Start to Bid") self.prepare_bidding() elif self.game_state == GameState.BIDDING: bid_complete = self.start_bidding(game_events) if bid_complete: self.game_state = GameState.PLAYING self.update_all_players(role=True, wins=True) self.update_team_scores() elif self.game_state == GameState.PLAYING: self.play_a_round(game_events) if self.current_round == 13: self.declare_winner() self.ongoing = False self.game_state = GameState.ENDING else: self.reset_game() self.game_state = GameState.DEALING def shuffle_and_deal(self): """ Shuffle and deal the discard deck to the players, which should have 52 cards. :return: None """ if self.discard_deck: for i in range(10): random.shuffle(self.discard_deck) for player in self.players: for i in range(STARTING_HAND): player.add_card(self.discard_deck.pop()) self.update_table.emit() def check_reshuffle(self, game_events): """ Detect any possible reshuffle request within the players :return: True if reshuffle requested, else False """ if not self.require_player_input: if not self.players[self.current_player].AI: self.require_player_input = True self.write_message("Do you want a reshuffle?", line=1, update_now=False) self.yes_button.visible = True self.no_button.visible = True self.update_table.emit() return else: reshuffle = self.players[self.current_player].make_decision( self.game_state, 0) else: reshuffle = self.players[self.current_player].make_decision( self.game_state, 0, game_events) if reshuffle is None: return None self.require_player_input = False self.yes_button.visible = False self.no_button.visible = False self.update_table.emit() self.current_player = (self.current_player + 1) % NUM_OF_PLAYERS while self.current_player not in self.reshuffling_players: self.current_player = (self.current_player + 1) % NUM_OF_PLAYERS return reshuffle def prepare_bidding(self): # Randomly pick a starting player, whom also is the current bid winner self.current_player = random.randint(1, NUM_OF_PLAYERS) - 1 print("Starting Player: {0:d}".format(self.current_player)) self.passes = 0 self.table_status["bid"] = 11 # Lowest Bid: 1 Club by default self.first_player = True # Starting bidder "privilege" to raise the starting bid msg = "Current Bid: {0:d} {1:s}".format( self.table_status["bid"] // 10, cards.get_suit_string(self.table_status["bid"] % 10)) self.write_message(msg, line=1, delay_time=0) self.display_current_player(self.current_player) self.update_player_bid(self.current_player, 11, update_now=False) msg = 'Bid Leader: Player {0:d}'.format( (self.current_player - self.passes - 1 * (not self.first_player)) % NUM_OF_PLAYERS) self.write_message(msg, line=2, delay_time=0.5) if not self.terminal_play: self.calling_panel.cancel_button.visible = True self.calling_panel.change_lists_elements( [str(i + 1) for i in range(7)], ['Clubs', 'Diamonds', 'Hearts', 'Spades', 'No Trump']) def start_bidding(self, game_events): """ The bidding procedure. Flag up if player input required :return: Whether bidding is completed """ # Highest bid: 7 NoTrump. No further check required if self.passes < NUM_OF_PLAYERS - 1 and self.table_status["bid"] < 75: if not self.require_player_input: if not self.players[self.current_player].AI: self.require_player_input = True if not self.terminal_play: self.calling_panel.visible = True self.update_table.emit() return False else: player_bid = self.players[ self.current_player].make_decision(self.game_state, 0) else: player_bid, msg = self.players[ self.current_player].make_decision(self.game_state, 0, game_events) if msg: self.write_message(msg, delay_time=1, update_now=True) if player_bid < 0: return False self.require_player_input = False self.write_message("", delay_time=0, update_now=False) if not self.terminal_play: self.calling_panel.visible = False self.update_table.emit() if not player_bid: if not self.first_player: # Starting bidder pass do not count at the start self.passes += 1 else: self.table_status["bid"] = player_bid self.passes = 0 msg = "Current Bid: {0:d} {1:s}".format( self.table_status["bid"] // 10, cards.get_suit_string(self.table_status["bid"] % 10)) self.write_message(msg, line=1, update_now=False) msg = 'Bid Leader: Player {0:d}'.format(self.current_player) self.write_message(msg, line=2, update_now=True) if self.first_player: self.first_player = False if player_bid: self.update_player_bid(self.current_player, player_bid, update_now=False) else: self.update_player_bid(self.current_player, player_bid, update_now=False) if self.table_status["bid"] < 75: self.current_player += 1 self.current_player %= NUM_OF_PLAYERS self.display_current_player(self.current_player) time.sleep(0.5) if self.passes == NUM_OF_PLAYERS - 1 or self.table_status[ "bid"] == 75: if not self.terminal_play: self.calling_panel.cancel_button.visible = False self.calling_panel.change_lists_elements([ '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A' ], ['Clubs', 'Diamonds', 'Hearts', 'Spades']) return False else: if not self.require_player_input: self.write_message("Player {0:d} is the bid winner!".format( self.current_player), delay_time=1) msg = "Player {0:d} is calling a partner...".format( self.current_player) self.write_message(msg, delay_time=1) self.display_current_player(self.current_player) if not self.players[self.current_player].AI: self.require_player_input = True if not self.terminal_play: self.calling_panel.visible = True self.update_table.emit() return False else: # Ask for the partner card self.table_status["partner"] = self.players[ self.current_player].make_decision(self.game_state, 1) else: partner, msg = self.players[self.current_player].make_decision( self.game_state, 1, game_events) if msg: self.write_message(msg, delay_time=0, update_now=True) if not partner: return False self.table_status["partner"] = partner self.require_player_input = False if not self.terminal_play: self.calling_panel.visible = False self.update_table.emit() # Setup the table status before the play starts self.table_status['partner reveal'] = False self.table_status["trump suit"] = self.table_status["bid"] % 10 self.table_status["trump broken"] = False self.table_status['played cards'] = [0, 0, 0, 0] if self.table_status['trump suit'] == 5: self.table_status["leading player"] = self.current_player else: self.table_status["leading player"] = (self.current_player + 1) % NUM_OF_PLAYERS self.table_status['defender'][ 'target'] = self.table_status["bid"] // 10 + 6 self.table_status['attacker'][ 'target'] = 14 - self.table_status['defender']['target'] # Set the roles of the players self.players[self.current_player].role = PlayerRole.DECLARER self.write_message('Bidding Complete', delay_time=0) msg = 'Trump: {1:s}, Partner: {0:s}'.format( cards.get_card_string(self.table_status["partner"]), cards.get_suit_string(self.table_status['trump suit'])) self.write_message(msg, line=1, delay_time=1) return True def play_a_round(self, game_events): """ Ask each player to play a valid card and determine the winner of the round This must work without pause if only bots are playing The function will exit after every player decision or if a user input is needed. If a user input is required, the function will continuously exit without proceeding to the next player until a valid input is received. :return: None """ if not any(self.table_status["played cards"]): # Leading player starts with the leading card, which determines the leading suit if not self.require_player_input: if self.table_status['trump broken']: self.write_message("Trump has been broken!", delay_time=0) else: self.write_message("Trump is not broken", delay_time=0) self.current_player = self.table_status['leading player'] self.display_current_player(self.current_player) if not self.players[self.current_player].AI: self.require_player_input = True return else: card = self.players[self.current_player].make_decision( self.game_state, 0) else: card, msg = self.players[self.current_player].make_decision( self.game_state, 0, game_events) if msg: self.write_message(msg, delay_time=0, update_now=True) if not type(card) is cards.Card: if card: self.update_table.emit() return self.require_player_input = False self.table_status["played cards"][self.current_player] = card self.players_playzone[self.current_player].add_card(card) elif not all(self.table_status["played cards"]): # Subsequent player make their plays, following suit if possible if not self.require_player_input: self.display_current_player(self.current_player) if not self.players[self.current_player].AI: self.require_player_input = True return else: card = self.players[self.current_player].make_decision( self.game_state, 1) else: card, msg = self.players[self.current_player].make_decision( self.game_state, 1, game_events) if msg: self.write_message(msg, delay_time=0, update_now=False) if not type(card) is cards.Card: if card: self.update_table.emit() return self.require_player_input = False self.players_playzone[self.current_player].add_card(card) self.table_status["played cards"][self.current_player] = card else: # Once all player played, find out who wins leading_card = self.table_status["played cards"][ self.table_status['leading player']] card_suits = [ card.suit() for card in self.table_status["played cards"] ] card_nums = [ card.number() for card in self.table_status["played cards"] ] follow_suits = [suit == leading_card.suit() for suit in card_suits] trumps = [ suit == self.table_status['trump suit'] for suit in card_suits ] # Determine which players to check for winner, and determine winner if any(trumps): valid_nums = [ card_nums[i] * trumps[i] for i in range(NUM_OF_PLAYERS) ] else: valid_nums = [ card_nums[i] * follow_suits[i] for i in range(NUM_OF_PLAYERS) ] winning_player = valid_nums.index(max(valid_nums)) self.write_message("Player {0:d} wins!\n".format(winning_player), delay_time=1) self.players[winning_player].score += 1 self.update_player_wins(winning_player) # Clean up the cards, update score, set the next leading player, update round history for deck in self.players_playzone: self.discard_deck.append(deck.remove_card()) for player in self.players: if player.AI: player.AI.update_memory() if self.players[winning_player].role == PlayerRole.DECLARER or\ self.players[winning_player].role == PlayerRole.PARTNER: self.table_status['defender']['wins'] += 1 elif self.players[winning_player].role == PlayerRole.ATTACKER: self.table_status['attacker']['wins'] += 1 self.table_status['leading player'] = winning_player self.table_status['round history'].append( copy.copy(self.table_status["played cards"])) self.update_team_scores() self.table_status["played cards"] = [0] * NUM_OF_PLAYERS self.current_round += 1 self.update_table.emit() return # Break trump if the trump suit is played if not self.table_status['trump broken']: self.table_status['trump broken'] = card.suit( ) == self.table_status['trump suit'] if self.table_status['trump broken']: self.write_message("Trump broken!", delay_time=1) if not self.table_status['partner reveal']: if card.value == self.table_status['partner']: self.table_status['partner reveal'] = True self.write_message("Partner Revealed!", delay_time=1) self.reveal_all_roles(self.current_player) self.update_all_players(role=True, wins=False) self.current_player += 1 self.current_player %= NUM_OF_PLAYERS self.update_table.emit() time.sleep(0.5) def write_message(self, text, delay_time=0.5, line=0, update_now=True): """ Write a message into the center board surface (announcer) :param text: String to be displayed on the center board :param delay_time: How much delay to put once the string is display :param line: Which line of the announcer to write to :param update_now: :return: None """ if 0 <= line < len(self.announcer_line): print(text) text = text.strip('\n') rendered_text = self.table_font.render( text, True, (255, 255, 255)).convert_alpha() self.center_text_on_surface( self.announcer_line[line], rendered_text, (255, 255, 255, 255 * VIEW_TRANSPARENT)) if update_now: self.update_table.emit() time.sleep(delay_time) def update_players_role(self, player_num, update_now=True): """ Update the display of the player roles. Blank if UNKNOWN :param player_num: :param update_now: :return: """ self.player_stats[player_num][1].fill( (255, 255, 255, 255 * VIEW_TRANSPARENT)) role_text = '' colour = (0, 239, 224) if self.players[player_num].role == PlayerRole.DECLARER: role_text = 'Declarer' elif self.players[player_num].role == PlayerRole.ATTACKER: role_text = 'Attacker' colour = (225, 0, 0) elif self.players[player_num].role == PlayerRole.PARTNER: role_text = 'Partner' rendered_text = self.player_font.render(role_text, True, colour).convert_alpha() self.center_text_on_surface(self.player_stats[player_num][1], rendered_text, (255, 255, 255, 255 * VIEW_TRANSPARENT)) if update_now: self.update_table.emit() def update_player_wins(self, player_num, update_now=True, clear=False): """ Update the display of player's number of wins. :param player_num: :param update_now: :param clear: :return: """ self.player_stats[player_num][2].fill( (255, 255, 255, 255 * VIEW_TRANSPARENT)) if not clear: if self.players[player_num].score > 1: rendered_text = self.player_font.render( "Wins: {0:d}".format(self.players[player_num].score), True, (255, 255, 255)).convert_alpha() else: rendered_text = self.player_font.render( "Win: {0:d}".format(self.players[player_num].score), True, (255, 255, 255)).convert_alpha() self.center_text_on_surface( self.player_stats[player_num][2], rendered_text, (255, 255, 255, 255 * VIEW_TRANSPARENT)) if update_now: self.update_table.emit() def update_player_bid(self, player_num, bid, update_now=True): """ Update the display of the player's last bid. :param player_num: :param update_now: :param clear: :return: """ self.player_stats[player_num][2].fill( (255, 255, 255, 255 * VIEW_TRANSPARENT)) if not bid: rendered_text = self.player_font.render( "Pass".format(self.players[player_num].score), True, (255, 255, 255)).convert_alpha() else: bid_text = str(bid // 10) + ' ' + cards.get_suit_string(bid % 10) rendered_text = self.player_font.render( bid_text.format(self.players[player_num].score), True, (255, 255, 255)).convert_alpha() self.center_text_on_surface(self.player_stats[player_num][2], rendered_text, (255, 255, 255, 255 * VIEW_TRANSPARENT)) if update_now: self.update_table.emit() def update_all_players(self, role=False, wins=True, clear_wins=False): for i in range(NUM_OF_PLAYERS): if wins: self.update_player_wins(i, update_now=False, clear=clear_wins) if role: self.update_players_role(i, update_now=False) self.update_table.emit() def display_current_player(self, current=-1): if current >= 0: print("Player {0:d}\n".format(current)) for i in range(NUM_OF_PLAYERS): rendered_text = self.player_font.render( "Player {0:d}".format(i), True, (255, 0, 255)).convert_alpha() if i == current: self.center_text_on_surface(self.player_stats[i][0], rendered_text, (0, 64, 0, 255)) else: self.center_text_on_surface( self.player_stats[i][0], rendered_text, (255, 255, 255, 255 * VIEW_TRANSPARENT)) self.update_table.emit() def update_team_scores(self): if self.table_status['partner reveal']: msg = "Declarer: {0:d}/{2:d}, Attacker: {1:d}/{3:d}\n".format( self.table_status['defender']['wins'], self.table_status['attacker']['wins'], self.table_status['defender']['target'], self.table_status['attacker']['target']) self.write_message(msg, line=2) else: msg = "Declarer: {0:d}?/{1:d}, Attacker: ?/{2:d}\n".format( self.table_status['defender']['wins'], self.table_status['defender']['target'], self.table_status['attacker']['target']) self.write_message(msg, line=2) def reveal_all_roles(self, partner): """ Update all roles once the partner card is shown Also updates the partner to the player number :param partner: :return: """ self.players[partner].role = PlayerRole.PARTNER self.table_status["partner"] = partner self.table_status['defender']['wins'] += self.players[partner].score for i in range(NUM_OF_PLAYERS): if self.players[i].role == PlayerRole.UNKNOWN: self.players[i].role = PlayerRole.ATTACKER self.table_status['attacker']['wins'] += self.players[i].score def declare_winner(self): if self.table_status['attacker']['wins'] >= self.table_status[ 'attacker']['target']: self.write_message("Attacker wins! Press P to play again!") if self.table_status['defender']['wins'] >= self.table_status[ 'defender']['target']: self.write_message("Declarer wins! Press P to play again!") def reset_game(self): """ Reset all variables for the next game :return: """ for player in self.players: while not player.is_empty(): self.discard_deck.append(player.remove_card()) player.score = 0 player.role = PlayerRole.UNKNOWN if player.AI: player.AI.reset_memory() for i in range(NUM_OF_PLAYERS): self.update_players_role(i) self.update_player_wins(i, clear=True) self.table_status['defender']['wins'] = 0 self.table_status['attacker']['wins'] = 0 self.table_status["played cards"] = [0] * NUM_OF_PLAYERS self.table_status['round history'] = [] self.current_round = 0 self.write_message("", line=1, update_now=False) self.write_message("", line=2) self.display_current_player() self.update_table.emit()
class FileCollector(pytson.Translatable): """ Collects all files recursively from TS3 filetransfer directories with their corresponding download path. Emits a signal collectionFinished with a list of tuples(str, list[File]) containing the download dir and a list of files. The signal collectionError(str, int) is emitted on error with the errorstring and the errorcode. """ def __init__(self, schid, cid, password, rootdir): """ Instantiates a new object. @param schid: the id of the serverconnection handler @type schid: int @param cid: the id of the channel @type cid: int @param password: the password of the channel @type password: str @param rootdir: the root download directory @type rootdir: str """ super().__init__() self.schid = schid self.cid = cid self.password = password self.rootdir = rootdir self.collectionFinished = Signal() self.collectionError = Signal() self.queue = {} self.files = {} PluginHost.registerCallbackProxy(self) def __del__(self): PluginHost.unregisterCallbackProxy(self) def addFiles(self, files): """ Manually adds a list of files to the collection (emitted with the rootdir) @param files: list of files to emit @type files: list(File) """ self.files[self.rootdir] = files def collect(self, dirs): """ Starts collecting files from a list of directories @param dirs: list of directories @type dirs: list(File) """ for d in dirs: retcode = ts3lib.createReturnCode() self.queue[retcode] = d.fullpath err = ts3lib.requestFileList(self.schid, self.cid, self.password, d.fullpath, retcode) if err != ERROR_ok: del self.queue[retcode] self.collectionError.emit( self._tr( "Error requesting " "filelist of {dirname}").format(dirname=d.fullpath), err) def onServerErrorEvent(self, schid, errorMessage, error, returnCode, extraMessage): if schid != self.schid or returnCode not in self.queue: return if error not in [ERROR_ok, ERROR_database_empty_result]: self.collectionError.emit( self._tr( "Error requesting filelist " "of {dirname}").format(dirname=self.queue[returnCode]), error) del self.queue[returnCode] if not self.queue: self.collectionFinished.emit([(k, v) for k, v in self.files.items()]) self.files = {} def onFileListEvent(self, schid, channelID, path, name, size, datetime, atype, incompletesize, returnCode): if (schid != self.schid or self.cid != channelID or returnCode not in self.queue): return downpath = os.path.join(self.rootdir, *splitpath(path)[1:]) f = File(path, name, size, datetime, atype, incompletesize) if f.isDirectory: self.collect([f]) else: if downpath in self.files: self.files[downpath].append(f) else: self.files[downpath] = [f]
class FileBrowser(QDialog, pytson.Translatable): """ Dialog to display files contained on a TS3 filepath. """ def __init__(self, schid, cid, password='', path='/', parent=None, *, staticpath=False, readonly=False, downloaddir=None, iconpack=None): """ Instantiates a new object. @param schid: the id of the serverconnection handler @type schid: int @param cid: the id of the channel @type cid: int @param password: password to the channel, defaults to an empty string @type password: str @param path: path to display, defaults to the root path @type path: str @param parent: parent of the dialog; optional keyword arg; defaults to None @type parent: QWidget @param staticpath: if set to True, the initial path can't be changed by the user; optional keyword arg; defaults to False @type staticpath: bool @param readonly: if set to True, the user can't download, upload or delete files, or create new directories; optional keyword arg; defaults to False @type readonly: bool @param downloaddir: directory to download files to; optional keyword arg; defaults to None; if set to None, the TS3 client's download directory is used @type downloaddir: str @param iconpack: iconpack to load icons from; optional keyword arg; defaults to None; if set to None, the current iconpack is used @type iconpack: ts3client.IconPack """ super(QDialog, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) iconpackopened = False if not iconpack: try: iconpack = ts3client.IconPack.current() iconpack.open() iconpackopened = True except Exception as e: self.delete() raise e try: setupUi(self, pytson.getPluginPath("ressources", "filebrowser.ui"), iconpack=iconpack) self.statusbar = SmartStatusBar(self) self.layout().addWidget(self.statusbar) self.statusbar.hide() except Exception as e: self.delete() raise e err, cname = ts3lib.getChannelVariableAsString( schid, cid, ChannelProperties.CHANNEL_NAME) if err == ERROR_ok: self.setWindowTitle( self._tr("File Browser - {cname}").format(cname=cname)) else: self.setWindowTitle(self._tr("File Browser")) self.schid = schid self.cid = cid self.password = password self.path = None self.staticpath = staticpath self.readonly = readonly self.createretcode = None self.delretcode = None if not self.readonly and not downloaddir: cfg = ts3client.Config() q = cfg.query("SELECT value FROM filetransfer " "WHERE key='DownloadDir'") del cfg if q.next(): self.downloaddir = q.value("value") else: self.delete() raise Exception("Error getting DownloadDir from config") else: self.downloaddir = downloaddir if not self.readonly: menu = self.menu = QMenu(self) self.openAction = menu.addAction(QIcon(iconpack.icon("FILE_UP")), self._tr("Open")) self.openAction.connect("triggered()", self.on_openAction_triggered) self.downAction = menu.addAction(QIcon(iconpack.icon("DOWN")), self._tr("Download")) self.downAction.connect("triggered()", self.downloadFiles) self.renameAction = menu.addAction(QIcon(iconpack.icon("EDIT")), self._tr("Rename")) self.renameAction.connect("triggered()", self.on_renameAction_triggered) self.copyAction = menu.addAction(QIcon(iconpack.icon("COPY")), self._tr("Copy URL")) self.copyAction.connect("triggered()", self.on_copyAction_triggered) self.delAction = menu.addAction(QIcon(iconpack.icon("DELETE")), self._tr("Delete")) self.delAction.connect("triggered()", self.deleteFiles) self.upAction = menu.addAction(QIcon(iconpack.icon("UP")), self._tr("Upload files")) self.upAction.connect("triggered()", self.uploadFiles) self.createAction = menu.addAction(QIcon.fromTheme("folder"), self._tr("Create Folder")) self.createAction.connect("triggered()", self.createFolder) self.refreshAction = menu.addAction( QIcon(iconpack.icon("FILE_REFRESH")), self._tr("Refresh")) self.refreshAction.connect("triggered()", self.refresh) self.allactions = [ self.openAction, self.downAction, self.renameAction, self.copyAction, self.delAction, self.upAction, self.createAction, self.refreshAction ] self.collector = FileCollector(schid, cid, password, self.downloaddir) self.collector.collectionFinished.connect(self._startDownload) self.collector.collectionError.connect(self.showError) self.fileDoubleClicked = Signal() self.contextMenuRequested = Signal() self.transdlg = None self.listmodel = FileListModel(schid, cid, password, self, readonly=readonly) self.listmodel.pathChanged.connect(self.onPathChanged) self.listmodel.error.connect(self.showError) self.proxy = QSortFilterProxyModel(self) self.proxy.setSortRole(Qt.UserRole) self.proxy.setSortCaseSensitivity(Qt.CaseInsensitive) self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxy.setSourceModel(self.listmodel) self.listmodel.path = path self._adjustUi() if iconpackopened: iconpack.close() PluginHost.registerCallbackProxy(self) def __del__(self): PluginHost.unregisterCallbackProxy(self) def _enableMenus(self, actlist): for act in self.allactions: act.setVisible(act in actlist) def _adjustMenu(self): selfiles = self.selectedFiles() cur = self.listmodel.fileByIndex(self.currentItem()) if len(selfiles) == 0: self._enableMenus( [self.upAction, self.createAction, self.refreshAction]) elif cur.isDirectory: self._enableMenus([ self.openAction, self.downAction, self.renameAction, self.copyAction, self.delAction ]) else: self._enableMenus([ self.downAction, self.renameAction, self.copyAction, self.delAction ]) def _adjustUi(self): self.filterFrame.hide() self.filecountLabel.hide() self.downloaddirButton.setText(self.downloaddir) self.iconButton.setChecked(True) self.stack.setCurrentWidget(self.listPage) self.list.setModel(self.proxy) self.table.setModel(self.proxy) self.table.sortByColumn(0, Qt.AscendingOrder) if self.staticpath: self.upButton.hide() self.homeButton.hide() if self.readonly: self.uploadButton.hide() self.downloadButton.hide() self.directoryButton.hide() self.deleteButton.hide() self.downloaddirLabel.hide() self.downloaddirButton.hide() header = self.table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) for i in range(1, header.count()): header.setSectionResizeMode(i, QHeaderView.ResizeToContents) self.refreshButton.connect("clicked()", self.refresh) self.uploadButton.connect("clicked()", self.uploadFiles) self.downloadButton.connect("clicked()", self.downloadFiles) self.deleteButton.connect("clicked()", self.deleteFiles) self.directoryButton.connect("clicked()", self.createFolder) self.list.connect("doubleClicked(QModelIndex)", self.viewDoubleClicked) self.table.connect("doubleClicked(QModelIndex)", self.viewDoubleClicked) def _showTransfers(self): if not self.transdlg: self.transdlg = FileTransferDialog(self.schid, self.cid, self.password, self) self.transdlg.show() def onPathChanged(self, newpath): self.path = newpath self.pathEdit.setText(newpath) inroot = newpath == "/" self.upButton.setEnabled(not inroot) self.homeButton.setEnabled(not inroot) files = self.listmodel.currentFiles if not files: self.filecountLabel.hide() else: self.filecountLabel.show() fcount = 0 dcount = 0 for f in files: if f.isDirectory: dcount += 1 else: fcount += 1 fstr = self._tr("{filecount} file(s)", n=fcount).format(filecount=fcount) dstr = self._tr("{dircount} directory(s)", n=dcount).format(dircount=dcount) if dcount == 0: self.filecountLabel.setText(fstr) elif fcount == 0: self.filecountLabel.setText(dstr) else: cstr = self._tr("{dircountstr} and {fcountstr}").format( dircountstr=dstr, fcountstr=fstr) self.filecountLabel.setText(cstr) def on_pathEdit_returnPressed(self): oldpath = self.listmodel.path if not self.readonly: self.listmodel.path = self.pathEdit.text self.pathEdit.text = oldpath or "" def on_iconButton_toggled(self, act): if act: self.stack.setCurrentWidget(self.listPage) def on_detailedButton_toggled(self, act): if act: self.stack.setCurrentWidget(self.tablePage) def on_filterButton_clicked(self): self.filterFrame.show() def on_clearButton_clicked(self): self.filterEdit.clear() self.filterFrame.hide() def on_filterEdit_textChanged(self, newtext): self.proxy.setFilterRegExp(newtext) def on_upButton_clicked(self): if self.staticpath: return if self.path == "/": return self.listmodel.path = joinpath(*splitpath(self.path)[:-1]) def on_homeButton_clicked(self): if self.staticpath: return self.listmodel.path = "/" def refresh(self): self.listmodel.path = self.listmodel.path def on_downloaddirButton_clicked(self): QDesktopServices.openUrl(QUrl(self.downloaddir)) def showError(self, prefix, errcode, msg=None): if not msg: err, msg = ts3lib.getErrorMessage(errcode) else: err = ERROR_ok if err != ERROR_ok: self.statusbar.showMessage("%s: %s" % (prefix, errcode)) else: self.statusbar.showMessage("%s: %s" % (prefix, msg)) def uploadFiles(self): if self.readonly: return files = QFileDialog.getOpenFileNames(self, self._tr("Upload files"), self.downloaddir) fca = FileCollisionAction.overwrite curfiles = {f.name: f for f in self.listmodel.currentFiles} for f in files: fname = os.path.split(f)[-1] if fname in curfiles: if not fca & FileCollisionAction.toall: fca = FileCollisionDialog.getAction( f, curfiles[fname], False, len(files) > 1, self) if fca == 0: return if fca & FileCollisionAction.skip: if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite break self._showTransfers() self.transdlg.addUpload(self.path, f, fca & FileCollisionAction.overwrite, fca & FileCollisionAction.resume) if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite def onServerErrorEvent(self, schid, errorMessage, error, returnCode, extraMessage): if schid != self.schid: return if returnCode == self.createretcode: if error == ERROR_ok: self.listmodel.path = self.path else: self.showError(self._tr("Error creating directory"), error, errorMessage) elif returnCode == self.delretcode: if error == ERROR_ok: self.listmodel.path = self.path else: self.showError(self._tr("Error deleting files"), error, errorMessage) def selectedFiles(self): if self.stack.currentWidget() == self.listPage: view = self.list else: view = self.table return [ self.listmodel.fileByIndex(self.proxy.mapToSource(x)) for x in view.selectionModel().selectedIndexes ] def currentItem(self, source=True): if self.stack.currentWidget() == self.listPage: view = self.list else: view = self.table if source: return self.proxy.mapToSource(view.currentIndex()) else: return view.currentIndex() def _startDownload(self, collection): """ @param collection: list of tuples containing the download directory and the list of files to download to that directory @type collection: list[tuple(str, list[File])] """ if not collection: return fca = FileCollisionAction.overwrite for (downdir, files) in collection: for f in files: multi = len(files) + len(collection) > 2 fname = os.path.join(downdir, f.name) if os.path.isfile(fname): if not fca & FileCollisionAction.toall: fca = FileCollisionDialog.getAction( fname, f, True, multi, self) if fca == 0: return if fca & FileCollisionAction.skip: if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite break self._showTransfers() self.transdlg.addDownload(f, downdir, fca & FileCollisionAction.overwrite, fca & FileCollisionAction.resume) if not fca & FileCollisionAction.toall: fca = FileCollisionAction.overwrite def downloadFiles(self, files=None): if self.readonly: return if not files: selfiles = self.selectedFiles() else: selfiles = files if not selfiles: return downfiles = [] downdirs = [] for f in selfiles: if f.isDirectory: downdirs.append(f) else: downfiles.append(f) if not downdirs: self._startDownload([(self.downloaddir, downfiles)]) else: if downfiles: self.collector.addFiles(downfiles) self.collector.collect(downdirs) def createFolder(self): if self.readonly: return ok = BoolResult() dirname = QInputDialog.getText(self, self._tr("Create Folder"), self._tr("Folder name:"), QLineEdit.Normal, "", ok) if not ok or dirname == "": return self.createretcode = ts3lib.createReturnCode() err = ts3lib.requestCreateDirectory(self.schid, self.cid, self.password, joinpath(self.path, dirname), self.createretcode) if err != ERROR_ok: self.showError(self._tr("Error creating directory"), err) def deleteFiles(self, files=None): if self.readonly: return if not files: selfiles = self.selectedFiles() else: selfiles = files if not selfiles: return if QMessageBox.question( self, self._tr("Delete files"), self._tr("Do you really want to delete all " "selected files?")) == QMessageBox.No: return pathes = [f.fullpath for f in selfiles] self.delretcode = ts3lib.createReturnCode() err = ts3lib.requestDeleteFile(self.schid, self.cid, self.password, pathes, self.delretcode) if err != ERROR_ok: self.showError(self._tr("Error deleting files"), err) def on_table_customContextMenuRequested(self, pos): selfiles = self.selectedFiles() globpos = self.table.mapToGlobal(pos) if self.readonly: self.contextMenuRequested.emit(selfiles, globpos) else: self._adjustMenu() self.menu.popup(globpos) def on_list_customContextMenuRequested(self, pos): selfiles = self.selectedFiles() globpos = self.list.mapToGlobal(pos) if self.readonly: self.contextMenuRequested.emit(selfiles, globpos) else: self._adjustMenu() self.menu.popup(globpos) def viewDoubleClicked(self, idx): if not idx.isValid(): return f = self.listmodel.fileByIndex(self.proxy.mapToSource(idx)) if f.isDirectory: if self.staticpath: self.fileDoubleClicked.emit(f) else: self.listmodel.path = f.fullpath else: if self.readonly: self.fileDoubleClicked.emit(f) else: self.downloadFiles([f]) def on_openAction_triggered(self): cur = self.listmodel.fileByIndex(self.currentItem()) if not cur or not cur.isDirectory: return self.listmodel.path = cur.fullpath def on_renameAction_triggered(self): if self.stack.currentWidget() == self.listPage: view = self.list else: view = self.table view.edit(self.currentItem(False)) def on_copyAction_triggered(self): cur = self.listmodel.fileByIndex(self.currentItem()) if not cur: return err, host, port, _ = ts3lib.getServerConnectInfo(self.schid) if err == ERROR_ok: url = ("[URL=ts3file://{address}?port={port}&channel={cid}&" "path={path}&filename={fname}&isDir={isdir}&" "size={size}&fileDateTime={date}]{fname}[/URL]").format( address=host, port=port, cid=self.cid, path=QUrl.toPercentEncoding(cur.path), fname=cur.name, isdir=1 if cur.isDirectory else 0, size=cur.size, date=int(cur.datetime.timestamp())) QApplication.clipboard().setText(url) else: self.showError(self._tr("Error getting server connection info"), err)
class FileCollector(pytson.Translatable): """ Collects all files recursively from TS3 filetransfer directories with their corresponding download path. Emits a signal collectionFinished with a list of tuples(str, list[File]) containing the download dir and a list of files. The signal collectionError(str, int) is emitted on error with the errorstring and the errorcode. """ def __init__(self, schid, cid, password, rootdir): """ Instantiates a new object. @param schid: the id of the serverconnection handler @type schid: int @param cid: the id of the channel @type cid: int @param password: the password of the channel @type password: str @param rootdir: the root download directory @type rootdir: str """ super().__init__() self.schid = schid self.cid = cid self.password = password self.rootdir = rootdir self.collectionFinished = Signal() self.collectionError = Signal() self.queue = {} self.files = {} PluginHost.registerCallbackProxy(self) def __del__(self): PluginHost.unregisterCallbackProxy(self) def addFiles(self, files): """ Manually adds a list of files to the collection (emitted with the rootdir) @param files: list of files to emit @type files: list(File) """ self.files[self.rootdir] = files def collect(self, dirs): """ Starts collecting files from a list of directories @param dirs: list of directories @type dirs: list(File) """ for d in dirs: retcode = ts3lib.createReturnCode() self.queue[retcode] = d.fullpath err = ts3lib.requestFileList(self.schid, self.cid, self.password, d.fullpath, retcode) if err != ERROR_ok: del self.queue[retcode] self.collectionError.emit(self._tr("Error requesting " "filelist of {dirname}"). format(dirname=d.fullpath), err) def onServerErrorEvent(self, schid, errorMessage, error, returnCode, extraMessage): if schid != self.schid or returnCode not in self.queue: return if error not in [ERROR_ok, ERROR_database_empty_result]: self.collectionError.emit(self._tr("Error requesting filelist " "of {dirname}"). format(dirname=self.queue[returnCode]), error) del self.queue[returnCode] if not self.queue: self.collectionFinished.emit([(k, v) for k, v in self.files.items()]) self.files = {} def onFileListEvent(self, schid, channelID, path, name, size, datetime, atype, incompletesize, returnCode): if (schid != self.schid or self.cid != channelID or returnCode not in self.queue): return downpath = os.path.join(self.rootdir, *splitpath(path)[1:]) f = File(path, name, size, datetime, atype, incompletesize) if f.isDirectory: self.collect([f]) else: if downpath in self.files: self.files[downpath].append(f) else: self.files[downpath] = [f]
class CallPanel(GenericUI): """ The panel to contain the UI for the player to make bids and call a partner. """ def __init__(self, x, y, width, height): super().__init__(x, y, width, height) self.background.set_colorkey(None) self.confirm_output = Signal(args=['output']) self.text_size = 20 margins = 5 ui_width = 80 ui_height = 25 width_spacings = (width - 2.5 * ui_width - 2 * margins) / 4 height_spacings = (height - 2 * margins - 3 * ui_height) / 4 self.output_text = ['', ''] self.list1 = ScrollList(margins + width_spacings, margins, ui_width / 2, height - 2 * margins, texts=[str(i) for i in range(20)], text_size=self.text_size) self.list1.list_selected.connect( lambda text, **z: self.print_list_selection(text, 0)) self.list2 = ScrollList(margins + width_spacings * 2 + ui_width / 2, margins, ui_width, height - 2 * margins, texts=['a', 'b', 'c', 'd'], text_size=self.text_size) self.list2.list_selected.connect( lambda text, **z: self.print_list_selection(text, 1)) self.output_box = TextBox(margins + width_spacings * 3 + ui_width * 1.5, margins + height_spacings, ui_width, ui_height, text='-', text_size=self.text_size) self.confirm_button = Button(margins + width_spacings * 3 + ui_width * 1.5, margins + height_spacings * 2 + ui_height, ui_width, ui_height, text='Call', text_size=self.text_size) self.confirm_button.clicked.connect(self.emit_output) self.cancel_button = Button( margins + width_spacings * 3 + ui_width * 1.5, margins + height_spacings * 3 + ui_height * 2, ui_width, ui_height, text='Pass', text_size=self.text_size) self.cancel_button.visible = False self.cancel_button.clicked.connect(self.cancelling) #self.children = [self.label1, self.list1, self.label2, self.list2, self.children = [ self.list1, self.list2, self.confirm_button, self.output_box, self.cancel_button ] for element in self.children: element.parent = self self.redraw() def redraw(self, **kwargs): super().redraw() #self.background.fill((255,0,255)) #if self.visible: outline = (0, 0, self.rect.w, self.rect.h) pygame.draw.rect(self.background, self.outline_colour, outline, self.outline_thickness) for element in self.children: if element.visible: self.background.blit(element.background, element.get_pos()) def process_events(self, event): draw_update = False for element in self.children: if element.visible and element.process_events(event): draw_update = True if draw_update: self.redraw() return draw_update def print_list_selection(self, text, num, **kwargs): self.output_text[num] = text self.output_box.set_text(' '.join(self.output_text)) def emit_output(self, **kwargs): initial = '' if self.output_text[1]: initial = self.output_text[1][0].lower() output = self.output_text[0].lower() + initial self.confirm_output.emit(output=output) def cancelling(self, **kwargs): self.confirm_output.emit(output='') def change_lists_elements(self, left_list=None, right_list=None): if left_list is not None: self.list1.replace_list(left_list) if right_list is not None: self.list2.replace_list(right_list) self.redraw()
class ScrollList(GenericUI): def __init__(self, x, y, width, height, texts, text_size=25): super().__init__(x, y, width, height) self.list_selected = Signal(args=['text']) self.font = pygame.font.SysFont("None", text_size) self.texts = texts self.text_rects = [] self.y_offset = 0 self.selected = -1 self.outline_thickness = 3 self.selected_colour = (255, 0, 0) self.max_offset = 0 self.replace_list(texts) self.release_function = self.check_click_pos @property def selected(self): return self.__selected @selected.setter def selected(self, selected): if selected < 0: self.list_selected.emit(text='') else: self.list_selected.emit(text=self.texts[selected]) self.__selected = selected def redraw(self): super().redraw() #if self.visible: outline = (0, 0, self.rect.w, self.rect.h) pygame.draw.rect(self.background, self.outline_colour, outline, self.outline_thickness) i = 0 for text, text_rect in zip(self.texts, self.text_rects): if i == self.selected: pygame.draw.rect(self.background, self.selected_colour, text_rect) rendered_text = self.font.render(text, True, self.text_colour).convert_alpha() self.background.blit(rendered_text, text_rect) i += 1 def process_events(self, event): draw_update = super().process_events(event) if event.type == pygame.MOUSEBUTTONUP: mouse_pos = pygame.mouse.get_pos() if self.collide_at(mouse_pos): if event.button == 4: self.scroll_up() draw_update = True if event.button == 5: self.scroll_down() draw_update = True return draw_update def offset_text_rects(self, offset): prev_offset = self.y_offset self.y_offset += offset self.y_offset = max(-self.max_offset, self.y_offset) self.y_offset = min(0, self.y_offset) #if -self.max_offset <= self.y_offset <= 0: for text_rect in self.text_rects: text_rect.y += self.y_offset - prev_offset def scroll_down(self, offset=10): """ To scroll down, all elements should shift up :param offset: :return: """ self.offset_text_rects(-offset) self.redraw() def scroll_up(self, offset=10): self.offset_text_rects(offset) self.redraw() def check_click_pos(self, *args): pos = args[0] x0, y0 = self.get_offset_pos() relative_pos_x = pos[0] - x0 relative_pos_y = pos[1] - y0 mouse_pos = (relative_pos_x, relative_pos_y) for i, rect in enumerate(self.text_rects): if rect.collidepoint(mouse_pos): self.selected = i self.redraw() return def reset_scroll(self): self.offset_text_rects(-self.y_offset) def add_item(self, text): prev_offset = self.y_offset self.reset_scroll() self.texts.append(text) current_y = self.text_rects[-1].y + self.text_rects[-1].height rendered_text = self.font.render(text, True, self.text_colour).convert_alpha() text_rect = rendered_text.get_rect() text_rect.x = 0 text_rect.y = current_y text_rect.width = self.width self.text_rects.append(text_rect) self.max_offset = max(0, self.max_offset + text_rect.height) self.offset_text_rects(prev_offset) self.scroll_down(text_rect.height) self.redraw() def remove_item(self, pos=-1): prev_offset = self.y_offset self.reset_scroll() n_items = len(self.texts) if self.texts and 0 <= pos < n_items: self.texts.pop(pos) text_rect = self.text_rects.pop(pos) self.selected = min(self.selected, n_items - 2) if self.texts and pos < len(self.texts): for rect in self.text_rects[pos:]: rect.y -= text_rect.height self.max_offset = max(0, self.max_offset - text_rect.height) self.offset_text_rects(prev_offset) self.scroll_up(text_rect.height) self.redraw() def replace_list(self, texts): self.reset_scroll() self.texts = texts self.text_rects = [] current_y = self.outline_thickness self.selected = -1 for text in texts: rendered_text = self.font.render(text, True, self.text_colour).convert_alpha() text_rect = rendered_text.get_rect() text_rect.x = 0 text_rect.y = current_y text_rect.width = self.width self.text_rects.append(text_rect) current_y += text_rect.height self.max_offset = max(0, current_y - self.height) self.redraw() self.draw_update.emit()
class HaystackOperation(object): """ A core state machine object. This implements the basic interface presented for all operations in pyhaystack. """ def __init__(self, result_copy=True, result_deepcopy=True): """ Initialisation. This should be overridden by subclasses to accept and validate the inputs presented for the operation, raising an appropriate Exception subclass if the inputs are found to be invalid. These should be stored here by the initialisation function as private variables in suitably sanitised form. The core state machine object shall then be created and stored before the object is returned to the caller. """ # Event object to represent when this operation is "done" self._done_evt = Event() # Signal emitted when the operation is "done" self.done_sig = Signal(name="done", threadsafe=True) # Result returned by operation self._result = None self._result_copy = result_copy self._result_deepcopy = result_deepcopy def go(self): """ Start processing the operation. This is called by the caller (so after all __init__ functions have executed) in order to begin the asynchronous operation. """ # This needs to be implemented in the subclass. raise NotImplementedError("To be implemented in subclass %s" % self.__class__.__name__) def wait(self, timeout=None): """ Wait for an operation to finish. This should *NOT* be called in the same thread as the thread executing the operation as this will deadlock. """ self._done_evt.wait(timeout) @property def state(self): """ Return the current state machine's state. """ return self._state_machine.current @property def is_done(self): """ Return true if the operation is complete. """ return self._state_machine.is_finished() @property def is_failed(self): """ Return true if the result is an Exception. """ return isinstance(self._result, AsynchronousException) @property def result(self): """ Return the result of the operation or raise its exception. Raises NotReadyError if not ready. """ if not self.is_done: raise NotReadyError() if self.is_failed: self._result.reraise() if not self._result_copy: # Return the original instance (do not copy) return self._result elif self._result_deepcopy: # Return a deep copy return deepcopy(self._result) else: # Return a shallow copy return self._result.copy() def __repr__(self): """ Return a representation of this object's state. """ if self.is_failed: return "<%s failed>" % self.__class__.__name__ elif self.is_done: return "<%s done: %s>" % (self.__class__.__name__, self._result) else: return "<%s %s>" % (self.__class__.__name__, self.state) def _done(self, result): """ Return the result of the operation to any listeners. """ self._result = result self._done_evt.set() self.done_sig.emit(operation=self)
class TestSignal(object): def setup_method(self, method): self.signal_a = Signal(threadsafe=True) self.signal_b = Signal(args=['foo']) self.slot_a = mock.Mock(spec=lambda **kwargs: None) self.slot_a.return_value = None self.slot_b = mock.Mock(spec=lambda **kwargs: None) self.slot_b.return_value = None def test_is_connected(self, inspect): self.signal_a.connect(self.slot_a) assert self.signal_a.is_connected(self.slot_a) assert not self.signal_a.is_connected(self.slot_b) assert not self.signal_b.is_connected(self.slot_a) assert not self.signal_b.is_connected(self.slot_b) def test_emit_one_slot(self, inspect): self.signal_a.connect(self.slot_a) self.signal_a.emit() self.slot_a.assert_called_once_with() assert self.slot_b.call_count == 0 def test_emit_two_slots(self, inspect): self.signal_a.connect(self.slot_a) self.signal_a.connect(self.slot_b) self.signal_a.emit() self.slot_a.assert_called_once_with() self.slot_b.assert_called_once_with() def test_emit_one_slot_with_arguments(self, inspect): self.signal_b.connect(self.slot_a) self.signal_b.emit(foo='bar') self.slot_a.assert_called_once_with(foo='bar') assert self.slot_b.call_count == 0 def test_emit_two_slots_with_arguments(self, inspect): self.signal_b.connect(self.slot_a) self.signal_b.connect(self.slot_b) self.signal_b.emit(foo='bar') self.slot_a.assert_called_once_with(foo='bar') self.slot_b.assert_called_once_with(foo='bar') def test_reconnect_does_not_duplicate(self, inspect): self.signal_a.connect(self.slot_a) self.signal_a.connect(self.slot_a) self.signal_a.emit() self.slot_a.assert_called_once_with() def test_disconnect_does_not_fail_on_not_connected_slot(self, inspect): self.signal_a.disconnect(self.slot_b)
class DistantIO(): def __init__(self): # Init logging facility # From : http://sametmax.com/ecrire-des-logs-en-python/ logger = logging.getLogger() logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s') file_handler = RotatingFileHandler('api_log.log', 'a', 1000000, 1) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) logger.addHandler(file_handler) stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.DEBUG) logger.addHandler(stream_handler) # Signals self.signal_MCU_state_changed = Signal(args=['alive']) self.signal_received_descriptor = Signal(args=['var_id','var_type','var_name','var_writeable','group_id']) self.signal_received_group_descriptor = Signal(args=['group_id','group_name']) self.signal_received_value = Signal(args=['var_id']) self.distantio = distantio_protocol() self.protocol = Protocol(self.unused) # Queue holding received characters to be processed by worker process self.input_queue = mp.Queue() # Queue holding decoded frames self.output_queue = mp.Queue() # Conditions for controlling run process self.condition_new_rx_data = mp.Event() self.condition_new_rx_data.clear() self.condition_run_process = mp.Event() self.condition_run_process.clear() # Worker process for decoding characters self.producer_conn, self.consumer_conn = mp.Pipe() self.worker = Worker(self.input_queue,self.producer_conn,self.condition_new_rx_data,self.condition_run_process) self.worker.start() # Array containing buffers with MCU variables values self.variables_values = dict() # max size of the buffers self.buffer_length = 128 # Array containing last time each individual variable was updated self.last_variables_update = dict() # Min delay in seconds between two emit value received signal self.emit_signal_delay = 0.1 self.time_start = time.time() # Timer for monitoring MCU alive self.mcu_died_delay = 2.0 self.mcu_alive_timer = threading.Timer(self.mcu_died_delay,self.on_mcu_lost_connection) self.variable_list = dict() self.connected = False self.datalogger = Datalogger() # Start MCU timer self.mcu_alive_timer = threading.Timer(self.mcu_died_delay,self.on_mcu_lost_connection) self.mcu_alive_timer.start() logging.info('DistantIO API initialized successfully.') def decode_rx_data(self,data): self.input_queue.put(data) def export_data(self): self.signal_MCU_state_changed.emit(alive=False) self.connected = False logging.info('Disconnected successfully.') # Write emergency data to file self.datalogger.export() def terminate(self): self.mcu_alive_timer.cancel() if self.mcu_alive_timer.isAlive(): self.mcu_alive_timer.join() logging.info('Sending terminate signal to all threads.') self.condition_new_rx_data.set() self.condition_run_process.set() self.worker.join() logging.info("Worker process joined.") logging.info('Active thread count :'+str(threading.active_count())) for t in threading.enumerate(): logging.info('Thread :'+str(t)) logging.info('API terminated successfully.') ### Distant IO calls to MCU # Ask the MCU to return all descriptors def request_descriptors(self): logging.info('requested all descriptors to MCU.') frame = self.distantio.get_descriptors_frame() frame = self.protocol.encode(frame) return frame # Ask the MCU to write a variable def request_write(self, variable_id, data): if not variable_id in self.variable_list: logging.error("variable id provided "+str(variable_id)+" not found.") return # Check data is number try: # Cast to float and see if that fails dummy = float(data) except ValueError: logging.error("value provided "+str(data)+" not correct.") return logging.info('requested MCU to write '+str(data)+' to var id '+str(variable_id)+'.') frame = self.distantio.get_write_value_frame(variable_id,self.variable_list[variable_id]['type'],data) frame = self.protocol.encode(frame) return frame # Ask the MCU to read all variables def request_read_all(self): for key in self.variable_list: logging.info('requested to receive readings of var id '+str(key)+'.') frame = self.distantio.get_start_reading_frame(key,self.variable_list[key]['type']) frame = self.protocol.encode(frame) yield frame def update(self): # Check new decoded data is available available = self.consumer_conn.poll() if not available: return None instruction = self.consumer_conn.recv(False) # If distantio received a alive signal if instruction['type'] == "alive-signal": # Restart the timer self.mcu_alive_timer.cancel() self.mcu_alive_timer.join() self.mcu_alive_timer = threading.Timer(self.mcu_died_delay,self.on_mcu_lost_connection) self.mcu_alive_timer.start() self.signal_MCU_state_changed.emit(alive=True) # if returned-value elif instruction['type'] == 'returned-value': # Check var id is known, otherwise create a buffer for it if not instruction['var-id'] in self.variables_values: self.variables_values[instruction['var-id']] = ValuesXY(self.buffer_length) # Store value and time in sbuffer self.variables_values[instruction['var-id']].append(instruction['var-time'],instruction['var-value']) if not instruction['var-id'] in self.last_variables_update: self.last_variables_update[instruction['var-id']] = 0 current_time = time.time() elapsed_time = current_time - self.last_variables_update[instruction['var-id']] # Make sure the received-value signal was not emitted too ofter if elapsed_time > self.emit_signal_delay: self.last_variables_update[instruction['var-id']] = current_time self.signal_received_value.emit(var_id=instruction['var-id']) # if returned-descriptor elif instruction['type'] == 'returned-descriptor': self.variable_list[instruction['var-id']] = dict() self.variable_list[instruction['var-id']]['type'] = instruction['var-type'] self.variable_list[instruction['var-id']]['name'] = ['var-name'] self.variable_list[instruction['var-id']]['writeable'] = ['var-writeable'] logging.info('Received MCU variable descriptor with id '+str(instruction['var-id'])) if not instruction['var-id'] in self.variables_values: self.variables_values[instruction['var-id']] = ValuesXY(self.buffer_length) if not instruction['var-id'] in self.last_variables_update: self.last_variables_update[instruction['var-id']] = 0 self.signal_received_descriptor.emit(var_id=instruction['var-id'], var_type=instruction['var-type'], var_name=instruction['var-name'], var_writeable=instruction['var-writeable'], group_id=instruction['var-group']) elif instruction['type'] == 'returned-group-descriptor': self.signal_received_group_descriptor.emit(group_id=instruction['group-id'], group_name=instruction['group-name']) elif instruction['type'] == 'emergency-send': logging.warning("Received emergency data with user id "+str(instruction['data-id'])) self.datalogger.append(instruction['data-id'],instruction['data-time'],instruction['data-index'],instruction['data-value']) else: logging.error("Unknown instruction :"+str(instruction)) if count == maxamount and self.output_queue.qsize() > 200: logging.warning("Instruction queue not processed fast enough. Current size :"+str(self.output_queue.qsize())) ## Callbacks def on_mcu_lost_connection(self): self.signal_MCU_state_changed.emit(alive=False) def unused(self,frame): logging.error("Local protocol decoded frame "+str(frame)+" instead of Worker") # Getters setters def get_last_value(self, var_id): if not var_id in self.variables_values: raise IndexError("Variable ID "+str(instruction['var-id'])+" not found.") else: return self.variables_values[var_id].y[-1] def get_buffers_value(self,var_id): if not var_id in self.variables_values: raise IndexError("Variable ID "+str(instruction['var-id'])+" not found.") else: return self.variables_values[var_id]
class HaystackOperation(object): """ A core state machine object. This implements the basic interface presented for all operations in pyhaystack. """ def __init__(self, result_copy=True, result_deepcopy=True): """ Initialisation. This should be overridden by subclasses to accept and validate the inputs presented for the operation, raising an appropriate Exception subclass if the inputs are found to be invalid. These should be stored here by the initialisation function as private variables in suitably sanitised form. The core state machine object shall then be created and stored before the object is returned to the caller. """ # Event object to represent when this operation is "done" self._done_evt = Event() # Signal emitted when the operation is "done" self.done_sig = Signal(name='done', threadsafe=True) # Result returned by operation self._result = None self._result_copy = result_copy self._result_deepcopy = result_deepcopy def go(self): """ Start processing the operation. This is called by the caller (so after all __init__ functions have executed) in order to begin the asynchronous operation. """ # This needs to be implemented in the subclass. raise NotImplementedError("To be implemented in subclass %s" \ % self.__class__.__name__) def wait(self, timeout=None): """ Wait for an operation to finish. This should *NOT* be called in the same thread as the thread executing the operation as this will deadlock. """ self._done_evt.wait(timeout) @property def state(self): """ Return the current state machine's state. """ return self._state_machine.current @property def is_done(self): """ Return true if the operation is complete. """ return self._state_machine.is_finished() @property def is_failed(self): """ Return true if the result is an Exception. """ return isinstance(self._result, AsynchronousException) @property def result(self): """ Return the result of the operation or raise its exception. Raises NotReadyError if not ready. """ if not self.is_done: raise NotReadyError() if self.is_failed: self._result.reraise() if not self._result_copy: # Return the original instance (do not copy) return self._result elif self._result_deepcopy: # Return a deep copy return deepcopy(self._result) else: # Return a shallow copy return self._result.copy() def __repr__(self): """ Return a representation of this object's state. """ if self.is_failed: return '<%s failed>' % self.__class__.__name__ elif self.is_done: return '<%s done: %s>' % (self.__class__.__name__, self._result) else: return '<%s %s>' % (self.__class__.__name__, self.state) def _done(self, result): """ Return the result of the operation to any listeners. """ self._result = result self._done_evt.set() self.done_sig.emit(operation=self)
class FileListModel(QAbstractItemModel, pytson.Translatable): """ Itemmodel to abstract the files contained on a TS3 filepath. """ def __init__(self, schid, cid, password, parent=None, *, readonly=False): super(QAbstractItemModel, self).__init__(parent) self.schid = schid self.cid = cid self.password = password self.readonly = readonly self.pathChanged = Signal() self.error = Signal() self._path = None self.newpath = None self.files = [] self.newfiles = [] self.retcode = None self.renretcode = None self.renfile = () self.titles = [self._tr("Name"), self._tr("Size"), self._tr("Type"), self._tr("Last Changed")] PluginHost.registerCallbackProxy(self) def __del__(self): PluginHost.unregisterCallbackProxy(self) @property def path(self): return self._path @path.setter def path(self, val): self._reload(val) @property def currentFiles(self): return self.files def _reload(self, path=None): if path: self.newpath = path else: self.newpath = self._path self.retcode = ts3lib.createReturnCode() err = ts3lib.requestFileList(self.schid, self.cid, self.password, self.newpath, self.retcode) if err != ERROR_ok: _errprint(self._tr("Error requesting filelist"), err, self.schid, self.cid) def onFileListEvent(self, schid, channelID, path, name, size, date, atype, incompletesize, returnCode): if (schid != self.schid or channelID != self.cid or returnCode != self.retcode): return self.newfiles.append(File(path, name, size, date, atype, incompletesize)) def onFileListFinishedEvent(self, schid, channelID, path): if (schid != self.schid or channelID != self.cid or path != self.newpath): return # might be unneeded (event is catched in onServerErrorEvent) def onServerErrorEvent(self, schid, errorMessage, error, returnCode, extraMessage): if schid != self.schid: return if returnCode == self.retcode: if error in [ERROR_ok, ERROR_database_empty_result]: self.beginResetModel() self.files = self.newfiles self.newfiles = [] self.endResetModel() if self._path != self.newpath: self._path = self.newpath self.pathChanged.emit(self._path) else: self.error.emit(self._tr("Error requesting filelist"), error, errorMessage) elif returnCode == self.renretcode: if error != ERROR_ok: self.renfile[0].name = self.renfile[1] self.error.emit(self._tr("Error renaming file"), error, errorMessage) self.renfile = () def onServerPermissionErrorEvent(self, schid, errorMessage, error, returnCode, failedPermissionID): if schid != self.schid or returnCode != self.retcode: return if returnCode == self.retcode: if error != ERROR_ok: self.error.emit(self._tr("Error requesting filelist"), error, errorMessage) elif returnCode == self.renretcode: if error != ERROR_ok: self.renfile[0].name = self.renfile[1] self.error.emit(self._tr("Error renaming file"), error, errorMessage) self.renfile = () def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole and orientation == Qt.Horizontal: return self.titles[section] return None def flags(self, idx): f = Qt.ItemIsEnabled | Qt.ItemIsSelectable if not self.readonly: return f | Qt.ItemIsEditable else: return f def index(self, row, column, parent=QModelIndex()): if parent.isValid(): return QModelIndex() return self.createIndex(row, column) def parent(self, idx): return QModelIndex() def rowCount(self, parent=QModelIndex()): if parent.isValid(): return 0 return len(self.files) def columnCount(self, parent=QModelIndex()): return len(self.titles) def data(self, idx, role=Qt.DisplayRole): if not idx.isValid(): return None f = self.files[idx.row()] if idx.column() == 0: if role == Qt.DisplayRole: return f.name elif role == Qt.DecorationRole: return f.icon elif role == Qt.EditRole and not self.readonly: return f.name elif role == Qt.UserRole: if f.isDirectory: return "a%s" % f.name else: return "b%s" % f.name elif role == Qt.DisplayRole: if idx.column() == 1 and not f.isDirectory: return bytesToStr(f.size) elif idx.column() == 2: if f.isDirectory: return self._tr("Directory") else: return self._tr("File") elif idx.column() == 3: return f.datetime.strftime(pytson.tr("filetransfer", "%Y-%m-%d %H:%M:%S")) return None def setData(self, idx, value, role=Qt.EditRole): if not idx.isValid(): return False f = self.fileByIndex(idx) if value == f.name: return self.renretcode = ts3lib.createReturnCode() self.renfile = (f, f.name) err = ts3lib.requestRenameFile(self.schid, self.cid, self.password, 0, "", f.fullpath, joinpath(f.path, value), self.renretcode) if err == ERROR_ok: f.name = value return True def fileByIndex(self, idx): if idx.isValid(): return self.files[idx.row()] return None