def accept(self): # Override accept so we can first validate if self.is_valid(): category_name = self.addEditCatLineEdit.text().strip() try: with session_scope() as session: if self.edit_cat_id is not None: category = session.query(Category).get( int(self.edit_cat_id)) category.category_name = category_name logger.debug('Edited cat with id %s' % self.edit_cat_id) else: category = Category(category_name=category_name) session.add(category) logger.debug('Added cat with name %s' % category_name) except exc.IntegrityError as int_exc: logger.debug(int_exc) QMessageBox.warning(self, "Already exists warning", unicode('This category already exists')) self.addEditCatLineEdit.setFocus() self.selectAll() return except Exception as uexc: logger.error(str(uexc)) QMessageBox.warning(self, "Unexpected Error", unicode('Could not edit category.')) return else: # All good, accept after triggering tree refresh with sig self.categories_changed.emit() QDialog.accept(self)
def init_list(self): ''' Build list widget according to db ''' # Init all_categories = [] selected_categories = [] try: with session_scope() as session: all_categories = session.query(Category.category_name).order_by(Category.category_name).all() all_categories = [c[0] for c in all_categories] if self.edit_reminder_id is not None: reminder = session.query(Reminder).get(int(self.edit_reminder_id)) selected_categories = [category.category_name for category in reminder.categories] except Exception as cat_exc: QtGui.QMessageBox.warning(self, "Unexpected error", unicode('Could not init categories list')) logger.error(str(cat_exc)) else: logger.debug('All categories is %s' % all_categories) logger.debug('Selected categories is %s' % selected_categories) for category in all_categories: # Add to list widget item = QtGui.QListWidgetItem(category, self.manageRemCatsListWidget) if category in selected_categories: item.setSelected(True) # Ensure ordered self.manageRemCatsListWidget.sortItems()
def areadCSVLinks(self): '''read input CSV file. File MUST be structured either: preferred = *kwargs,Name,Link || optional = *kwargs,Link''' seasons = self.seasons with open(self.csvinput, 'r', newline='', encoding='latin-1') as csvread: reader = csv.reader(csvread) playerdict = {} # define a basic dict to pass csv information into i = 0 for row in reader: if i < 1: # define headers self.header = [str(i+1) for i in range(len(row))] # handle kwargs as header - assign number self.header[-2] = "Name" self.header[-1] = "Link" name,link = row[-2:] # select last two items try: gamertag = link.split('/')[-1] # last item in link is gamertag platform = link.split('/')[-2] # item before gamertag is platform except IndexError: logger.error("Gamertag:%(name)s Link:%(link)s is not formatted properly" % locals()) else: playerdict[gamertag] = {} # define dict for each gamertag and values for that gamertag a = 0 for item in row: # handle kwargs if len(row) - a > 2: playerdict[gamertag][a] = item a += 1 playerdict[gamertag]['platform'] = platform playerdict[gamertag]['name'] = name playerdict[gamertag]['link'] = link i += 1 return playerdict
def refresh_tree(self): ''' Re-build the tree from database. Record the current category item, and if it is still present after the re-build set it as the current, else set 'Upcoming' category as current. Note that the root is the "Categories" item, the first 3 of its children are static, mandatory categories, "Upcoming", "Complete" and "Uncategorized" that don't come from the database - they are fixed. We only delete the custom user categories that come after these... ''' # Record the current category old_category = self.mainTreeWidget.currentItem() if old_category is not None: old_category = old_category.text( self.mainTreeWidget.currentColumn()) # Reload all the categories from the database categories = [] try: with session_scope() as session: categories = session.query(Category.category_name).order_by( Category.category_name).all() categories = [c[0] for c in categories] except Exception as uexc: logger.error(str(uexc)) QtGui.QMessageBox.error(self, 'Unexpected error', 'Could not select query categories.') return logger.debug('All categories %s' % categories) # Rebuild the tree widget with the reloaded categories root = self.mainTreeWidget.topLevelItem(0) root.setExpanded(True) # Clear custom categories, reverse important to not mess up interator # First 3 kids are static, don't delete. for i in reversed(range(root.childCount())): if i > 2: root.removeChild(root.child(i)) # Set 'Upcoming' selected by default but set old current cat as current if # it still exists all_item = root.child(0) self.mainTreeWidget.setCurrentItem(all_item) for category in categories: cat_child = QtGui.QTreeWidgetItem() cat_child.setText(0, category) # Icon cat_icon = QtGui.QIcon() cat_icon.addPixmap(QtGui.QPixmap(":/icons/icons/play-button.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) cat_child.setIcon(0, cat_icon) root.addChild(cat_child) if category == old_category: logger.debug('Set %s as current category item' % category) self.mainTreeWidget.setCurrentItem(cat_child)
def export_action_triggered(self): """Database export handler""" # Build JSON jdump = { 'timestamp': arrow.utcnow().timestamp, 'reminders': [], 'categories': [] } try: with session_scope() as session: for r in session.query(Reminder).all(): rdict = r.as_dict() rdict['category_ids'] = [ c.category_id for c in r.categories ] jdump['reminders'].append(rdict) jdump['categories'] = [ c.as_dict() for c in session.query(Category).all() ] except Exception as xp_exc: logger.error(str(xp_exc)) QtGui.QMessageBox(self, "Unexpected Exception", "Could not export to file") return dbFile = QtGui.QFileDialog.getSaveFileName( parent=None, caption="Export database to a file", directory=".", filter="QTierna JSON (*.json)") if dbFile[0]: try: with io.open(dbFile[0], 'w', encoding='utf-8') as f: logger.debug(jdump) f.write(json.dumps(jdump, ensure_ascii=False)) msg = ( "Successfully exported %i reminders and %i categories to a file\r\n%s" % (len(jdump['reminders']), len(jdump['categories']), (QtCore.QDir.toNativeSeparators(dbFile[0])))) QtGui.QMessageBox.information(self, __appname__, msg) except Exception as xportexc: QtGui.QMessageBox.critical( self, __appname__, "Error exporting file, error is\r\n" + str(xportexc)) return
def readCSVLinks(self): '''read input CSV file. File MUST be structured either: Link || *kwargs,Link || *kwargs,Name,Link''' scrape = Webscrape() seasons = self.seasons with open(self.csvinput, newline='', encoding="ISO-8859-1") as csvread: reader = csv.reader(csvread) i = 0 for row in reader: if i < 1: self.header = [str(i + 1) for i in range(len(row))] self.header[-2] = "ID" self.header[-1] = "Link" self.rows.append(row) i += 1 else: self.rows.append(row) i += 1 row_count = sum(1 for row in self.rows) i = 1 for row in tqdm(self.rows, total=row_count): name, link = row[-2:] # select last two items try: gamertag = link.split('/')[-1] # last item in link is gamertag platform = link.split('/')[ -2] # item before gamertag is platform except IndexError: logger.error( "Gamertag:%(name)s Link:%(link)s is not formatted properly" % locals()) i += 1 else: newrow = [] data = scrape.retrieveDataRLTracker(gamertag=gamertag, platform=platform, seasons=seasons, tiertf=self.tiertf) newrow = self._dictToList(data) a = 0 for item in row: #account for kwargs newrow.insert(a, item) a += 1 self.newrows.append(newrow) i += 1 self.writeCSV()
def _testSeason(self, season): '''True/False to see if season is valid 1) is a number, 2) is a valid season less than current number, 3) if current season in list, just pass''' latestseason = self.latestseason try: if not season.isdigit(): raise NameError if int(season) > int(latestseason): raise ValueError if int(season) == int(latestseason): return False except ValueError: logger.error( "Season:%(season)s was higher than Current Season:%(latestseason)s" % locals()) return False except NameError: logger.error("Season:%(season)s was not a number" % locals()) return False else: return True
def accept(self, edit=False): ''' Save the new/edited Reminder if valid ''' if self.is_valid(): try: category_names = [] if self.categoriesDlg is not None: # User edited categories we must update category_names = self.categoriesDlg._get_selected_categories( ) logger.debug('Selected categories were %s' % category_names) with session_scope() as session: category_instances = [] if category_names: category_instances = session.query(Category).filter( Category.category_name.in_(category_names)).all() if self.edit_reminder_id: reminder = session.query(Reminder).get( int(self.edit_reminder_id)) else: reminder = Reminder() reminder.due = self._get_reminder_utc_datetime_str() reminder.complete = self._() reminder.note = self._get_reminder_note() if category_instances: reminder.categories = category_instances session.add(reminder) except exc.IntegrityError as int_exc: # Rollback already handled by scoped_session ctx manager logger.error(int_exc) QtGui.QMessageBox.warning( self, "Already exists warning", unicode('This reminder already exists')) return else: # All good, accept QtGui.QDialog.accept(self)
def __init__(self, edit_cat_id=None, parent=None): super(AddEditCatDialog, self).__init__(parent) self.setupUi(self) # If editing self.edit_cat_id = edit_cat_id if self.edit_cat_id: try: with session_scope() as session: category = session.query(Category).get( int(self.edit_cat_id)) self.addEditCatLineEdit.setText(category.category_name) except Exception as cexc: logger.error(str(cexc)) QMessageBox.warning(self, "Unexpected Error", unicode('Could not set category name.')) return # Preventitive validation self.addEditCatLineEdit.setMaxLength(50) self.addEditCatButtonBox.button( QDialogButtonBox.Save).setEnabled(False) self.addEditCatLineEdit.textChanged.connect(self.disableAddButton)
def delete_cats_btn_pressed(self): ''' Delete selected categories. Preventitive validation (enable/disable delete button) means always some categories selected if made it here ''' # Get all checked in listviewwidget # delete from db selected_items = self.manageRemCatsListWidget.selectedItems() selected_category_names = [item.text() for item in selected_items] logger.debug('Got %i categories for deletion..' % len(selected_category_names)) try: with session_scope() as session: session.query(Category).filter(Category.category_name.in_(selected_category_names)).delete(synchronize_session='fetch') except Exception as del_exc: QtGui.QMessageBox.warning(self, "Unexpected error", unicode('Could not delete categories')) logger.error(str(del_exc)) else: logger.debug('Deleted %s' % selected_category_names) self.categories_changed.emit() # Delete these items from the list widget for selected_item in selected_items: logger.debug('Removing item %s from list widget' % selected_item) self.manageRemCatsListWidget.takeItem(self.manageRemCatsListWidget.row(selected_item))
def launch_notification(self, reminder_id): due = note = None try: with session_scope() as session: reminder = session.query(Reminder).get(int(reminder_id)) due = reminder.due note = reminder.note reminder.complete = True except Exception as uexc: logger.error(str(uexc)) QtGui.QMessageBox(self, 'Unexpected exception', 'Could not mark due reminder as complete') return # Get local datetime for output to user and format note as html local_due = dt2str(utcstr2local(due, self.time_zone, date_format='%Y-%m-%d %H:%M'), date_format='%d %b %I:%M%p') htmlcontent = '<p>%s</p>' % note # QApplication.instance().beep() # if QtGui.QSound.isAvailable(): # # Seems I would have to recompile with NAS support, but # # what does that mean for python when pyside was pip installed?? # QtGui.QSound.play("media/alarm_beep.wav") media = Phonon.MediaObject() audio = Phonon.AudioOutput(Phonon.MusicCategory) Phonon.createPath(media, audio) # alarm_file = os.path.join(os.getcwd(), 'media/alarm_beep.wav') alarm_file = resource_path('alarm_beep.wav') logger.debug('Trying to open alarm file...%s' % alarm_file) f = QtCore.QFile(alarm_file) if f.exists(): source = Phonon.MediaSource(alarm_file) if source.type() != -1: # -1 stands for invalid file media.setCurrentSource(source) media.play() else: logger.debug('Alert media missing: %s' % alarm_file) # Systray notification self.tray_icon.showMessage(unicode('Reminder due at %s' % local_due), smart_truncate(note, length=100), QtGui.QSystemTrayIcon.Information, 5000) # Dialog notification self.show() dlg = NotificationDialog() dlg.notificationTextBrowser.setHtml(htmlcontent) dlg.dtLabel.setText(local_due) dlg.setWindowTitle(unicode('Due at %s' % local_due)) # Change std buttons to "Reschedule" and "Mark Complete". # Resched will set complete=False and launch the edit reminder with # time selected. "Mark Complete" does nothing, since we already # marked complete to prevent further popups dlg.notificationButtonBox.button( QtGui.QDialogButtonBox.Ok).setText('Mark Complete') dlg.notificationButtonBox.button( QtGui.QDialogButtonBox.Cancel).setText('Reschedule') if dlg.exec_(): logger.debug( 'User wants to close dlg and keep the reminder as completed') else: # Launch edit reminder logger.debug('User wants to reschedule') self.addedit_rem_action_triggered(reminder_id=reminder_id) # Refresh table to account for this reminder completion self.refresh_table()
def retrieveDataRLTracker(self, gamertag="memlo", platform="steam", seasons=["12"], tiertf=False): '''Python BeautifulSoup4 Webscraper to https://rocketleague.tracker.network/ to retrieve gamer data''' playlistdict = { 0: 'Un-Ranked', 10: 'Ranked Duel 1v1', 11: 'Ranked Doubles 2v2', 12: 'Ranked Solo Standard 3v3', 13: 'Ranked Standard 3v3', 27: 'Hoops', 28: 'Rumble', 29: 'Dropshot', 30: 'Snowday' } latestseason = self.latestseason webpathmmr = self.webpathmmr webpath = self.webpath rltrackermissing = self.rltrackermissing psyonixdisabled = self.psyonixdisabled playerdata = {} # define the playerdata dict playerdata[gamertag] = {} # define the gamertag dict if '[]' in seasons: logger.warning( "Season was not set - you should never see this error") page = requests.get("%(webpath)s/%(platform)s/%(gamertag)s" % locals()) if page.status_code == 200: soup = BeautifulSoup(page.content, features="lxml") if soup(text=re.compile(rltrackermissing) ): # find "we could not find your stats" on webpage logger.critical( "Player Missing - URL:%(webpath)s/%(platform)s/%(gamertag)s" % locals()) elif soup( text=re.compile(psyonixdisabled) ): # find "Psyonix has disabled the Rocket League API" on webpage logger.critical( "Psyonix Disabled API - URL:%(webpath)s/%(platform)s/%(gamertag)s" % locals()) else: if latestseason in seasons: playerdata[gamertag][latestseason] = { } #define the season dict pagemmr = requests.get( "%(webpathmmr)s/%(platform)s/%(gamertag)s" % locals()) if pagemmr.status_code == 200: soupmmr = BeautifulSoup(pagemmr.content, features="lxml") # for every playlist, create a record of data # on the website, grab the 'div' for specific playlist for numrank, playlist in playlistdict.items(): try: soupmmr.find('a', { "data-id": numrank }).find('span').text except: playerdata[gamertag][latestseason][ playlist] = None else: playerdata[gamertag][latestseason][ playlist] = {} #define the playlist dict playerdata[gamertag][latestseason][playlist][ 'MMR'] = soupmmr.find( 'a', { "data-id": numrank }).find('span').text playerdata[gamertag][latestseason][playlist][ 'Games Played'] = soupmmr.find( 'div', { "data-id": numrank }).find('div').find('span').text playerdata[gamertag][latestseason][playlist][ 'Rank'] = soupmmr.find( 'div', { "data-id": numrank }).select('div > span')[2].text playerdata[gamertag][latestseason][playlist][ 'Rank Division'] = soupmmr.find( 'div', { "data-id": numrank }).select('div > h4')[2].text if tiertf == True: try: scripttags = soupmmr.findAll( 'script', type='text/javascript' ) #grab all <script> tags except: logger.error( "Finding <script/> tags in website:%(webpathmmr)s/%(platform)s/%(gamertag)s" % locals()) else: scripttags = soupmmr.findAll( 'script', type='text/javascript' ) #grab all <script> tags for script in scripttags: #find the data we care about if 'var data' in script.text: a = script.text.replace( ' ', '').replace('\n', '').replace( '\r', '').split(';') data = {} for blob in a[1:6]: b = re.split('\w+.:', blob) if '[]' in b[ 2] and 'Un-Ranked' not in b[ 2]: #if there aren't any dates listed, except in Un-Ranked #logger.error("Issue for player:%s in season:%s with dates:%s and tier:%s in playlist:%s" % (gamertag,latestseason,b[2],b[4],b[1])) # using locals with dict[k] doesn't work :/ continue else: if 'Un-Ranked' in b[1]: playlist = 'Un-Ranked' elif 'RankedDuel1v1' in b[1]: playlist = 'Ranked Duel 1v1' elif 'RankedDoubles2v2' in b[ 1]: playlist = 'Ranked Doubles 2v2' elif 'RankedSoloStandard3v3' in b[ 1]: playlist = 'Ranked Solo Standard 3v3' elif 'RankedStandard3v3' in b[ 1]: playlist = 'Ranked Standard 3v3' else: playlist = '' #dates = list(filter(None, re.split('\[|,|\]|\'',b[2]))) #futureproof #rating_over_time' = list(filter(None, re.split('\[|,|\]',b[3]))) #futureproof tier_over_time = list( filter( None, re.split( '\[|,|\]|\}', b[4]))) if tier_over_time is not None: playerdata[gamertag][ latestseason][playlist][ 'Tier'] = tier_over_time[ -1] else: playerdata[gamertag][ latestseason][ playlist][ 'Tier'] = None if '[]' not in seasons: for season in seasons: if self._testSeason(season): playerdata[gamertag][season] = { } #define the season dict seasonid = "season-%(season)s" % locals() try: seasontable = soup.find(id=seasonid).select( 'table > tbody')[0].select('tr')[1:] except: logger.error( "Finding season:%(season)s data for gamertag:%(gamertag)s" % locals()) else: seasontable = soup.find(id=seasonid).select( 'table > tbody')[0].select('tr') playerdata[gamertag][ season] #define the playlist dict for tabledata in seasontable: td = tabledata.find_all('td') listdata = [] for x in td: data = x.text.strip().split('\n') listdata = listdata + data try: blank1, playlist, blank2, writtenrank, mmr, gamesplayed = listdata except ValueError: logger.error( "Error parsing:%(listdata)s season:%(season)s data for gamertag:%(gamertag)s" % locals()) else: blank1, playlist, blank2, writtenrank, mmr, gamesplayed = listdata playerdata[gamertag][season][ playlist] = { } #define the playlist dict playerdata[gamertag][season][playlist][ 'MMR'] = mmr playerdata[gamertag][season][playlist][ 'Games Played'] = gamesplayed #if self.playerdata.get(gamertag) != {}: return playerdata