diff --git a/README.MD b/README.MD index 2ae4fee..ae307aa 100644 --- a/README.MD +++ b/README.MD @@ -2,13 +2,13 @@ [![Mentioned in Awesome reMarkable](https://awesome.re/mentioned-badge.svg)](https://github.com/reHackable/awesome-reMarkable) -This is a collection of scripts that utilize the reMarkable USB webinterface to export files. +This is a collection of scripts that utilize the reMarkable USB web interface to export files. ## DISCLAIMER Using the export functionality, the script will prompt your device to **export every single file** onto your pc. -This will - as of now - lead to freezes and other problems, as the software is not specifically suited for this usecase. +This will - as of now - lead to freezes and other problems, as the software is not specifically suited for this use case. I (and reMarkable AS too) **WON'T take any responsibility** for potential damage done to your device using this software. @@ -48,7 +48,7 @@ $ ./stats.py Right now, you can export/mirror all your files into a specified folder, export only changed files or view general information using on of the various scripts available. -You can also make your own scripts using the `api.py` and give you access to all metadata provided by the reMarkable USB webinterface. An example of such information can be found in the file `.exampleRootMetadata.json`. +You can also make your own scripts using the `api.py` and give you access to all metadata provided by the reMarkable USB web interface. An example of such information can be found in the file `.exampleRootMetadata.json`. There is certainly a lot more possible than currently provided. If you want a certain feature, you can create an issue (= feature request) or contribute it yourself. @@ -59,4 +59,3 @@ If you want a certain feature, you can create an issue (= feature request) or co When exporting a large amount of files, it can happen that your reMarkable **enters sleep mode** during that process or **locks up and restarts** (happens on some huge pdf files). When this happens, you can just execute the same command again. It should continue where it failed with no data loss. - diff --git a/api.py b/api.py index 7251ac6..41a0dc4 100755 --- a/api.py +++ b/api.py @@ -1,10 +1,11 @@ ''' -Utilities regarding the reMarkable usb webinterface. +Utilities regarding the reMarkable USB web interface. ''' from collections.abc import Iterable from datetime import datetime import requests +import os RM_WEB_UI_URL = 'http://10.11.99.1' # No trailing slash! @@ -20,7 +21,7 @@ def __init__(self, metadata, parent=None): self.metadata = metadata self.parent = parent - # Hierachial data: + # Hierarchial data: self.isFolder = (metadata['Type'] == 'CollectionType') self.files = [] if self.isFolder else None @@ -71,15 +72,16 @@ def parentFolderPath(self, basePath=''): else: basePath - def exportPdf(self, targetPath): + def exportDoc(self, targetPath): ''' - Downloads this file as pdf to a given path. + Downloads this file as pdf or rmdoc depending in the file extension to a given path. I may take a while before the download is started, due to the conversion happening on the reMarkable device. Returns bool with True for successful and False for unsuccessful. ''' - response = requests.get(RM_WEB_UI_URL + '/download/' + self.id + '/placeholder', stream=True) + (_, filetype) = os.path.splitext(targetPath) + response = requests.get(RM_WEB_UI_URL + '/download/' + self.id + '/' + filetype[1:], stream=True) if not response.ok: return False @@ -101,12 +103,12 @@ def __repr__(self): def iterateAll(filesOrRmFile): ''' - Yields all files in this iterable (list, tuple, ...) or file including subfiles and folders. + Yields all files in this iterable (list, tuple, ...) or file including sub-files and folders. In case any (nested) value that isn't a iterable or of class api.RmFile a ValueError will be raised! ''' if isinstance(filesOrRmFile, RmFile): - # Yield file and optional sub files recursivly: + # Yield file and optional sub files recursively: yield filesOrRmFile if filesOrRmFile.isFolder: for recursiveSubFile in iterateAll(filesOrRmFile.files): @@ -123,20 +125,20 @@ def iterateAll(filesOrRmFile): def fetchFileStructure(parentRmFile=None): ''' - Fetches the fileStructure from the reMarkable USB webinterface. + Fetches the fileStructure from the reMarkable USB web interface. Specify a RmFile of type folder to fetch only all folders after the given one. Ignore to get all possible file and folders. Returns either list of files OR FILLS given parentRmFiles files - May throw RuntimeError or other releated ones from "requests" - if there are problems fetching the data from the usb webinterface. + May throw RuntimeError or other related ones from "requests" + if there are problems fetching the data from the USB web interface. ''' if parentRmFile and not parentRmFile.isFolder: raise ValueError('"api.fetchFileStructure(parentRmFile)": parentRmFile must be None or of type folder!') - # Use own list if not parentRmFile is given (aka beeing in root) + # Use own list if not parentRmFile is given (aka being in root) if not parentRmFile: rootFiles = [] @@ -146,7 +148,7 @@ def fetchFileStructure(parentRmFile=None): response.encoding = 'UTF-8' # Ensure, all Non-ASCII characters get decoded properly if not response.ok: - raise RuntimeError('Url %s responsed with status code %d' % (directoryMetadataUrl, response.status_code)) + raise RuntimeError('Url %s responded with status code %d' % (directoryMetadataUrl, response.status_code)) directoryMetadata = response.json() @@ -158,7 +160,7 @@ def fetchFileStructure(parentRmFile=None): else: rootFiles.append(rmFile) - # Fetch subdirectories recursivly: + # Fetch subdirectories recursively: if rmFile.isFolder: fetchFileStructure(rmFile) diff --git a/backup.bat b/backup.bat index 47636f6..874d43f 100644 --- a/backup.bat +++ b/backup.bat @@ -1,19 +1,36 @@ @echo off setlocal +:: hide any "UNC paths are not supported" warnings +cls + +:: If this batch file is on a UNC path, then its folder will be mapped to new a drive letter +:: (that will be removed later by the popd command). +:: pushd will change your working directory to the scripts location in the new mapped drive. +@pushd %~dp0 + set VENV=venv set DEST=_backup if not exist %VENV% ( + echo ===== CREATING VIRTUAL ENVIROMENT py -3 -m venv %VENV% - call %VENV%\scripts\activate + echo ===== ACTIVATE VIRTUAL ENVIROMENT + call %VENV%\scripts\activate.bat + echo ===== UPGRADING PIP python -m pip install --upgrade pip + echo ===== INSTALLING REQUIREMENTS pip install --requirement requirements.txt ) else ( - call %VENV%\scripts\activate + echo ===== ACTIVATE VIRTUAL ENVIROMENT + call %VENV%\scripts\activate.bat ) if not exist %DEST% mkdir %DEST% -export.py --update %DEST% +echo ===== EXPORTING FROM REMARKABLE +python export.py --update %DEST% + +:: popd to clean up the mapped drive, if created. +@popd pause diff --git a/export.py b/export.py index 631d916..22213b0 100755 --- a/export.py +++ b/export.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 ''' -Export - Exports all files of the remarkable onto your PC as pdfs. +Export - Exports all files of the remarkable onto your PC as PDFs and rmdoc. Info: If a file is already exported, it will get skipped by default. ''' @@ -9,11 +9,10 @@ import api from argparse import ArgumentParser, RawDescriptionHelpFormatter -from datetime import datetime +from datetime import datetime, timezone from os import makedirs, utime -from os.path import exists, getmtime -from sys import stderr -from time import time +from os.path import exists, getmtime, splitext +from sys import stderr, argv # ------------------------------ # Config: @@ -21,8 +20,10 @@ # ------------------------------ def local_time_offset(): - now_timestamp = time() - return (datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)).total_seconds() + local_now = datetime.now() + utc_now = datetime.now(timezone.utc) + offset = (local_now - utc_now.replace(tzinfo=None)).total_seconds() + return offset def exportTo(files, targetFolderPath, onlyNotebooks, onlyBookmarked, updateFiles, onlyPathPrefix=None): # Preprocessing filterPath: @@ -43,7 +44,7 @@ def exportTo(files, targetFolderPath, onlyNotebooks, onlyBookmarked, updateFiles if onlyNotebooks: exportableFiles = list(filter(lambda rmFile: rmFile.isNotebook, exportableFiles)) - # Filter for only bookmared if requested: + # Filter for only bookmarked if requested: if onlyBookmarked: exportableFiles = list(filter(lambda rmFile: rmFile.isBookmarked, exportableFiles)) @@ -94,14 +95,23 @@ def exportTo(files, targetFolderPath, onlyNotebooks, onlyBookmarked, updateFiles # Export file if necessary: if not skipFile: + (root, _) = splitext(path) + path_rmdoc = root + ".rmdoc" + path_pdf = root + ".pdf" try: - exportableFile.exportPdf(path) + exportableFile.exportDoc(path_rmdoc) except Exception as ex: - print('ERROR: Failed to export "%s" to "%s"' % (exportableFile.name, path)) + print('ERROR: Failed to export "%s" to "%s"' % (exportableFile.name, path_rmdoc)) raise ex try: - local_mod_time = exportableFile.modifiedTimestamp + local_time_offset() - utime(path, (local_mod_time, local_mod_time)) # Use timestamp from the reMarkable device + exportableFile.exportDoc(path_pdf) + except Exception as ex: + print('ERROR: Failed to export "%s" to "%s"' % (exportableFile.name, path_pdf)) + raise ex + try: + local_mod_time = exportableFile.modifiedTimestamp + local_time_offset() + utime(path_pdf, (local_mod_time, local_mod_time)) # Use timestamp from the reMarkable device + utime(path_rmdoc, (local_mod_time, local_mod_time)) # Use timestamp from the reMarkable device except Exception as ex: print('ERROR: Failed to change timestamp for exported file "%s"' % path) raise ex @@ -115,7 +125,7 @@ def printUsageAndExit(): if __name__ == '__main__': # Disclaimer: print('DISCLAIMER: Please be aware that this puts the STRAIN of creating exported pdf files on YOUR REMARKABLE DEVICE rather than this computer.\n' \ - 'This can lead to UNEXPECTED BEHAVIOUR when many and/or big files are being exported.\n' \ + 'This can lead to UNEXPECTED BEHAVIOR when many and/or big files are being exported.\n' \ 'I WON\'T TAKE ANY RESPONSIBILITY for potential damage this may induce!\n', file=stderr) # Argument parsing: @@ -171,5 +181,5 @@ def printUsageAndExit(): else: print('ERROR: %s' % ex, file=stderr) print(file=stderr) - print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr) + print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr) exit(1) diff --git a/paths.py b/paths.py index 332f708..d725f33 100755 --- a/paths.py +++ b/paths.py @@ -24,5 +24,5 @@ else: print('ERROR: %s' % ex, file=stderr) print(file=stderr) - print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr) + print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr) exit(1) diff --git a/requirements.txt b/requirements.txt index 2c24336..d80d9fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests==2.31.0 +requests==2.32.3 diff --git a/stats.py b/stats.py index aea658f..ed58a05 100755 --- a/stats.py +++ b/stats.py @@ -107,5 +107,5 @@ def printStats(files): else: print('ERROR: %s' % ex, file=stderr) print(file=stderr) - print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr) + print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr) exit(1) diff --git a/tree.py b/tree.py index dccbf69..718c52b 100755 --- a/tree.py +++ b/tree.py @@ -38,5 +38,5 @@ def printTree(rmFiles, prefix=(' '*4)): else: print('ERROR: %s' % ex, file=stderr) print(file=stderr) - print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr) + print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr) exit(1)