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()))
Exemple #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
    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)
Exemple #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
Exemple #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)
Exemple #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)
Exemple #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
Exemple #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
Exemple #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
    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
Exemple #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'])
Exemple #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)
Exemple #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
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()