Skip to content

Instantly share code, notes, and snippets.

@AaronDavidSchneider
Last active December 7, 2021 11:04
Show Gist options
  • Save AaronDavidSchneider/fbff95a8cd01c203faa6c1e283d79bba to your computer and use it in GitHub Desktop.
Save AaronDavidSchneider/fbff95a8cd01c203faa6c1e283d79bba to your computer and use it in GitHub Desktop.
Sync your pdfs inside a specific folder to the remarkable
# 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