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()
Esempio n. 2
0
    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()))
Esempio n. 3
0
    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
Esempio n. 4
0
    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)
Esempio n. 5
0
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
Esempio n. 6
0
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)
Esempio n. 7
0
    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)
Esempio n. 8
0
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
Esempio n. 9
0
    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
Esempio n. 10
0
        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
Esempio n. 11
0
    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
Esempio n. 12
0
    def send_message(self, channel, text):
        """Send message using slackclient.

        """
        text = text.replace(u'&', u'&amp')
        # text = text.replace(u'<', u'&lt')
        # text = text.replace(u'>', u'&gt')
        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'])
Esempio n. 13
0
    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)
Esempio n. 14
0
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
Esempio n. 15
0
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()