class RootCleaner
A class that completely clears the reMarkable cloud's root.docSchema file, removing all document references while maintaining the proper file structure and version.
/tf/active/vicechatdev/e-ink-llm/cloudtest/clear_root_docschema.py
47 - 257
complex
Purpose
RootCleaner provides a safe and verified method to completely empty the reMarkable cloud storage by clearing the root.docSchema file. It handles authentication, retrieves current root state, creates an empty root structure (containing only the version line '3'), uploads it using the working reMarkable sync API endpoints, and verifies the operation succeeded. This is useful for resetting cloud storage or testing purposes.
Source Code
class RootCleaner:
"""Clears root.docSchema using the working upload mechanism"""
def __init__(self):
# Load auth session
auth = RemarkableAuth()
self.session = auth.get_authenticated_session()
if not self.session:
raise RuntimeError("Failed to authenticate with reMarkable")
print("๐งน Root Cleaner Initialized")
def get_current_root_info(self):
"""Get current root.docSchema info using working method"""
print("\n๐ Step 1: Getting current root.docSchema...")
# Get root info
root_response = self.session.get("https://eu.tectonic.remarkable.com/sync/v4/root")
root_response.raise_for_status()
root_data = root_response.json()
# Get root content
root_content_response = self.session.get(f"https://eu.tectonic.remarkable.com/sync/v3/files/{root_data['hash']}")
root_content_response.raise_for_status()
root_content = root_content_response.text
print(f"โ
Current root hash: {root_data['hash']}")
print(f"โ
Current generation: {root_data.get('generation')}")
print(f"โ
Root content size: {len(root_content)} bytes")
print(f"๐ Current root content:")
print(f" {repr(root_content)}")
# Count current documents
lines = root_content.strip().split('\n')
doc_count = len(lines) - 1 # Subtract version line
print(f"๐ Documents currently in root: {doc_count}")
return root_data, root_content
def create_empty_root(self):
"""Create completely empty root.docSchema with only version line"""
print(f"\n๐งน Step 2: Creating empty root.docSchema...")
# Empty root content: just version "3" and newline
empty_root_content = "3\n"
print(f"โ
Empty root content: {repr(empty_root_content)}")
print(f"โ
Empty root size: {len(empty_root_content)} bytes")
return empty_root_content
def upload_empty_root(self, empty_root_content: str, current_generation: int):
"""Upload empty root.docSchema and update roothash using WORKING method"""
print(f"\nโฌ๏ธ Step 3: Uploading empty root.docSchema...")
# Calculate hash for empty root
root_hash = hashlib.sha256(empty_root_content.encode()).hexdigest()
print(f"โ
New empty root hash: {root_hash}")
# Upload root content using WORKING method from test_move_from_trash.py
headers = {
'Content-Type': 'text/plain',
'rm-batch-number': '1',
'rm-filename': 'root.docSchema', # System filename for root.docSchema
'rm-sync-id': str(uuid.uuid4()),
'User-Agent': 'reMarkable-desktop-win/3.11.1.1951',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-BE,*',
'Connection': 'Keep-Alive'
}
# Add CRC32C checksum
crc32c_header = compute_crc32c_header(empty_root_content.encode())
if crc32c_header:
headers['x-goog-hash'] = crc32c_header
print(f"๐ค PUT to: https://eu.tectonic.remarkable.com/sync/v3/files/{root_hash}")
print(f" Headers: {list(headers.keys())}")
upload_response = self.session.put(
f"https://eu.tectonic.remarkable.com/sync/v3/files/{root_hash}", # WORKING ENDPOINT
data=empty_root_content.encode(),
headers=headers
)
print(f"โ
Root content upload response: {upload_response.status_code}")
if upload_response.status_code not in [200, 202]:
print(f"โ Upload failed: {upload_response.text}")
raise RuntimeError(f"Root content upload failed: {upload_response.status_code}")
# Update root hash pointer using WORKING method
print(f"\n๐ Step 4: Updating root hash pointer...")
# Create root data exactly like working upload_manager.py
root_update_data = {
"broadcast": True,
"generation": current_generation, # Use current generation, don't increment
"hash": root_hash
}
# Convert to JSON with 2-space indent like real app
root_content_body = json.dumps(root_update_data, indent=2).encode('utf-8')
print(f"โ
Root update data:")
print(f" Generation: {current_generation} (keeping current)")
print(f" Hash: {root_hash}")
# Headers exactly like working upload_manager.py
headers = {
'Content-Type': 'application/json',
'rm-batch-number': '1',
'rm-filename': 'roothash',
'rm-sync-id': str(uuid.uuid4()),
'User-Agent': 'reMarkable-desktop-win/3.11.1.1951',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-BE,*',
'Connection': 'Keep-Alive'
}
# Add CRC32C checksum
crc32c_header = compute_crc32c_header(root_content_body)
if crc32c_header:
headers['x-goog-hash'] = crc32c_header
# Use /sync/v3/root endpoint like working code
print(f"๐ค PUT to: https://eu.tectonic.remarkable.com/sync/v3/root")
root_update_response = self.session.put(
"https://eu.tectonic.remarkable.com/sync/v3/root", # WORKING ENDPOINT
data=root_content_body,
headers=headers
)
print(f"โ
Root update response: {root_update_response.status_code}")
if root_update_response.status_code not in [200, 202]:
print(f"โ Root update failed: {root_update_response.text}")
raise RuntimeError(f"Root update failed: {root_update_response.status_code}")
return root_hash
def verify_empty_root(self):
"""Verify that the root is now empty"""
print(f"\n๐ Step 5: Verifying empty root...")
try:
# Get updated root info
root_response = self.session.get("https://eu.tectonic.remarkable.com/sync/v4/root")
root_response.raise_for_status()
root_data = root_response.json()
# Get root content
root_content_response = self.session.get(f"https://eu.tectonic.remarkable.com/sync/v3/files/{root_data['hash']}")
root_content_response.raise_for_status()
root_content = root_content_response.text
print(f"โ
Verification - New root hash: {root_data['hash']}")
print(f"โ
Verification - New generation: {root_data.get('generation')}")
print(f"โ
Verification - Root content size: {len(root_content)} bytes")
print(f"๐ Verification - Root content: {repr(root_content)}")
# Check if truly empty
lines = root_content.strip().split('\n')
doc_count = len(lines) - 1 # Subtract version line
if doc_count == 0 and root_content.strip() == "3":
print(f"๐ SUCCESS: Root is completely empty!")
print(f" Only version line '3' remains")
print(f" Document count: 0")
return True
else:
print(f"โ ๏ธ Root not completely empty:")
print(f" Document count: {doc_count}")
print(f" Content: {repr(root_content)}")
return False
except Exception as e:
print(f"โ Verification failed: {e}")
return False
def clear_root_completely(self):
"""Complete process to clear root.docSchema"""
print(f"๐งน Clearing Root DocSchema Completely")
print("=" * 50)
try:
# Step 1: Get current root info
root_data, root_content = self.get_current_root_info()
# Step 2: Create empty root
empty_root_content = self.create_empty_root()
# Step 3-4: Upload empty root and update pointer
new_root_hash = self.upload_empty_root(empty_root_content, root_data['generation'])
# Step 5: Verify result
verification_success = self.verify_empty_root()
if verification_success:
print(f"\n๐ SUCCESS! Root cleared completely")
print(f" New root hash: {new_root_hash}")
print(f" Cloud is now completely empty from user perspective")
print(f" All documents have been removed from root.docSchema")
return True
else:
print(f"\nโ ๏ธ Root clearing completed but verification failed")
return False
except Exception as e:
print(f"\nโ Root clearing failed: {e}")
return False
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
- | - |
Parameter Details
__init__: No parameters required. The constructor automatically initializes authentication with reMarkable cloud services using RemarkableAuth and establishes an authenticated session. Raises RuntimeError if authentication fails.
Return Value
Instantiation returns a RootCleaner object with an authenticated session. The main method clear_root_completely() returns a boolean: True if root was successfully cleared and verified, False otherwise. Individual methods return: get_current_root_info() returns tuple (root_data dict, root_content string), create_empty_root() returns string '3\n', upload_empty_root() returns string (new root hash), verify_empty_root() returns boolean.
Class Interface
Methods
__init__(self)
Purpose: Initialize RootCleaner with authenticated reMarkable session
Returns: None - initializes instance with self.session attribute
get_current_root_info(self) -> tuple[dict, str]
Purpose: Retrieve current root.docSchema information including hash, generation, and content
Returns: Tuple of (root_data dict containing 'hash' and 'generation', root_content string with document list)
create_empty_root(self) -> str
Purpose: Create an empty root.docSchema content with only the version line
Returns: String '3\n' representing empty root with version 3
upload_empty_root(self, empty_root_content: str, current_generation: int) -> str
Purpose: Upload empty root content to reMarkable cloud and update root hash pointer
Parameters:
empty_root_content: The empty root content string (typically '3\n')current_generation: Current generation number from root metadata to preserve
Returns: String containing the SHA256 hash of the uploaded empty root content
verify_empty_root(self) -> bool
Purpose: Verify that the root.docSchema is now empty by fetching and checking current state
Returns: Boolean: True if root contains only version line '3' with 0 documents, False otherwise
clear_root_completely(self) -> bool
Purpose: Execute complete workflow to clear root.docSchema: get current state, create empty root, upload, and verify
Returns: Boolean: True if entire clearing process succeeded and was verified, False if any step failed
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
session |
requests.Session | Authenticated HTTP session for making API requests to reMarkable cloud services, initialized by RemarkableAuth | instance |
Dependencies
jsontimehashlibuuidbase64zlibpathlibcrc32c
Required Imports
import json
import time
import hashlib
import uuid
import base64
import zlib
from pathlib import Path
from auth import RemarkableAuth
import crc32c
Usage Example
# Basic usage to clear root completely
cleaner = RootCleaner()
success = cleaner.clear_root_completely()
if success:
print('Root cleared successfully')
# Step-by-step usage with individual methods
cleaner = RootCleaner()
# Get current state
root_data, root_content = cleaner.get_current_root_info()
print(f'Current generation: {root_data["generation"]}')
# Create empty root
empty_content = cleaner.create_empty_root()
# Upload and update
new_hash = cleaner.upload_empty_root(empty_content, root_data['generation'])
# Verify
if cleaner.verify_empty_root():
print('Verification passed')
Best Practices
- Always instantiate RootCleaner in a try-except block to handle authentication failures gracefully
- Use clear_root_completely() for the full workflow rather than calling individual methods unless you need fine-grained control
- The class maintains state through self.session - do not modify this attribute directly
- Methods should be called in order: get_current_root_info() -> create_empty_root() -> upload_empty_root() -> verify_empty_root()
- The clear_root_completely() method orchestrates all steps automatically and includes error handling
- This operation is destructive - all document references in root.docSchema will be removed from cloud
- The class uses the working reMarkable sync v3/v4 API endpoints with proper headers and checksums
- Generation numbers are preserved (not incremented) to match reMarkable's expected behavior
- Each method prints detailed progress information for debugging and monitoring
- Verification step is critical - always check the return value to ensure operation succeeded
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function main_v23 84.1% similar
-
function simple_move_to_trash 75.2% similar
-
function verify_cloud_empty 75.0% similar
-
class RootDocSchemaRepair 74.1% similar
-
function show_current_root 72.8% similar