def test_slack_token(self): if not self.slacktoken_editor.text(): return try: import bookmarks.slacker as slacker except ImportError as err: common_ui.ErrorBox( u'Could not import SlackClient', u'The Slack API python module was not loaded:\n{}'.format(err), ).open() log.error('Slack import error.') raise client = slacker.Client(self.slacktoken_editor.text()) client.verify_token(silent=False) self.slacktoken_editor.setStyleSheet(u'color: rgba({});'.format( common.rgb(common.ADD))) pretty_response = u'Slack URL: {url}\nTeam: {team}'.format( url=client._response['url'], team=client._response['team'], ) common_ui.OkBox( u'Token is valid.', pretty_response, ).open()
def __init__(self, path, parent=None): global _viewer_instance _viewer_instance = self super(AlembicView, self).__init__(parent=parent) if not isinstance(path, unicode): raise ValueError( u'Expected <type \'unicode\'>, got {}'.format(type(path))) if not self.parent(): common.set_custom_stylesheet(self) file_info = QtCore.QFileInfo(path) if not file_info.exists(): s = '{} does not exists.'.format(path) common_ui.ErrorBox( u'Error viewing the alembic contents.', s).open() log.error(s) raise RuntimeError(s) self.path = path self.view = AlembicTree(path, parent=self) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setAttribute(QtCore.Qt.WA_NoSystemBackground) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setWindowFlags( QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint ) self._create_UI() self.view.setStyleSheet(u'QTreeView {{padding:{p}px; border-radius: {p}px; border: {s}px solid black;}}'.format( p=common.INDICATOR_WIDTH() * 2, s=common.ROW_SEPARATOR()))
def get_channels(self, silent=False): response = self.api_call(u'conversations.list', exclude_archived=True, types='public_channel,private_channel') if not response['ok']: s = u'Maybe a required scope is missing?\nError: "{}"'.format( response[u'error']) if 'needed' in response: s += '\nScope needed: "{}"'.format(response['needed']) log.error(s) if not silent: common_ui.ErrorBox(u'Slack Token Error.', s).open() raise ValueError(s) channels = [] for channel in response['channels']: if channel['is_archived']: continue if u'is_channel' in channel: if channel['is_channel']: channels.append(channel) if u'is_group' in channel: if channel['is_group']: channels.append(channel) return channels
def dropEvent(self, event): """Slack drop event""" try: import bookmarks.slacker as slacker except ImportError as err: common_ui.ErrorBox( u'Could not import SlackClient', u'The Slack API python module was not loaded:\n{}'.format(err), ).open() log.error('Slack import error.') return if event.source() == self: return # Won't allow dropping an item from itself mime = event.mimeData() if not mime.hasUrls(): return event.accept() message = [] for f in mime.urls(): file_info = QtCore.QFileInfo(f.toLocalFile()) line = u'```{}```'.format(file_info.filePath()) message.append(line) message = u'\n'.join(message) parent = self.parent().parent().stackedwidget index = parent.widget(0).model().sourceModel().active_index() if not index.isValid(): return widget = parent.currentWidget().show_slacker(index) widget.message_widget.append_message(message)
def push_to_rv(path): """Uses `rvpush` to view a given footage.""" import subprocess import bookmarks.log as log import bookmarks.common_ui as common_ui import bookmarks.settings as settings def get_preference(k): return settings.local_settings.value(u'preferences/{}'.format(k)) rv_path = get_preference(u'rv_path') if not rv_path: common_ui.MessageBox( u'Shotgun RV not found.', u'To push footage to RV, set RV\'s path in Preferences.').open() log.error(u'RV not set') return rv_info = QtCore.QFileInfo(rv_path) if not rv_info.exists(): common_ui.ErrorBox( u'Invalid Shotgun RV path set.', u'Make sure the currently set RV path is valid and try again!' ).open() log.error(u'Invalid RV path set') return if get_platform() == u'win': rv_push_path = u'{}/rvpush.exe'.format(rv_info.path()) if QtCore.QFileInfo(rv_push_path).exists(): cmd = u'"{RV}" -tag {PRODUCT} url \'rvlink:// -reuse 1 -inferSequence -l -play -fps 25 -fullscreen -nofloat -lookback 0 -nomb "{PATH}"\''.format( RV=rv_push_path, PRODUCT=PRODUCT, PATH=path) startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE subprocess.Popen(cmd, startupinfo=startupinfo) log.success(u'Footage sent to RV.') log.success(u'Command used:') log.success(cmd) else: common_ui.ErrorBox( u'Pushing to RV not yet implemented on this platform.', u'Sorry about this. Send me an email if you\'d like to see this work soon!' ).open() log.error(u'Function not implemented') return
def get_db(server, job, root): """Creates a saver a database controller associated with a bookmark. SQLite cannot share the same connection between different threads, hence we will create and cache the controllers per thread. Args: server (unicode): The name of the `server`. job (unicode): The name of the `job`. root (unicode): The name of the `root`. Returns: BookmarkDB: Database controller instance. Raises: RuntimeError: If the database is locked or impossible to open. """ if not isinstance(server, unicode): raise TypeError('Expected <type \'unicode\'>, got {}'.format( type(server))) if not isinstance(job, unicode): raise TypeError('Expected <type \'unicode\'>, got {}'.format( type(job))) if not isinstance(root, unicode): raise TypeError('Expected <type \'unicode\'>, got {}'.format( type(root))) t = unicode(repr(QtCore.QThread.currentThread())) key = (u'/'.join((server, job, root)) + t).lower() global DB_CONNECTIONS if key in DB_CONNECTIONS: return DB_CONNECTIONS[key] # The SQLite database can be locked for a brief period of time whilst it is # being used by another controller isntance. This normally will raise an # exception, but it is safe to wait on this a little and try again. n = 0 while True: if n > 100: import bookmarks.common_ui as common_ui # After 5 seconds we will give up and return `None` s = u'Unable to get the database.' s2 = u'{}/{}/{} might be locked'.format(server, job, root) log.error(s) common_ui.ErrorBox(s, s2).open() raise RuntimeError(s) try: # Create and cache the instance DB_CONNECTIONS[key] = BookmarkDB(server, job, root) return DB_CONNECTIONS[key] except RuntimeError: # Wait a little and try again n += 1 QtCore.QThread.msleep(50)
def verify_token(self, silent=False): """Tests the slack token and the permissions needed to send messages to channels. Using the Conversations API,the app requires any `channels:read` and/or `groups:read` to get channel lists. Addittionally, `chat.write` and `users.read` are *required* to send messages. """ # Checking the token's validity response = self.api_call(u'auth.test') if not response['ok']: s = u'Maybe a required scope is missing?.\nError: "{}"'.format( response[u'error']) log.error(s) if not silent: common_ui.ErrorBox(u'Slack Token Error.', s).open() raise ValueError(s) self._response = response # Check if we can read channels response = self.api_call(u'conversations.list') if not response['ok']: s = u'Maybe a required scope is missing?\nError: "{}"'.format( response[u'error']) log.error(s) if not silent: common_ui.ErrorBox(u'Slack Token Error.', s).open() raise ValueError(s) # Checking users scope for method in ('users.info', 'users.list'): response = self.api_call(method, user=self._response['user_id']) if not response['ok']: s = u'Maybe a required scope is missing?\nError: "{}"'.format( response[u'error']) if 'needed' in response: s += '\nScope needed: "{}"'.format(response['needed']) log.error(s) if not silent: common_ui.ErrorBox(u'Slack Token Error.', s).open() raise ValueError(s)
def export_favourites(): """Saves all favourites including the descriptions and the thumbnails.""" try: import uuid import bookmarks.settings as settings import bookmarks.bookmark_db as bookmark_db res = QtWidgets.QFileDialog.getSaveFileName( caption=u'Select where to save your favourites', filter=u'*.favourites', dir=QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.HomeLocation), ) destination, _ = res if not destination: return favourites = settings.local_settings.favourites() server, job, root = get_favourite_parent_paths() db = bookmark_db.get_db(server, job, root) zip_path = u'{}/{}/{}/{}.zip'.format(server, job, root, uuid.uuid4()) # Make sure the temp folder exists QtCore.QFileInfo(zip_path).dir().mkpath(u'.') with zipfile.ZipFile(zip_path, 'a') as z: # Adding thumbnail to zip for favourite in favourites: file_info = QtCore.QFileInfo(db.thumbnail_path(favourite)) if not file_info.exists(): continue z.write(file_info.filePath(), file_info.fileName()) z.writestr(u'favourites', u'\n'.join(favourites)) file_info = QtCore.QFileInfo(zip_path) if not file_info.exists(): raise RuntimeError( u'Unexpected error occured: could not find the favourites file' ) QtCore.QDir().rename(file_info.filePath(), destination) if not QtCore.QFileInfo(destination).exists(): raise RuntimeError( u'Unexpected error occured: could not find the favourites file' ) reveal(destination) except Exception as e: import bookmarks.log as log import bookmarks.common_ui as common_ui common_ui.ErrorBox(u'Could not save the favourites.', u'{}'.format(e)).open() log.error(u'Exporting favourites failed.') raise
def refresh(self): """Populates the list from the database.""" if not self.parent(): return if not self.index.isValid(): return if not self.index.data(common.FileInfoLoaded): return db = bookmark_db.get_db( self.index.data(common.ParentPathRole)[0], self.index.data(common.ParentPathRole)[1], self.index.data(common.ParentPathRole)[2]) if self.index.data(common.TypeRole) == common.FileItem: k = self.index.data(QtCore.Qt.StatusTipRole) elif self.index.data(common.TypeRole) == common.SequenceItem: k = common.proxy_path(self.index) v = db.value(k, u'notes') if not v: return try: v = base64.b64decode(v) d = json.loads(v) except: log.error(u'Error decoding notes from JSON') return if not v: return self.clear() keys = sorted(d.keys()) try: for k in keys: self.add_item(text=d[k][u'text'], checked=d[k][u'checked']) except: log.error(u'Error adding notes') common_ui.ErrorBox(u'Error refreshing the data', u'').open() raise
def _get_profiles(response): if not response['ok']: s = u'Maybe a required scope is missing?\nError: "{}"'.format( response[u'error']) if 'needed' in response: s += '\nScope needed: "{}"'.format(response['needed']) log.error(s) if not silent: common_ui.ErrorBox(u'Slack Token Error.', s).open() raise ValueError(s) _profiles = [] for member in response['members']: if member[u'deleted']: continue if member[u'is_app_user']: continue if member[u'is_bot']: continue _profiles.append(member) return _profiles
def init_database_values(self, compare=False): """Load the current settings from the database and apply them to the UI controls. """ def set_saved(k): v = db.value(1, k.replace(u'_editor', u''), table=u'properties') if not v: return if compare: return v getattr(self, k).setText(unicode(v) if v else u'') def emit_text(k): getattr(self, k).textEdited.emit(getattr(self, k).text()) controls = (u'framerate_editor', u'width_editor', u'height_editor', u'prefix_editor', u'startframe_editor', u'duration_editor', u'identifier_editor', u'slacktoken_editor') db = bookmark_db.get_db(self.server, self.job, self.root) d = {} with db.transactions(): try: for control in controls: d[control] = set_saved(control) if compare: continue emit_text(control) except Exception as e: common_ui.ErrorBox( u'Could not save the properties.', u'There seems to be an error with the database:\n{}'. format(e), ).open() log.error(u'Error saving properties to the database') raise return d
def send_message(self, channel, text): """Send message using slackclient. """ text = text.replace(u'&', u'&') # text = text.replace(u'<', u'<') # text = text.replace(u'>', u'>') response = self.api_call( 'chat.postMessage', channel=channel, text=text, mrkdwn=True, unfurl_media=True, unfurl_links=True, link_names=True, ) if not response[u'ok']: log.error(u'Failed to send message') common_ui.ErrorBox(u'Could not send message', response['error']).open() raise RuntimeError(response[u'error'])
def save_settings(self): """Saves the current list of todo items to the assets configuration file.""" if not self.index.isValid(): return data = {} for n in xrange(len(self.todoeditors_widget.items)): item = self.todoeditors_widget.items[n] editor = item.findChild(TodoItemEditor) checkbox = item.findChild(CheckBoxButton) if not editor.document().toPlainText(): continue data[n] = { u'checked': not checkbox.checked, u'text': editor.document().toHtml(), } k = common.proxy_path(self.index) db = bookmark_db.get_db( self.index.data(common.ParentPathRole)[0], self.index.data(common.ParentPathRole)[1], self.index.data(common.ParentPathRole)[2]) try: v = json.dumps(data, ensure_ascii=False, encoding='utf-8') v = base64.b64encode(v.encode('utf-8')) except: s = u'Error saving notes.' log.error(s) common_ui.ErrorBox(u'Error saving notes.', s).open() raise db.setValue(k, u'notes', v) todo_count = len([k for k in data if not data[k][u'checked']]) self.index.model().setData(self.index, todo_count, role=common.TodoCountRole)
def import_favourites(source=None): """Import a previously exported favourites file. Args: source (unicode): Path to a file. Defaults to `None`. """ try: import bookmarks.settings as settings import bookmarks.bookmark_db as bookmark_db if not isinstance(source, unicode): res = QtWidgets.QFileDialog.getOpenFileName( caption=u'Select the favourites file to import', filter=u'*.favourites' # options=QtWidgets.QFileDialog.ShowDirsOnly ) source, _ = res if not source: return current_favourites = settings.local_settings.favourites() create_temp_dir() with zipfile.ZipFile(source) as zip: namelist = zip.namelist() namelist = [f.lower() for f in namelist] if u'favourites' not in namelist: import bookmarks.log as log import bookmarks.common_ui as common_ui s = u'The favourites list is missing from the archive.' common_ui.ErrorBox( u'Invalid ".favourites" file', s, ).open() log.error(s) raise RuntimeError(s) with zip.open(u'favourites') as f: favourites = f.readlines() favourites = [unicode(f).strip().lower() for f in favourites] server, job, root = get_favourite_parent_paths() db = bookmark_db.get_db(server, job, root) for favourite in favourites: file_info = QtCore.QFileInfo(db.thumbnail_path(favourite)) if file_info.fileName().lower() in namelist: dest = u'{}/{}/{}/.bookmark'.format(server, job, root) zip.extract(file_info.fileName(), dest) if favourite not in current_favourites: current_favourites.append(favourite) current_favourites = sorted(list(set(current_favourites))) settings.local_settings.setValue(u'favourites', current_favourites) except Exception as e: import bookmarks.log as log import bookmarks.common_ui as common_ui common_ui.ErrorBox(u'Could not import the favourites.', u'{}'.format(e)).open() log.error(u'Import favourites failed.') raise
def check(): """Checks the latest release tag on Github and compares it with the current version number. """ # First let's check if there's a valid internet connection try: r = urllib2.urlopen(u'http://google.com', timeout=5) except urllib2.URLError: common_ui.ErrorBox( u'Could not check version', u'Internet connection seems to be down.', ).open() log.error(u'Internet connection seems to be down.') raise except socket.timeout: common_ui.ErrorBox( u'Could not check version', u'Connection has timed out.', ).open() log.error(u'Connection has timed out.') raise try: r = urllib2.urlopen(URL, timeout=5) data = r.read() except urllib2.URLError as err: common_ui.ErrorBox(u'Could not check version', u'{}'.format(err)).open() log.error(u'Error checking the version.') raise except socket.timeout as err: common_ui.ErrorBox(u'Could not check version', u'{}'.format(err)).open() log.error(u'Error checking the version.') raise except RuntimeError as err: common_ui.ErrorBox(u'Could not check version', u'{}'.format(err)).open() log.error(u'Error checking the version.') raise code = r.getcode() if not (200 <= code <= 300): s = u'# Error {}. "{}" {}'.format(code, URL, responses[code]) common_ui.ErrorBox(u'Could not check version', s).open() log.error(s) raise RuntimeError(s) # Convert json to dict try: data = json.loads(data) except Exception as err: s = u'# Error {}. "{}" {}'.format(code, URL, responses[code]) common_ui.ErrorBox( u'Error occured loading the server response.\n{}'.format(err), s).open() log.error(s) raise tags = [(version.parse(f[u'tag_name']).release, f) for f in data] if not tags: common_ui.MessageBox( u'No versions are available', u'Could not find any releases. Maybe no release has been published yet.' ).open() return # Getting the latest version latest = max(tags, key=lambda x: x[0]) current_version = version.parse(bookmarks.__version__) latest_version = version.parse(latest[1][u'tag_name']) # We're good and there's not need to update if current_version >= latest_version: common_ui.OkBox(u'Already up-to-date!', u'').open() return mbox = QtWidgets.QMessageBox() mbox.setWindowTitle(u'A new update is available') mbox.setWindowFlags(QtCore.Qt.FramelessWindowHint) mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) mbox.setText(u'There is a new version of Bookmarks available.') mbox.setText( u'Your current version is {} and the latest available version is {}.\nDo you want to download the new version?' .format(current_version, latest_version)) res = mbox.exec_() if res == QtWidgets.QMessageBox.No: return # Getting the packages... downloads_folder = QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.DownloadLocation) progress_widget = QtWidgets.QProgressDialog(u'Downloading installer...', u'Cancel download', 0, 0) progress_widget.setWindowTitle(u'Downloading...') progress_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint) # On windows, we will download the asset to the user downloads folder if common.get_platform() == u'win': asset = next( (f for f in latest[1][u'assets'] if f[u'name'].endswith(u'exe')), None) if common.get_platform() == u'mac': asset = next( (f for f in latest[1][u'assets'] if f[u'name'].endswith(u'zip')), None) # We will check if a file exists already... file_info = QtCore.QFileInfo(u'{}/{}'.format(downloads_folder, asset['name'])) _file_info = file_info # Rename our download if the file exists already idx = 0 while _file_info.exists(): idx += 1 _file_info = QtCore.QFileInfo(u'{}/{} ({}).{}'.format( file_info.path(), file_info.completeBaseName(), idx, file_info.completeSuffix(), )) file_info = _file_info file_path = os.path.abspath(os.path.normpath(file_info.absoluteFilePath())) with open(file_path, 'wb') as f: response = urllib2.urlopen(asset[u'browser_download_url'], timeout=5) total_length = response.headers['content-length'] if total_length is None: # no content length header progress_widget.setMaximum(0) progress_widget.forceShow() QtWidgets.QApplication.instance().processEvents() f.write(response.content) progress_widget.close() return else: progress_widget.setMaximum(100) current_length = 0 progress_widget.forceShow() while True: QtWidgets.QApplication.instance().processEvents() data = response.read(4096) if not data: break if not progress_widget.wasCanceled(): current_length += len(data) f.write(data) progress_widget.setValue( (float(current_length) / float(total_length)) * 100) else: f.close() if os.path.exists(file_path): os.remove(file_path) if not progress_widget.wasCanceled(): import subprocess cmd = u'"{}"'.format(file_path) subprocess.Popen(cmd) common.reveal(file_path) progress_widget.close() progress_widget.deleteLater()