Last active
December 7, 2021 11:04
-
-
Save AaronDavidSchneider/fbff95a8cd01c203faa6c1e283d79bba to your computer and use it in GitHub Desktop.
Sync your pdfs inside a specific folder to the remarkable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This script is a tool too sync pdfs in a folder to the remarkable. | |
# It uses rmcl to access the remarkable cloud and rmrl to download annotated versions. | |
# | |
# /////////////////// | |
# Usage Instructions: | |
# /////////////////// | |
# 1. Install rmcl and rmrl: 'pip install rmcl rmrl' | |
# 2. Get a code from my.remarkable.com and uncomment the second last line of this script while replacing the code | |
# 3. replace FOLDER and DESTINATION. FOLDER needs to be in the top directory of the RM. | |
# 4. run the script: 'python folder_sync.py' | |
# 5. Next time you run it don't forget to comment the register_device_s line. | |
# 6. run the script every time you add a PDF to the folder (can be automated, depends on your platform) | |
# ///////////// | |
# Capabilities: | |
# ///////////// | |
# 1. Sync files inside a folder (currently only PDFs) to RM. | |
# 2. Download and convert annotations from RM cloud (one way: from RM to PC). | |
from rmcl import Item, Document, Folder, invalidate_cache, register_device_s | |
from rmcl.const import FileType | |
import trio | |
import os | |
import glob | |
import datetime | |
import pytz | |
# Replace those with your values: | |
FOLDER = '/Users/schneider/Documents/bibmanager/pdf' | |
DESTINATION = 'paper' # Needs to be in root directory (currently) | |
async def init(): | |
"""Initialize the API, create and/or return the dest Folder.""" | |
async def get_dest_id(): | |
root = await Item.get_by_id('') | |
return [child.id for child in root.children if isinstance(child, Folder) and child.name == DESTINATION] | |
async def create_destfolder(): | |
root = await Item.get_by_id('') | |
folder = Folder.new(name=DESTINATION, parent_id=root.id) | |
await folder.upload() | |
await invalidate_cache() | |
dest_id = await get_dest_id() | |
if not bool(dest_id): | |
await create_destfolder() | |
dest_id = await get_dest_id() | |
elif len(dest_id) != 1: | |
print('Error: more than one folder with same name') | |
dest = await Item.get_by_id(dest_id[0]) | |
return dest | |
async def check_in_collection(name, dest): | |
"""Check if file exists.""" | |
res = [child.id for child in dest.children if isinstance(child, Document) and child.name == name] | |
return bool(res) | |
def get_local_name_dict(): | |
"""Look for local files.""" | |
local_file_list = glob.glob(os.path.join(FOLDER, '*.pdf')) | |
local_name_dict = {} | |
for file in local_file_list: | |
name = os.path.relpath(file, FOLDER) | |
name_without_ending = '.'.join(n for n in name.split(".")[:-1]) | |
local_name_dict[name_without_ending] = file | |
return local_name_dict | |
async def upload_directory(): | |
"""Check if files of directory exist in cloud or upload if necessary.""" | |
dest = await init() | |
local_name_dict = get_local_name_dict() | |
for name_without_ending, file in local_name_dict.items(): | |
available = await check_in_collection(name_without_ending, dest) | |
if not available: | |
print(f'Syncing {name_without_ending}') | |
doc = Document.new(name=name_without_ending, parent_id=dest.id) | |
await doc.upload(new_contents=open(file, 'rb'), type_=FileType.pdf) | |
async def download_changes(): | |
"""Check for changes in the cloud, render those files and replace local files by changed files.""" | |
dest = await init() | |
local_name_dict = get_local_name_dict() | |
for child in dest.children: | |
if isinstance(child, Folder): | |
continue | |
if local_path := local_name_dict.get(child.name): | |
last_changed_locally = datetime.datetime.fromtimestamp(os.path.getmtime(local_path), tz=pytz.utc) | |
if child.mtime > last_changed_locally and child.version > 1: | |
print(f'updating local file by annotated file: {child.name}') | |
stream = await child.annotated() | |
fout = open(local_path, 'wb') | |
fout.write(stream.read()) | |
fout.close() | |
else: | |
await child.delete() | |
async def main(): | |
"""Program execution.""" | |
# Upload files that are not available on the RM | |
await upload_directory() | |
# Replace local files by RM annotated files | |
# Comment out if you prefer one way sync. | |
await download_changes() | |
if __name__ == "__main__": | |
# First time use (uncomment below, get code from 'my.remarkable.com'): | |
# register_device_s("PUT YOUR CODE HER") | |
trio.run(main) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment