Esempio n. 1
0
    def __init__(self,
                 config_name: str,
                 app_key: str = DROPBOX_APP_KEY) -> None:

        self._app_key = app_key
        self._config_name = config_name

        self._conf = MaestralConfig(config_name)
        self._state = MaestralState(config_name)

        self._auth_flow = DropboxOAuth2FlowNoRedirect(
            self._app_key,
            use_pkce=True,
            token_access_type=self.default_token_access_type,
        )

        self._account_id = self._conf.get("account", "account_id") or None
        self._token_access_type = (self._state.get(
            "account", "token_access_type") or None)

        self.keyring = self._get_keyring_backend()

        # defer keyring access until token requested by user
        self.loaded = False
        self._access_token: Optional[str] = None
        self._refresh_token: Optional[str] = None
        self._expires_at: Optional[datetime] = None
Esempio n. 2
0
    def __init__(self, config_name='maestral', run=True, log_to_stdout=False):

        self._daemon_running = True
        self._log_to_stdout = log_to_stdout

        self._config_name = config_name
        self._conf = MaestralConfig(self._config_name)
        self._state = MaestralState(self._config_name)

        self._setup_logging()

        self.client = MaestralApiClient(self._config_name)
        self.monitor = MaestralMonitor(self.client)
        self.sync = self.monitor.sync

        # periodically check for updates and refresh account info
        self.update_thread = Thread(
            name='maestral-update-check',
            target=self._periodic_refresh,
            daemon=True,
        )
        self.update_thread.start()

        if run:
            self.run()
Esempio n. 3
0
def remove_configuration(config_name):
    """
    Removes all config and state files associated with the given configuration.

    :param str config_name: The configuration to remove.
    """

    MaestralConfig(config_name).cleanup()
    MaestralState(config_name).cleanup()
    index_file = get_data_path('maestral', f'{config_name}.index')
    delete(index_file)
Esempio n. 4
0
def _check_for_updates():
    """Checks if updates are available by reading the cached release number from the
    config file and notifies the user."""
    from packaging.version import Version
    from maestral import __version__

    state = MaestralState('maestral')
    latest_release = state.get('app', 'latest_release')

    has_update = Version(__version__) < Version(latest_release)

    if has_update:
        click.secho(
            f'Maestral v{latest_release} has been released, you have v{__version__}. '
            f'Please use your package manager to update.',
            fg='red')
Esempio n. 5
0
def configs() -> None:

    # clean up stale configs
    config_names = list_configs()

    for name in config_names:
        dbid = MaestralConfig(name).get("account", "account_id")
        if dbid == "" and not is_running(name):
            remove_configuration(name)

    # display remaining configs
    names = list_configs()
    emails = [MaestralState(c).get("account", "email") for c in names]

    click.echo("")
    click.echo(
        format_table(columns=[names, emails], headers=["Config name", "Account"])
    )
    click.echo("")
Esempio n. 6
0
    def __init__(self, config_name='maestral', timeout=_timeout):

        self.config_name = config_name

        self._state = MaestralState(config_name)

        # get Dropbox session
        self.auth = OAuth2Session(config_name)
        if not self.auth.load_token():
            self.auth.link()
        self._timeout = timeout
        self._last_longpoll = None
        self._backoff = 0
        self._retry_count = 0

        # initialize API client
        self.dbx = dropbox.Dropbox(self.auth.access_token,
                                   session=SESSION,
                                   user_agent=USER_AGENT,
                                   timeout=self._timeout)
Esempio n. 7
0
def configs():
    """Lists all configured Dropbox accounts."""
    from maestral.daemon import get_maestral_pid
    from maestral.utils.backend import remove_configuration

    # clean up stale configs
    config_names = list_configs()

    for name in config_names:
        dbid = MaestralConfig(name).get('account', 'account_id')
        if dbid == '' and not get_maestral_pid(name):
            remove_configuration(name)

    # display remaining configs
    names = list_configs()
    emails = [MaestralState(c).get('account', 'email') for c in names]

    click.echo('')
    click.echo(
        format_table(columns=[names, emails],
                     headers=['Config name', 'Account']))
    click.echo('')
Esempio n. 8
0
def check_for_updates() -> None:
    """
    Checks if updates are available by reading the cached release number from the
    config file and notifies the user. Prints an update note to the command line.
    """
    from packaging.version import Version

    conf = MaestralConfig("maestral")
    state = MaestralState("maestral")

    interval = conf.get("app", "update_notification_interval")
    last_update_check = state.get("app", "update_notification_last")
    latest_release = state.get("app", "latest_release")

    if interval == 0 or time.time() - last_update_check < interval:
        return

    has_update = Version(__version__) < Version(latest_release)

    if has_update:
        click.echo(
            f"Maestral v{latest_release} has been released, you have v{__version__}. "
            f"Please use your package manager to update."
        )
Esempio n. 9
0
    def __init__(self, config_name='maestral', pending_link=True, parent=None):
        super().__init__(parent=parent)
        # load user interface layout from .ui file
        uic.loadUi(SETUP_DIALOG_PATH, self)
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        self._config_name = config_name
        self._conf = MaestralConfig(config_name)
        self._state = MaestralState(config_name)

        self.app_icon = QtGui.QIcon(APP_ICON_PATH)

        self.labelIcon_0.setPixmap(icon_to_pixmap(self.app_icon, 150))
        self.labelIcon_1.setPixmap(icon_to_pixmap(self.app_icon, 70))
        self.labelIcon_2.setPixmap(icon_to_pixmap(self.app_icon, 70))
        self.labelIcon_3.setPixmap(icon_to_pixmap(self.app_icon, 120))

        self.mdbx = None
        self.dbx_model = None
        self.excluded_items = []

        # resize dialog buttons
        width = self.pushButtonAuthPageCancel.width()*1.1
        for b in (self.pushButtonAuthPageLink, self.pushButtonDropboxPathUnlink,
                  self.pushButtonDropboxPathSelect, self.pushButtonFolderSelectionBack,
                  self.pushButtonFolderSelectionSelect, self.pushButtonAuthPageCancel,
                  self.pushButtonDropboxPathCalcel, self.pushButtonClose):
            b.setMinimumWidth(width)
            b.setMaximumWidth(width)

        # set up combobox
        self.dropbox_location = osp.dirname(self._conf.get('main', 'path')) or get_home_dir()
        relative_path = self.rel_path(self.dropbox_location)

        folder_icon = get_native_item_icon(self.dropbox_location)
        self.comboBoxDropboxPath.addItem(folder_icon, relative_path)

        self.comboBoxDropboxPath.insertSeparator(1)
        self.comboBoxDropboxPath.addItem(QtGui.QIcon(), 'Other...')
        self.comboBoxDropboxPath.currentIndexChanged.connect(self.on_combobox)
        self.dropbox_folder_dialog = QtWidgets.QFileDialog(self)
        self.dropbox_folder_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
        self.dropbox_folder_dialog.setFileMode(QtWidgets.QFileDialog.Directory)
        self.dropbox_folder_dialog.setOption(QtWidgets.QFileDialog.ShowDirsOnly, True)
        self.dropbox_folder_dialog.fileSelected.connect(self.on_new_dbx_folder)
        self.dropbox_folder_dialog.rejected.connect(
                lambda: self.comboBoxDropboxPath.setCurrentIndex(0))

        # connect buttons to callbacks
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.pushButtonLink.clicked.connect(self.on_link)
        self.pushButtonAuthPageCancel.clicked.connect(self.on_reject_requested)
        self.pushButtonAuthPageLink.clicked.connect(self.on_auth_clicked)
        self.pushButtonDropboxPathCalcel.clicked.connect(self.on_reject_requested)
        self.pushButtonDropboxPathSelect.clicked.connect(self.on_dropbox_location_selected)
        self.pushButtonDropboxPathUnlink.clicked.connect(self.unlink_and_go_to_start)
        self.pushButtonFolderSelectionBack.clicked.connect(self.stackedWidget.slideInPrev)
        self.pushButtonFolderSelectionSelect.clicked.connect(self.on_folders_selected)
        self.pushButtonClose.clicked.connect(self.on_accept_requested)
        self.selectAllCheckBox.clicked.connect(self.on_select_all_clicked)

        default_dir_name = self._conf.get('main', 'default_dir_name')

        self.labelDropboxPath.setText(self.labelDropboxPath.text().format(default_dir_name))

        # check if we are already authenticated, skip authentication if yes
        if not pending_link:
            start_maestral_daemon_thread(self._config_name, run=False)
            self.mdbx = get_maestral_proxy(self._config_name)
            self.mdbx.get_account_info()
            self.labelDropboxPath.setText("""
            <html><head/><body>
            <p align="left">
            Your Dropbox folder has been moved or deleted from its original location.
            Maestral will not work properly until you move it back. It used to be located
            at: </p><p align="left">{0}</p>
            <p align="left">
            To move it back, click "Quit" below, move the Dropbox folder back to its
            original location, and launch Maestral again.
            </p>
            <p align="left">
            To re-download your Dropbox, please select a location for your Dropbox
            folder below. Maestral will create a new folder named "{1}" in the
            selected location.</p>
            <p align="left">
            To unlink your Dropbox account from Maestral, click "Unlink" below.</p>
            </body></html>
            """.format(self._conf.get('main', 'path'), default_dir_name))
            self.pushButtonDropboxPathCalcel.setText('Quit')
            self.stackedWidget.setCurrentIndex(2)
            self.stackedWidgetButtons.setCurrentIndex(2)
        else:
            self.stackedWidget.setCurrentIndex(0)
            self.stackedWidgetButtons.setCurrentIndex(0)