From a026189adf2ff3fb4f8fe08454bb0969af9308c0 Mon Sep 17 00:00:00 2001 From: John Simpson Date: Sat, 7 Sep 2024 22:02:30 -0400 Subject: [PATCH] Added "download rmdoc" functionality - added download_rmdoc() - replaced '/', '\', and ':' in visible names, with underscores - moved PassThru() (prints byte counts while reading data from a file) from download_pdf.go to do_download.go, so other download_xxx() can use it - included '-D' in usage() message --- README.md | 288 +++++++++++++++++++++++++++++++++------------- do_download.go | 40 ++++++- download_pdf.go | 54 +++------ download_rmdoc.go | 173 ++++++++++++++++++++++++++++ main.go | 58 ++++++++-- read_files.go | 17 ++- 6 files changed, 494 insertions(+), 136 deletions(-) create mode 100644 download_rmdoc.go diff --git a/README.md b/README.md index 2956373..ee43e1c 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ This program lists all documents on a reMarakble tablet, or downloads them all a I threw this together after reading [this question](https://www.reddit.com/r/RemarkableTablet/comments/18js4wo/any_way_to_transfer_all_my_files_to_an_ipad_app/) on Reddit. +# Background Information + I figured out the correct web requests by using [Wireshark](https://www.wireshark.org/) to sniff the web traffic between my computer and three different reMarkable tablets (both rM1 and rM2, running a mix of 3.8 and 3.9 software.) -### Go +### Go (or Golang) [Go](https://go.dev/) (or "Golang") is a programming language from Google. I used this language for a few reasons ... @@ -24,15 +26,25 @@ I figured out the correct web requests by using [Wireshark](https://www.wireshar One of the ideas on my list is a more "general" command line utility, written in Go, which will be able to list, download, upload, and make backups of reMarkable tablets. I'm planning to make this other program use SSH instead of the web interface, however I was going to have to use the web API to download PDF files anyway, so when I get ready to write that other program, I'll be able to copy a few of the functions from this program. -## Installing the Program +### File Formats + +The program will download PDF files by default. These can be viewed and printed on a computer, but any pen strokes you may have written will be "burned into" the file, and cannot be edited if you upload them back to the tablet (or to a different tablet). + +The program *can* download `.rmdoc` files, which reMarkable calls "Archive files". Your computer can't do a whole lot with these files, but if you upload them to the tablet (or to a different tablet), they will be edit-able, just like the original document. + +Note that reMarkable software versions prior to 3.10 were not capable of downloading "Archive files". (There was a bug in 3.9 where the web interface had an option to download Archive files, but the option didn't work - the files it would download were actually PDF files.) -### Download +Because of this. when the program tries to download the first `.rmdoc` file, it will check the first few bytes in the downloaded file to be sure it *is* a valid `.rmdoc` file. If not, it will stop trying to download `.rmdoc` files, and if no other file formats were requested, the program will stop. + +# Installing the Program + +## Download I'm using Github's "releases" mechanism. There should be a "Latest release" section to the right, if you're not reading this through the Github web interface, click [here](https://github.com/kg4zow/rmweb/releases). Download the appropriate executable for the machine where you plan to run the program. Store it in a directory in your `PATH`, and make sure its permissions are set to be executable. I also recommend renaming it to `rmweb`. -### Compiling the Program +## Compiling the Program If you want to compile the program from source ... @@ -67,25 +79,9 @@ Note that you could also run "`make all`" to build binaries for a list of archit You can see a list of all *possible* `GOOS`/`GOARCH` combinations in your installed copy of `go` by running "`go tool dist list`". +# Set up the tablet -## Running the program - -The examples below assume that when you installed the executable, you renamed it to "`rmweb`". If not, you'll need to adjust the commands below to use whatever name you gave it. - -### Check the version - -The "`rmweb version`" command will show you the version number, along with information about the specific code it was built from in the git repo. - -``` -$ rmweb version -rmweb-darwin-arm64 version 0.04 -Built 2023-12-18T17:44:17Z from v0.04-0-g17c101b -https://github.com/kg4zow/rmweb/ -``` - -### Set up the tablet - -**Connect the tablet via USB cable.** +### Connect the tablet via USB cable The program uses the web interface to talk to the tablet, and reMarkable sets things up so that the web interface is only available over the USB interface. @@ -95,99 +91,139 @@ The program uses the web interface to talk to the tablet, and reMarkable sets th > $ rmweb -I 192.0.2.7 list > ``` -**Make sure the tablet is not sleeping.** +### Make sure the tablet is not sleeping Fairly obvious. -**Make sure the web interface is enabled.** +### Make sure the web interface is enabled See "Settings → Storage" on the tablet. Note that you won't be able to turn the web interface on unless the tablet is connected to a computer via the USB cable. -### List files on the tablet +# Running the program -To list the files on the tablet, use "`rmweb list`". +The examples below assume that when you installed the executable, you renamed it to "`rmweb`". If not, you'll need to adjust the commands below to use whatever name you gave it. + +## Available Options + +If you use the `-h` option, or run the program without specifying a command, it will show a quick explanation of how to run the program, along with lists of the commands and options it supports. ``` -$ rmweb list -UUID Size Pages Name ------------------------------------- ------- ----- ----------------------------------- -22e6d931-c205-4d86-b022-04b6a0527b67 /Amateur Radio/ -34f04076-4733-40bc-983d-6a92b41a2728 311475 7 /Amateur Radio/D-STAR -9d5198b0-ce76-4f58-ba0a-a1a058678695 277447 1 /Amateur Radio/RTL-SDR -89f3cbbd-dba2-46aa-bc66-debdf97b915a 1431776 8 /Documentation to write -f67c74d2-7d23-4587-95bd-7a6e8ebaed2c /Ebooks/ -cc2135a3-08ea-4ae5-be77-8f455b039451 8025642 684 /Ebooks/The Art of Unix Programming -702ef913-16a0-47b1-806e-1769f251b06b 4241185 306 /Ebooks/The Cathedral & the Bazaar -... -9e6891eb-2558-4e70-b6fc-d03b2d75614b 5961550 52 /Quick sheets -383dad70-b9db-4a04-a275-be17cfc6bc8c 2658181 27 /ReMarkable 2 Info -225a451f-61c9-4ffb-96ad-cbe7b7bb530c 597041 3 /TODO -015deb02-0589-462a-bc98-3034d7d23628 /Work/ -0d318d48-d638-4c4c-9e29-98bac57bb658 3203221 21 /Work/2023-11 Daily -66d3acae-9697-4a10-b827-3e619af36fae 1692264 10 /Work/2023-12 Daily -``` +$ rmweb -h +rmweb [options] COMMAND [...] -The UUID values are the internal identifiers for each document. The files within the tablet that make up each document, all have this as part of their filename. If you're curious, [this page](https://remarkable.jms1.info/info/filesystem.html) has a lot more detail. +Download files from a reMarkable tablet. -The size of each document is calculated by the tablet itself. It *looks like* it's the total of the sizes of all files which make up that document, including pen strokes and page thumbnail images. This is not the size you can expect the PDF to be if you download the file, from what I've seen the downloaded PDFs end up being anywhere from half to five times this size. +Commands -### Download documents to PDF files + list ___ List all files on tablet. + download ___ Download one or more documents to PDF file(s). -To download one or more individual documents as PDF files, first `cd` into the directory where you want to download the files. + version Show the program's version info + help Show this help message. -``` -$ mkdir ~/rm2-backup -$ cd ~/rm2-backup/ -``` +Options -Then, run "`rmweb download PATTERN`". The pattern can be either a UUID ... +-p Download PDF files. -``` -$ rmweb download 9e6891eb-2558-4e70-b6fc-d03b2d75614b -Downloading 'Quick sheets.pdf' ... 2577411 ... ok +-d Download RMDOC files. This requires that the tablet have + software version version 3.10 or later. + +-a Download all available file types. + +-c Collapse filenames, i.e. don't create any sub-directories. + All files will be written to the current directory. + +-f Overwrite existing files. + +-I ___ Specify the tablet's IP address. Default is '10.11.99.1', + which the tablet uses when connected via USB cable. Note that + unless you've "hacked" your tablet, the web interface is not + available via any interface other than the USB cable. + +-D Show debugging messages. + +-h Show this help message. + +If no file types are explicitly requested (i.e. no '-a', '-p' or '-d' +options are used), the program will download PDF files only by default. + +Commands with "___" after them allow you to specify one or more patterns +to search for. Only matching documents will be (listed, downloaded, etc.) +If a UUID is specified, that *exact* document will be selected. Otherwise, +all documents whose names (as seen in the tablet's UI) contain the pattern +will be selected. ``` -... or a portion of the filename ... +This example is from v0.07. You may see different output if you're using a different version. + +## Check the version + +The "`rmweb version`" command will show you the version number, along with information about the specific code it was built from in the git repo. ``` -$ rmweb download quick -Downloading 'Quick sheets-1.pdf' ... 2577411 ... ok +$ rmweb version +rmweb-darwin-arm64 version 0.04 +Built 2023-12-18T17:44:17Z from v0.04-0-g17c101b +https://github.com/kg4zow/rmweb/ ``` -... or nothing at all, in which case it will download all documents. +## List Documents on the Tablet + +To list the files on the tablet, use "`rmweb list`". ``` -$ rmweb download -Creating 'Amateur Radio' ... ok -Downloading 'Amateur Radio/D-STAR.pdf' ... 1792627 ... ok -Downloading 'Amateur Radio/RTL-SDR.pdf' ... 120895 ... ok -Downloading 'Documentation to write.pdf' ... 674706 ... ok -Creating 'Ebooks' ... ok -Downloading 'Ebooks/The Art of Unix Programming.pdf' ... 7856878 ... ok -Downloading 'Ebooks/The Cathedral & the Bazaar.pdf' ... 1382013 ... ok +$ rmweb list +UUID Size Pages Name +------------------------------------ --------- ----- ------------------------------------------ +22e6d931-c205-4d86-b022-04b6a0527b67 Amateur Radio/ +ec1989b1-bc41-40d7-a8f7-768b5be42bfd 38048 1 Amateur Radio/GMRS +f7acd9d5-6c98-475d-b7bf-5cbb31501720 95486 1 Amateur Radio/Icom ID-5100 +3314ef15-3b23-49dc-86f4-c89696963076 114720 2 Amateur Radio/Quansheng UV-K5(8) aka UV-K6 +9d5198b0-ce76-4f58-ba0a-a1a058678695 277447 1 Amateur Radio/RTL-SDR +f67c74d2-7d23-4587-95bd-7a6e8ebaed2c Ebooks/ +8efcdb0a-891f-40cb-901f-7c5bc0df7ad1 78249189 541 Ebooks/A City on Mars +9800e36b-5a0c-4eb7-aedc-72c1845d2816 5615420 264 Ebooks/Chokepoint Capitalism +cc2135a3-08ea-4ae5-be77-8f455b039451 8025642 684 Ebooks/The Art of Unix Programming +702ef913-16a0-47b1-806e-1769f251b06b 4241185 306 Ebooks/The Cathedral & the Bazaar +... +24f6b013-054e-4706-9248-3d3d97d0d268 606562 8 Quick sheets +225a451f-61c9-4ffb-96ad-cbe7b7bb530c 987530 9 TODO +015deb02-0589-462a-bc98-3034d7d23628 Work/ +6f3d00ae-925b-48a0-83fb-963644bd7747 19915627 380 Work/2024 Daily ... -Downloading 'Quick sheets.pdf' ... 2577411 ... ok -Downloading 'ReMarkable 2 Info.pdf' ... 1451907 ... ok -Downloading 'TODO.pdf' ... 258263 ... ok -Creating 'Work' ... ok -Downloading 'Work/2023-11 Daily.pdf' ... 5114386 ... ok -Downloading 'Work/2023-12 Daily.pdf' ... 2464473 ... ok ``` -Notes about patterns: +The UUID values are the internal identifiers for each document. The files within the tablet that make up each document, all have this as part of their filename. If you're curious, [this page](https://remarkable.jms1.info/info/filesystem.html) has a lot more detail. + +The size of each document is calculated by the tablet itself. It *looks like* it's the total of the sizes of all files which make up that document, including pen strokes and page thumbnail images. This is not the size you can expect the downloaded files to be. From what I've seen ... + +* PDF files can be anywhere from half to five times this size. +* `.rmdoc` files can be anywhere from 0.5 to about 1.1 times this size. + +### Patterns -* If multiple patterns are specified, all files which match *any* of the patterns will be downloaded. +You can specify one or more patterns when listing or downloading files. If you do, any documents whose names match one or more of the patterns will be downloaded. + +``` +$ rmweb list quick unix +UUID Size Pages Name +------------------------------------ ------- ----- ---------------------------------- +cc2135a3-08ea-4ae5-be77-8f455b039451 8025642 684 Ebooks/The Art of Unix Programming +24f6b013-054e-4706-9248-3d3d97d0d268 606562 8 Quick sheets +``` + +#### Notes * If you need to specify a pattern containing spaces, you should quote it. For example ... ``` - $ rmweb download quick brown fox + $ rmweb list quick brown fox ``` - This command would download any documents with "quick" in the name, OR with "brown" in the name, OR with "fox" in the name. + This command would list any documents with "quick" in the name, OR with "brown" in the name, OR with "fox" in the name. ``` - $ rmweb download "quick brown fox" + $ rmweb list "quick brown fox" ``` This command would only download documents with the string "quick brown fox" in their name. @@ -196,11 +232,99 @@ Notes about patterns: * If a pattern *looks like* a UUID (i.e. has the "8-4-4-4-12 hex digits" structure), the program will look it up by UUID rather than searching for it by filename. -* If no patterns are specified (i.e. just "`rmweb download`" by itself), the program will download ALL documents. +* If no patterns are specified (i.e. just "`rmweb list`" or "`rmweb download`" by itself), the program will list or download ALL documents. + +## Download Documents + +To download one or more individual documents, first `cd` into the directory where you want to download the files. + +``` +$ mkdir ~/rm2-backup +$ cd ~/rm2-backup/ +``` + +Then, run the appropriate "`rmweb download`" command. + +### Selecting file formats + +* The `-p` option tells the program to download PDF files. + + ``` + $ rmweb -p download + ``` + +* The `-d` option tells the program to download RMDOC files. + + ``` + $ rmweb -d download + ``` + +* The `-a` option tells the program to download all available file formats. Currently (for v0.07) this means PDF and RMDOC, but if more file formats are added in the future, this option will include those new formats. + + ``` + $ rmweb -a download + ``` + +* If none of these options are used, the program will download just PDF files. + +### Selecting which files to download + +The `rmweb download` command supports the same pattern-matching options as the `rmweb list` command. The notes in the Patterns section above, apply here as well. + +Each pattern can be either a UUID ... + +``` +$ rmweb download 24f6b013-054e-4706-9248-3d3d97d0d268 +Downloading 'Quick sheets.pdf' ... 2577411 ... ok +``` + +... or a portion of the filename ... + +``` +$ rmweb -d download quick unix +Creating 'Ebooks' ...ok +Downloading 'Ebooks/The Art of Unix Programming.rmdoc' ... 5804725 ... ok +Downloading 'Quick sheets.rmdoc' ... 347002 ... ok +``` + +... or nothing at all, in which case it will download all documents. + +``` +$ rmweb -a download +Creating 'Amateur Radio' ...ok +Downloading 'Amateur Radio/GMRS.pdf' ... 27201 ... ok +Downloading 'Amateur Radio/GMRS.rmdoc' ... 31160 ... ok +Downloading 'Amateur Radio/Icom ID-5100.pdf' ... 48021 ... ok +Downloading 'Amateur Radio/Icom ID-5100.rmdoc' ... 55268 ... ok +Downloading 'Amateur Radio/Quansheng UV-K5(8) aka UV-K6.pdf' ... 69276 ... ok +Downloading 'Amateur Radio/Quansheng UV-K5(8) aka UV-K6.rmdoc' ... 78329 ... ok +Downloading 'Amateur Radio/RTL-SDR.pdf' ... 120784 ... ok +Downloading 'Amateur Radio/RTL-SDR.rmdoc' ... 151231 ... ok +Creating 'Ebooks' ...ok +Downloading 'Ebooks/42.pdf' ... 53960677 ... ok +Downloading 'Ebooks/42.rmdoc' ... 52533573 ... ok +Downloading 'Ebooks/A City on Mars.pdf' ... 78018531 ... ok +Downloading 'Ebooks/A City on Mars.rmdoc' ... 60687232 ... ok +Downloading 'Ebooks/Chokepoint Capitalism.pdf' ... 4259286 ... ok +Downloading 'Ebooks/Chokepoint Capitalism.rmdoc' ... 3732748 ... ok +Downloading 'Ebooks/The Art of Unix Programming.pdf' ... 7856878 ... ok +Downloading 'Ebooks/The Art of Unix Programming.rmdoc' ... 5804725 ... ok +Downloading 'Ebooks/The Cathedral & the Bazaar.pdf' ... 1382013 ... ok +Downloading 'Ebooks/The Cathedral & the Bazaar.rmdoc' ... 3962084 ... ok +... +Downloading 'Quick sheets.pdf' ... 309012 ... ok +Downloading 'Quick sheets.rmdoc' ... 347002 ... ok +Downloading 'TODO.pdf' ... 502225 ... ok +Downloading 'TODO.rmdoc' ... 569832 ... ok +Creating 'Work' ...ok +Downloading 'Work/2024 Daily.pdf' ... 12172136 ... ok +Downloading 'Work/2024 Daily.rmdoc' ... 7485556 ... ok +... +``` -Other notes: +### Other notes -* As each file downloads, the program shows a counter of how many bytes have been read from the tablet. For larger files you'll notice a time delay before this counter starts. This is because the program uses the same API used by the [`http://10.11.99.1/`](http://10.11.99.1) web interface, and this delay is when the tablet is building the PDF file. +* As each file downloads, the program shows a counter of how many bytes have been read from the tablet. For larger files you'll notice a time delay before this counter starts. This delay is when the tablet is building the PDF file. * If an output file already exists, the program will add "`-1`", "`-2`", etc. to the filename until it finds a name which doesn't already exist. If you want to *overwrite* existing files, use the "`-f`" option. diff --git a/do_download.go b/do_download.go index 8218ef7..ed76945 100644 --- a/do_download.go +++ b/do_download.go @@ -7,11 +7,36 @@ package main import ( "fmt" + "io" "os" "sort" "strings" ) +/////////////////////////////////////////////////////////////////////////////// +// +// Passthru wrapper for io.Reader, prints total bytes while reading +// used by download_xxx() functions + +type PassThru struct { + io.Reader + total int64 +} + +func (pt *PassThru) Read( p []byte ) ( int , error ) { + n, err := pt.Reader.Read( p ) + pt.total += int64( n ) + + if err == nil { + x := fmt.Sprintf( "%d" , pt.total ) + b := fmt.Sprintf( strings.Repeat( "\b" , len( x ) ) ) + + fmt.Print( x , b ) + } + + return n , err +} + /////////////////////////////////////////////////////////////////////////////// // // Select and download one or more files @@ -95,12 +120,21 @@ func do_download( args ...string ) { //////////////////////////////////////// // Download the file - lname := the_files[uuid].full_name + ".pdf" + lname_pdf := the_files[uuid].full_name + ".pdf" + lname_rmdoc := the_files[uuid].full_name + ".rmdoc" + if ! flag_overwrite { - lname = safe_filename( lname ) + lname_pdf = safe_filename( lname_pdf ) + lname_rmdoc = safe_filename( lname_rmdoc ) } - download_pdf( uuid , lname ) + if flag_dl_pdf { + download_pdf( uuid , lname_pdf ) + } + + if flag_dl_rmdoc { + download_rmdoc( uuid , lname_rmdoc ) + } } } diff --git a/download_pdf.go b/download_pdf.go index 2daa7f1..b20a06a 100644 --- a/download_pdf.go +++ b/download_pdf.go @@ -13,35 +13,11 @@ import ( "io/fs" "net/http" "os" - "strings" ) /////////////////////////////////////////////////////////////////////////////// // -// Passthru wrapper for io.Reader, prints total bytes while reading - -type PassThru struct { - io.Reader - total int64 -} - -func (pt *PassThru) Read( p []byte ) ( int , error ) { - n, err := pt.Reader.Read( p ) - pt.total += int64( n ) - - if err == nil { - x := fmt.Sprintf( "%d" , pt.total ) - b := fmt.Sprintf( strings.Repeat( "\b" , len( x ) ) ) - - fmt.Print( x , b ) - } - - return n , err -} - -/////////////////////////////////////////////////////////////////////////////// -// -// Download a file +// Download a PDF file func download_pdf( uuid string , localfile string ) { @@ -69,27 +45,24 @@ func download_pdf( uuid string , localfile string ) { err := os.Mkdir( dir , 0755 ) if err != nil { - panic( fmt.Sprintf( "ERROR: %v" , err ) ) + fmt.Printf( "ERROR: %v\n" , err ) + os.Exit( 1 ) } - fmt.Println( "ok" ) + fmt.Println( " ok" ) + } else if err != nil { //////////////////////////////////////// // os.Stat() had some other error - panic( fmt.Sprintf( "ERROR: os.Stat('%s'): %v\n" , dir , err ) ) - } else if ( ( s.Mode() & fs.ModeDir ) != 0 ) { - //////////////////////////////////////// - // exists and is a directory + fmt.Printf( "ERROR: os.Stat('%s'): %v\n" , dir , err ) + os.Exit( 1 ) - if flag_debug { - fmt.Printf( "DEBUG '%s' exists and is a directory\n" , dir ) - } - } else { + } else if ( ( s.Mode() & fs.ModeDir ) == 0 ) { //////////////////////////////////////// // exists and is not a directory - panic( fmt.Sprintf( "ERROR: '%s' exists and is not a directory\n" , dir ) ) + fmt.Printf( "ERROR: '%s' exists and is not a directory\n" , dir ) os.Exit( 1 ) } @@ -108,7 +81,8 @@ func download_pdf( uuid string , localfile string ) { resp, err := http.Get( url ) if err != nil { - panic( fmt.Sprintf( "ERROR: %v" , err ) ) + fmt.Printf( "ERROR: %v" , err ) + os.Exit( 1 ) } defer resp.Body.Close() @@ -118,7 +92,8 @@ func download_pdf( uuid string , localfile string ) { dest, err := os.Create( localfile ) if err != nil { - panic( fmt.Sprintf( "ERROR: os.Create('%s'): %v" , localfile , err ) ) + fmt.Printf( "ERROR: os.Create('%s'): %v" , localfile , err ) + os.Exit( 1 ) } defer dest.Close() @@ -130,7 +105,8 @@ func download_pdf( uuid string , localfile string ) { total, err := io.Copy( dest , src ) if err != nil { - panic( fmt.Sprintf( "ERROR: os.Copy(): %v" , err ) ) + fmt.Printf( "ERROR: os.Copy(): %v" , err ) + os.Exit( 1 ) } //////////////////////////////////////// diff --git a/download_rmdoc.go b/download_rmdoc.go new file mode 100644 index 0000000..b5fe93e --- /dev/null +++ b/download_rmdoc.go @@ -0,0 +1,173 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// rmweb/download_rmdoc.go +// John Simpson 2024-09-07 +// +// Download an RMDOC file from a reMarkable tablet + +package main + +import ( + "fmt" + "io" + "io/fs" + "net/http" + "os" +) + +/////////////////////////////////////////////////////////////////////////////// +// +// Is a file a ZIP file or not? + +func is_zipfile( filename string ) bool { + + f, err := os.Open( filename ) + if err != nil { + fmt.Printf( "ERROR: can't read %s: %v\n" , filename , err ) + os.Exit( 1 ) + } + defer f.Close() + + b := make( []byte , 4 ) + nr, err := f.Read( b ) + if err != nil { + fmt.Sprintf( "ERROR: can't read 4 bytes from %s: %v\n" , filename , err ) + os.Exit( 1 ) + } + + if nr != 4 { + fmt.Printf( "ERROR: expected 4 bytes from %s, only got %d" , filename , nr ) + os.Exit( 1 ) + } + return ( b[0] == 0x50 && b[1] == 0x4b && b[2] == 0x03 && b[3] == 0x04 ) +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Download an RMDOC file + +var know_can_rmdoc bool = false +var can_rmdoc bool = false + +func download_rmdoc( uuid string , localfile string ) { + + //////////////////////////////////////////////////////////// + // If the output filename contains any directory names, + // make sure any necessary directories exist. + + for n := 1 ; n < len( localfile ) ; n ++ { + if localfile[n] == '/' { + dir := localfile[:n] + + if flag_debug { + fmt.Printf( "checking dir='%s'\n" , dir ) + } + + //////////////////////////////////////// + // Check the directory + + s,err := os.Stat( dir ) + if os.IsNotExist( err ) { + //////////////////////////////////////// + // doesn't exist yet - create it + + fmt.Printf( "Creating '%s' ..." , dir ) + + err := os.Mkdir( dir , 0755 ) + if err != nil { + fmt.Printf( "ERROR: %v\n" , err ) + os.Exit( 1 ) + } + + fmt.Println( " ok" ) + + } else if err != nil { + //////////////////////////////////////// + // os.Stat() had some other error + + fmt.Printf( "ERROR: os.Stat('%s'): %v\n" , dir , err ) + os.Exit( 1 ) + + } else if ( ( s.Mode() & fs.ModeDir ) == 0 ) { + //////////////////////////////////////// + // exists and is not a directory + + fmt.Printf( "ERROR: '%s' exists and is not a directory\n" , dir ) + os.Exit( 1 ) + } + + } // if localfile[n] == '/' + } // for n + + //////////////////////////////////////////////////////////// + // Download the file + + fmt.Printf( "Downloading '%s' ... " , localfile ) + + //////////////////////////////////////// + // Request the file + + url := "http://" + tablet_addr + "/download/" + uuid + "/rmdoc" + + resp, err := http.Get( url ) + if err != nil { + fmt.Printf( "ERROR: can't download '%s': %v" , url , err ) + os.Exit( 1 ) + } + + //////////////////////////////////////// + // Create output file + + dest, err := os.Create( localfile ) + if err != nil { + fmt.Printf( "ERROR: can't create '%s': %v\n" , localfile , err ) + } + + //////////////////////////////////////// + // Copy the output to the file + + var src io.Reader = &PassThru{ Reader: resp.Body } + + total, err := io.Copy( dest , src ) + if err != nil { + fmt.Printf( "ERROR: os.Copy(): %v" , err ) + os.Exit( 1 ) + } + + //////////////////////////////////////// + // Finished with transfer + + dest.Close() + resp.Body.Close() + fmt.Printf( "%d ... ok\n" , total ) + + //////////////////////////////////////// + // Check whether the output file is a ZIP file + + if ! know_can_rmdoc { + know_can_rmdoc = true + + if is_zipfile( localfile ) { + can_rmdoc = true + } + + if ! can_rmdoc { + //////////////////////////////////////// + // Remove the file we just downloaded (it isn't an RMDOC file) + + os.Remove( localfile ) + + //////////////////////////////////////// + // Tell the user what's going on + + if ! flag_dl_pdf { + fmt.Println( "FATAL: this tablet's software cannot download .rmdoc files, cannot continue" ) + os.Exit( 1 ) + } else { + fmt.Printf( "WARNING: this tablet's software cannot download .rmdoc files\n" ) + flag_dl_rmdoc = false + } + } + } + +} diff --git a/main.go b/main.go index 4aa4057..ff25166 100644 --- a/main.go +++ b/main.go @@ -33,11 +33,17 @@ var ( /////////////////////////////////////////////////////////////////////////////// // // Values will be set by command line options +// +// NOTE: if new 'flag_dl_xxx' items are needed to support new file types, +// be sure to update the check at the end of download_rmdoc() to check for +// the new file types. var flag_debug bool = false var flag_overwrite bool = false var flag_collapse bool = false var tablet_addr string = "10.11.99.1" +var flag_dl_pdf bool = false // default is set in main() below +var flag_dl_rmdoc bool = false // default is set in main() below //////////////////////////////////////// // All files and directories on the tablet @@ -61,7 +67,7 @@ func usage( ) { msg := `%s [options] COMMAND [...] -Download PDF files from a reMarkable tablet. +Download files from a reMarkable tablet. Commands @@ -73,17 +79,29 @@ Commands Options - -c Collapse filenames, i.e. don't create any sub-directories. - All PDFs will be written to the current directory. +-p Download PDF files. + +-d Download RMDOC files. This requires that the tablet have + software version version 3.10 or later. + +-a Download all available file types. + +-c Collapse filenames, i.e. don't create any sub-directories. + All files will be written to the current directory. + +-f Overwrite existing files. - -f Overwrite existing files. +-I ___ Specify the tablet's IP address. Default is '10.11.99.1', + which the tablet uses when connected via USB cable. Note that + unless you've "hacked" your tablet, the web interface is not + available via any interface other than the USB cable. - -I ___ Specify the tablet's IP address. Default is '10.11.99.1', - which the tablet uses when connected via USB cable. Note that - unless you've "hacked" your tablet, the web interface is not - available via any interface other than the USB cable. +-D Show debugging messages. - -h Show this help message. +-h Show this help message. + +If no file types are explicitly requested (i.e. no '-a', '-p' or '-d' +options are used), the program will download PDF files only by default. Commands with "___" after them allow you to specify one or more patterns to search for. Only matching documents will be (listed, downloaded, etc.) @@ -139,7 +157,10 @@ func main() { //////////////////////////////////////// // Parse command line options - var helpme = false + var helpme bool = false + var want_pdf bool = false + var want_rmdoc bool = false + var want_all bool = false flag.Usage = usage flag.BoolVar ( &helpme , "h" , helpme , "" ) @@ -147,6 +168,9 @@ func main() { flag.BoolVar ( &flag_overwrite , "f" , flag_overwrite , "" ) flag.BoolVar ( &flag_collapse , "c" , flag_collapse , "" ) flag.StringVar( &tablet_addr , "I" , tablet_addr , "" ) + flag.BoolVar ( &want_pdf , "p" , want_pdf , "" ) + flag.BoolVar ( &want_rmdoc , "d" , want_rmdoc , "" ) + flag.BoolVar ( &want_all , "a" , want_all , "" ) flag.Parse() //////////////////////////////////////// @@ -156,6 +180,20 @@ func main() { usage() } + //////////////////////////////////////// + // Figure out which file type options were requested + + if want_all { + flag_dl_pdf = true + flag_dl_rmdoc = true + } else if want_pdf || want_rmdoc { + flag_dl_pdf = want_pdf + flag_dl_rmdoc = want_rmdoc + } else { + flag_dl_pdf = true + flag_dl_rmdoc = false + } + //////////////////////////////////////// // Figure out which command we're being asked to run diff --git a/read_files.go b/read_files.go index 8308b82..37893b7 100644 --- a/read_files.go +++ b/read_files.go @@ -17,6 +17,12 @@ import ( "strings" ) +/////////////////////////////////////////////////////////////////////////////// +// +// Replace problematic characters in documents' visible names + +var name_cleaner = strings.NewReplacer( "/" , "_" , "\\" , "_" , ":" , "_" ) + /////////////////////////////////////////////////////////////////////////////// // // read_files @@ -95,11 +101,18 @@ func read_files() ( map[string]DocInfo ) { id := v["ID"].(string) parent := v["Parent"].(string) folder := bool( v["Type"].(string) == "CollectionType" ) - name := v["VissibleName"].(string) + vis_name := v["VissibleName"].(string) + + //////////////////////////////////////// + // Convert all '/', '\', and ':' with underscores + + name := name_cleaner.Replace( vis_name ) + + //////////////////////////////////////// if ! folder { fmt.Sscan( v["sizeInBytes"].(string) , &size ) - pages = int( v["pageCount"].(float64) ) + pages = int( v["pageCount"].(float64) ) } if flag_debug {