#!/usr/bin/perl -w # # rm2-list # John Simpson 2023-06-30 # # The reMarkable tablet presents a "filesystem" in the UI, but the files in # the tablet have names based on UUIDs, all stored in a single directory. # This script scans these UUID files and prints a list showing the directory # structure and filenames as presented in the UI. # # I wrote this to make sure I understand how the "filesystem" on the tablet # works, before writing other scripts which will DO things with the "files" # in the tablet. require 5.005 ; use strict ; use Getopt::Std ; use JSON ; my $indent = ' ' ; # printed for each "level" of indentation ######################################## # globals my $tablet = '' ; # path to tablet's root dir (from cmd line) my $xdir = '' ; # full path to .../remarkable/xochitl directory my %metadata = () ; # UUID => contents of UUID.metadata file my %content = () ; # UUID => contents of UUID.content file my %calc = () ; # UUID => calculated info (list of children) my %opt = () ; # getopts my $show_all = 0 ; # -a Show deleted items my $show_pages = 0 ; # -p Show info about pages my $show_uuid = 0 ; # -u Show UUIDs ############################################################################### # # usage sub usage(;$) { my $msg = ( shift || '' ) ; print < ) { $jtext .= $line ; } close M ; ######################################## # Parse contents as JSON my $j = decode_json( $jtext ) or die "ERROR: cannot parse contents of '$xdir/$f' as JSON\n$jtext\n" ; ######################################## # Store what we found $metadata{$uuid} = $j ; ############################################################ # Also read the corresponding UUID.content file my $file = "$xdir/$uuid.content" ; ######################################## # Read the file into memory open( C , '<' , $file ) or die "ERROR: open('$file'): $!\n" ; $jtext = '' ; while ( my $line = ) { $jtext .= $line ; } close C ; ######################################## # Parse contents as JSON $j = decode_json( $jtext ) or die "ERROR: cannot parse contents of '$file' as JSON\n$jtext\n" ; ######################################## # Store what we found $content{$uuid} = $j ; } } ############################################################################### # # Sort items by visibleName sub by_visibleName($$) { my $a = shift ; my $b = shift ; my $an = lc( $metadata{$a}->{'visibleName'} || '' ) ; my $bn = lc( $metadata{$b}->{'visibleName'} || '' ) ; return ( $an cmp $bn ) ; } ############################################################################### # # Recursive function to show entries sub show_object($$) ; sub show_object($$) { my $uuid = shift ; my $prefix = shift ; my $name = ( $metadata{$uuid}->{'visibleName'} || '(visibleName?)' ) ; my $type = ( $metadata{$uuid}->{'type'} || '(type?)' ) ; my $deleted = ( $metadata{$uuid}->{'deleted'} || 0 ) ; my $pinned = ( $metadata{$uuid}->{'pinned'} || 0 ) ; my $synced = ( $metadata{$uuid}->{'synced'} || 0 ) ; my $version = ( $metadata{$uuid}->{'version'} || 0 ) ; if ( $show_all || ( ! $deleted ) ) { ######################################## # Figure out what to show after the name if ( $uuid eq 'trash' ) { $name = '(Trash)' ; $type = '/' ; } elsif ( $type eq 'DocumentType' ) { $type = '' ; } elsif ( $type eq 'CollectionType' ) { $type = '/' ; } else { $type = " ($type)" ; } ######################################## # Maybe figure out how many pages are in a notebook my $pages = '' ; if ( $show_pages ) { if ( exists $content{$uuid}->{'cPages'}->{'pages'} ) { my $pg = 0 ; my $pd = 0 ; for my $p ( @{$content{$uuid}->{'cPages'}->{'pages'}} ) { if ( exists $p->{'deleted'} ) { $pd ++ ; } else { $pg ++ ; } } my $s = ( 1 == $pg ) ? '' : 's' ; if ( $pd > 0 ) { $pages = " ($pg page$s plus $pd deleted)" } elsif ( $pg > 0 ) { $pages = " ($pg page$s)" } } } ######################################## # Print the output $show_uuid && printf '%-37s ' , $uuid ; my $flags = $type . $pages . ( $version > 0 ? " (v$version)" : '' ) . ( $deleted ? ' (DELETED)' : '' ) . ( $pinned ? ' (PINNED)' : '' ) . ( $synced ? ' (SYNCED)' : '' ) ; print $prefix , $name , $flags, "\n" ; ######################################## # If the item has children, show them if ( exists $calc{$uuid}->{'children'} ) { ######################################## # Increase indent shown when children are printed $prefix .= $indent ; ######################################## # Show children for my $c ( sort by_visibleName @{$calc{$uuid}->{'children'}} ) { show_object( $c , $prefix ) ; } } } } ############################################################################### ############################################################################### ############################################################################### # # Process command line getopts( 'hauxp' , \%opt ) ; $opt{'h'} && usage() ; $show_all = ( $opt{'a'} ? 1 : 0 ) ; $show_uuid = ( $opt{'u'} ? 1 : 0 ) ; $show_pages = ( $opt{'p'} ? 1 : 0 ) ; my $dir = ( shift || '.' ) ; ######################################## # Figure out where the files are if ( $opt{'x'} ) { $xdir = $dir ; } else { $xdir = "$dir/home/root/.local/share/remarkable/xochitl" ; } ######################################## # Make sure the directory containing all the files, exists if ( ! -d $xdir ) { usage "ERROR: '$xdir' directory does not exist\n" ; } ############################################################################### # # Scan that directory read_metadata() ; ############################################################################### # # Build a "tree" representing the directory structure for my $uuid ( sort keys %metadata ) { my $parent = $metadata{$uuid}->{'parent'} ; push( @{$calc{$parent}->{'children'}} , $uuid ) ; } ############################################################################### # # Show the output for my $uuid ( sort by_visibleName @{$calc{''}->{'children'}} ) { show_object( $uuid , '' ) ; } if ( exists $calc{'trash'} ) { show_object( 'trash' , '' ) ; }