def test_recover_or_create(Path, tmpdir):
    """Test the function that works out the file and path for the config.
    """
    Path.home.side_effect = lambda: tmpdir

    cfg = userconfig.recover_or_create()
    base_dir = tmpdir / '.rmfriend'
    assert base_dir.isdir()

    notebooks = base_dir / 'notebooks'
    assert notebooks.isdir()

    config_file = base_dir / 'config.cfg'
    assert config_file.isfile()

    assert 'rmfriend' in cfg
    assert cfg['rmfriend']['address'] == '10.11.99.1'
    assert cfg['rmfriend']['port'] == '22'
    assert cfg['rmfriend']['username'] == 'root'
    assert cfg['rmfriend']['cache_dir'] == str(notebooks)
    assert cfg['rmfriend']['remote_dir'] == (
        '/home/root/.local/share/remarkable/xochitl')

    # Calling the second time will re-read from the already present config
    # file so there should be no problems.
    cfg = userconfig.recover_or_create()
    assert 'rmfriend' in cfg
    assert cfg['rmfriend']['address'] == '10.11.99.1'
    assert cfg['rmfriend']['port'] == '22'
    assert cfg['rmfriend']['username'] == 'root'
    assert cfg['rmfriend']['cache_dir'] == str(notebooks)
    assert cfg['rmfriend']['remote_dir'] == (
        '/home/root/.local/share/remarkable/xochitl')
Exemple #2
0
def save_configuration():
    """Save the configuration to disk."""
    config = userconfig.recover_or_create()

    settings = request.json()
    print('Saving settings: {}'.format(settings))

    config['rmfriend']['address'] = settings['address']
    config['rmfriend']['post'] = settings['post']
    config['rmfriend']['username'] = settings['username']
    config['rmfriend']['cache_dir'] = settings['cache_dir']
    config.write()

    config = userconfig.recover_or_create()
    return json.dumps(dict(config['rmfriend']))
    def notebook_cache(cls):
        """
        """
        notebooks = collections.defaultdict(dict)

        config = userconfig.recover_or_create()
        cache_dir = Path(config['rmfriend']['cache_dir'])

        for item in cache_dir.iterdir():
            if item.is_dir():
                # Skip for the moment
                continue
            doc_id, ext = document_id_and_extension(item.name)
            uri = item.as_uri()
            version = '-'
            name = '-'
            if ext == 'metadata':
                metadata = json.loads(item.read_bytes())
                name = metadata['visibleName']
                version = metadata['version']

            notebooks[doc_id][ext] = {
                'uri': uri,
                'version': version,
                'name': name,
            }

        return dict(notebooks)
    def recover(cls, sftp, document_id):
        """Recover a remote notebook from reMarkable.

        :param sftp: A connected paramiko SFTP instance.

        This should have changed directory to the one containing the notebooks.

        :param document_id: The UUID string for the notebook.

        This will attempt to recover the files

            <document_id>.(lines|metadata|content|pagedata)

        The notebook directories (thumbnails and cache) will also be recovered.

        :returns: A Notebook instance.

        """
        config = userconfig.recover_or_create()
        cache_dir = Path(config['rmfriend']['cache_dir'])
        address = config['rmfriend']['address']
        username = config['rmfriend']['username']
        auth = dict(
            hostname=address,
            username=username,
        )

        for extension in ('lines', 'metadata', 'content', 'pagedata'):
            # Recover to the cache:
            local_file = filename_from(document_id, extension, cache_dir)
            remote_file = filename_from(document_id, extension)
            try:
                sftp.get(remote_file, localpath=local_file)
            except IOError as error:
                print('Error recovering {} to {}: {}'.format(
                    remote_file, local_file, error))

        def get_(remote_dir, remote_file, local_file):
            with SFTP.connect(**auth) as sftp:
                sftp.chdir(remote_dir)
                sftp.get(
                    remote_file,
                    localpath=local_file,
                )

        for extension in ('thumbnails', 'cache'):
            with SFTP.connect(**auth) as sftp:
                name = filename_from(document_id, extension)
                local_dir = Path(
                    filename_from(document_id, extension, cache_dir))
                dirname = cache_dir / name
                if not dirname.is_dir():
                    os.makedirs(dirname)
                # Iterate through each image and recover it:
                sftp.chdir(name)
                for item in sftp.listdir_iter():
                    local_file = str(local_dir / item.filename)
                    remote_file = item.filename
                    get_(name, remote_file, local_file)
Exemple #5
0
def static(document_id, filepath):
    """Recover the current notebooks from cache.
    """
    config = userconfig.recover_or_create()
    cache_dir = config['rmfriend']['cache_dir']
    filename = "{}/{}.thumbnails/{}".format(cache_dir, document_id, filepath)
    with open(filename, 'rb') as fd:
        returned = fd.read()
    return returned
    def do_notebook_ls(self, subcmd, opts, *args, **kwargs):
        """${cmd_name}: Show a list of notebooks on reMarkable.

        ${cmd_usage}
        ${cmd_option_list}

        """
        config = userconfig.recover_or_create()
        address = config['rmfriend']['address']
        username = config['rmfriend']['username']

        if opts.ask:
            password = getpass.getpass(
                "Please enter password for {}@{}: ".format(username, address)
            )
        else:
            password = opts.password

        auth = dict(
            hostname=config['rmfriend']['address'],
            username=config['rmfriend']['username'],
            password=password,
        )
        with SFTP.connect(**auth) as sftp:
            results = SFTP.notebooks_from_listing(sftp.listdir())
            listing = SFTP.notebook_ls(sftp, results)

        table_listing = [
            ['Last Modified', 'Name', 'reMarkable Version', 'Local Version']
        ]
        if opts.show_id:
            table_listing[0].insert(0, 'ID')

        for e in listing:
            if opts.show_id:
                table_listing.append((
                    e['id'],
                    e['last_modified'],
                    e['name'],
                    e['version'],
                    e['local_version']
                )
                )
            else:
                table_listing.append((
                    e['last_modified'],
                    e['name'],
                    e['version'],
                    e['local_version']
                ))

        table = AsciiTable(table_listing)
        print(table.table)
    def notebook_previews(cls):
        """
        """
        config = userconfig.recover_or_create()
        cache_dir = Path(config['rmfriend']['cache_dir'])

        found = []
        for item in cache_dir.iterdir():
            document_id, ext = document_id_and_extension(item.name)
            listing = {
                "id": document_id,
                "name": '',
                "version": '',
                "last_modified": '',
                "last_opened": '',
                "images": [],
            }
            if ext == 'thumbnails':
                # recover metadata details:
                name = filename_from(document_id, 'metadata')
                metadata = cache_dir / name
                metadata = json.loads(metadata.read_bytes())
                listing['name'] = metadata['visibleName']
                listing['version'] = metadata['version']
                listing['last_modified'] = metadata['lastModified']

                # Is the last opened page present?
                name = filename_from(document_id, 'content')
                content = cache_dir / name
                if content.is_file():
                    content = json.loads(content.read_bytes())
                    if 'lastOpenedPage' in content:
                        listing['last_opened'] = content['lastOpenedPage']

                # find the thumbnail images:
                name = filename_from(document_id, 'thumbnails')
                thumbnails = cache_dir / name
                for item in thumbnails.iterdir():
                    listing['images'].append(item.name)
                listing['images'] = natsorted(
                    listing['images'], alg=ns.IGNORECASE
                )
                found.append(listing)

        # Sort by last modified decending emulating reMarkable / Notebooks UI
        found = sorted(
            found,
            key=lambda doc: doc['last_modified'],
            reverse=True
        )

        return found
    def do_recover(self, subcmd, opts, document_id):
        """${cmd_name}: Recover a specific notebook to the local cache.

        The given document will be recovered regardless of whether the same
        files already exist.

        ${cmd_usage}
        ${cmd_option_list}

        """
        config = userconfig.recover_or_create()
        auth = dict(
            hostname=config['rmfriend']['address'],
            username=config['rmfriend']['username'],
        )
        with SFTP.connect(**auth) as sftp:
            Notebook.recover(sftp, document_id)
    def rsync(cls):
        """
        """
        config = userconfig.recover_or_create()
        address = config['rmfriend']['address']
        username = config['rmfriend']['username']

        local_notebooks = Sync.notebook_cache()
        local = set(local_notebooks.keys())

        def progress_factory(message):
            def action_ticker(total, position):
                done = int((position / total) * 100)
                sys.stdout.write(
                    '\r{}: {:2d}% '.format(message, done)
                )
                sys.stdout.flush()

            return action_ticker

        auth = dict(
            hostname=address,
            username=username,
        )
        with SFTP.connect(**auth) as sftp:
            notebook_listing = SFTP.notebooks_from_listing(sftp.listdir())
            remote_notebooks = {
                nb['id']: nb
                for nb in SFTP.notebook_ls(sftp, notebook_listing)
            }

        remote = set(remote_notebooks.keys())
        print("All notebooks on reMarkable: {}".format(len(remote)))

        only_local = local.difference(remote)
        print("Notebooks only present locally: {}".format(len(only_local)))

        present_on_both = local.union(remote)
        print("Notebooks on both: {}".format(len(present_on_both)))

        only_remote = remote.difference(local)
        print("Notebooks only on reMarkable: {}".format(len(only_remote)))

        change_progress = utils.progress_factory('Working out changes')
        changed_notebooks = []
        with SFTP.connect(**auth) as sftp:
            progress = 1
            total = len(list(present_on_both))
            for doc_id in present_on_both:
                change_progress(progress, total)
                progress += 1

                if doc_id not in remote_notebooks:
                    # only local, ignore.
                    continue

                local_version = remote_notebooks[doc_id]['local_version']
                remarkable_version = remote_notebooks[doc_id]['version']
                if remarkable_version > local_version:
                    print(
                        "Recovering doc_id: ", doc_id, " local_version: ",
                        local_version, " remarkable_version: ",
                        remarkable_version
                    )
                    Notebook.recover(sftp, doc_id)

        recover_progress = utils.progress_factory('Recovering new notebooks')
        auth['ssh_only'] = False
        with SFTP.connect(**auth) as sftp:
            # clear change progress update.
            progress = 1
            total = len(only_remote)
            for document_id in only_remote:
                Notebook.recover(sftp, document_id)
                recover_progress(progress, total)
                progress += 1

        returned = {
            'new': list(only_remote),
            'deleted': list(only_local),
            'changed': list(changed_notebooks),
        }

        print("\nDone")

        return returned
    def notebook_ls(cls, sftp, notebooks):
        """Recover the metadata and print a listing of the notebooks.

        :param sftp: See connect() for details.

        :param notebooks: See notebooks_from_listing().

        :returns: A list of notebook information or an empty list.

        E.g.::

            [
                {
                    'id': 'UUID',
                    'name': 'notebook name',
                    'version': '<reMarkable notebook version number>',
                    'local_version': '<cached notebook version number>' or '',
                    'last_modified': 'iso8601 formatted string'
                },
                :
                etc
            ]

        This only contains the information and not the actual notebook lines
        data.

        """
        config = userconfig.recover_or_create()
        cache_dir = Path(config['rmfriend']['cache_dir'])

        listing = []

        for document_id in notebooks:
            file_ = "{}.{}".format(document_id, 'metadata')
            try:
                data = cls.get(sftp, file_)

            except IOError as error:  # noqa
                # print('Error reading {}: {}'.format(file_, error))
                pass

            else:
                notebooks[document_id]['metadata'] = json.loads(data)

                # If there is a local version of this file check its version
                local_version = 0
                local_metadata = cache_dir / file_
                if local_metadata.is_file():
                    local_metadata = json.loads(local_metadata.read_bytes())
                    local_version = int(local_metadata['version'])

                metadata = notebooks[document_id]['metadata']
                last_modified = int(metadata['lastModified']) / 1000
                last_modified = time.strftime(
                    '%Y-%m-%d %H:%M:%S', time.gmtime(last_modified)
                )

                if metadata['type'] == 'DocumentType':
                    listing.append({
                        'id': document_id,
                        'name': metadata['visibleName'],
                        'version': int(metadata['version']),
                        'local_version': local_version,
                        'last_modified': last_modified,
                    })

                else:
                    # This could be a collection type, PDF, epub, etc.
                    pass
                    # print('Not a notebook: {} {}'.format(
                    #     metadata['type'], metadata['visibleName']
                    # ))

        return listing
Exemple #11
0
def recover_configuration():
    """Recover current configuration file contents."""
    config = userconfig.recover_or_create()
    return json.dumps(dict(config['rmfriend']))