Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

[WIP] Download annotated notebooks #34

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
713b03f
Merge remote-tracking branch 'upstream/master'
peerdavid Oct 12, 2018
1660c61
Merge remote-tracking branch 'origin/master'
peerdavid Oct 31, 2018
b23b692
Changes to implement geta
peerdavid Oct 31, 2018
cc06bd9
Finished geta for svg files
peerdavid Nov 1, 2018
f967740
Finished geta for svg
peerdavid Nov 1, 2018
01cf2b4
Own dir for notebook
peerdavid Nov 1, 2018
09d6c6d
Move into own directory
peerdavid Nov 1, 2018
9e48489
Fixed exportNotebook script for rmapi usage
peerdavid Nov 1, 2018
68ebecd
Export of annotated pdf
peerdavid Nov 1, 2018
4038663
Pdf export for pdf files
peerdavid Nov 1, 2018
48f65d1
Updated documentation
peerdavid Nov 1, 2018
ff515f6
Change to line2svg
peerdavid Nov 1, 2018
2417149
Fix for 1.6 with rM2svg
peerdavid Nov 3, 2018
238f38c
Fixed for RM version 1.6
peerdavid Nov 3, 2018
5d635b3
Using invisible tmp folders
peerdavid Nov 4, 2018
6aea236
Test with new rM2svg script
peerdavid Nov 4, 2018
d78292c
Minor changes
peerdavid Nov 4, 2018
1f257b2
Fixes for notebook without background pdfs
peerdavid Nov 4, 2018
c29be12
Support templates
peerdavid Nov 4, 2018
94d1be9
Updated colors and docu
peerdavid Nov 4, 2018
b805445
Deleted unused code
peerdavid Nov 4, 2018
f3d4542
Removed logging statements
peerdavid Nov 4, 2018
a57cbaa
Added mgeta to get a folder annotated
peerdavid Nov 4, 2018
ba6d0a5
Updated readme for mgeta
peerdavid Nov 4, 2018
5662904
Minor changes
peerdavid Nov 4, 2018
3b978a1
Test with different svg to pdf conversion
peerdavid Nov 6, 2018
ac4b5c0
Test with inkscape
peerdavid Nov 6, 2018
a45c4fd
Store template in template tmp folder
peerdavid Nov 7, 2018
982d8f2
Using rsvg-convert as its faster.
peerdavid Nov 7, 2018
cb7771f
Update README.md
peerdavid Nov 7, 2018
cbc6c0c
Update README.md
peerdavid Nov 7, 2018
b09390d
Update README.md
peerdavid Nov 7, 2018
2315caa
Prepare for PR
peerdavid Dec 4, 2018
c6b6aa0
Merge remote-tracking branch 'origin/master'
peerdavid Dec 4, 2018
0dfee84
Update annotated pdf file only if modified date time changed.
peerdavid Dec 23, 2018
43bc787
Better logging
peerdavid Dec 23, 2018
f872dfd
Improved performance
peerdavid Dec 23, 2018
0c857d9
Fixed invalid logging
peerdavid Dec 23, 2018
ea56392
Ignore epub files and simply move pdfs that are not annotated.
peerdavid Dec 23, 2018
e434018
Removed dependency to pdfunite
peerdavid Dec 23, 2018
9fbfb30
1. Added verification for geta. If tools are missing geta and mgeta a…
peerdavid Dec 23, 2018
b36b097
Rotate annotations whenever the pdf file is rotated
peerdavid Dec 24, 2018
964ed36
Minor fix
peerdavid Feb 21, 2019
3ca7ec1
uising github.com/peerdavid; fixed 1.7 bug with missing page count
peerdavid Jul 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## rmapi master

* Added geta and mgeta command to read annotated notebooks

* Increased http timeout to 5 minutes to enable upload of larger files

* Add user-agent header to be a good reMarkable citezen
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ Install and build the project:

`go get -u github.com/juruen/rmapi`

Also the following tools are needed to read annotated notebooks:
- convert (Note: Policy must be set to read|write for pdfs: https://stackoverflow.com/questions/42928765/convertnot-authorized-aaaa-error-constitute-c-readimage-453)
- rsvg-convert
- pdf270
- pdftk
- pdfunite
- python3


## Binary

You can download an already built version for either Linux or OSX from [releases](https://github.com/juruen/rmapi/releases).
Expand Down Expand Up @@ -88,6 +97,11 @@ mput /Papers

Use `get path_to_file` to download a file from the cloud to your local computer.

## Download a file with annotations
Use `geta path_to_file` to download a file from the cloud to your local computer.
If there exists a template folder under `%HOME/Templates/Remarkable`, then those
are used for notebooks. Otherwise the background is white.

## Recursively download directories and files

Use `mget path_to_dir` to recursively download all the files in that directory.
Expand All @@ -98,6 +112,17 @@ E.g: download all the files
mget .
```

## Recursively download directories and files with annotations

Use `mgeta path_to_dir` to recursively download all the annotated files in that directory.

E.g: download all the files

```
mgeta .
```


## Create a directoy

Use `mkdir path_to_new_dir` to create a new directory
Expand Down Expand Up @@ -148,3 +173,8 @@ If you want to give it a go, you can run:
```bash
rmapi --fuse-mount mount_point
```


# Thanks to
[1] rmapi, https://github.com/juruen/rmapi <br />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop this line :)

[1] rM2svg, https://github.com/reHackable/maxio <br />
185 changes: 185 additions & 0 deletions shell/geta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package shell

import (
"errors"
"fmt"
"time"
"os/exec"
"strings"
"archive/zip"
"os"
"io"
"github.com/abiosoft/ishell"
"path/filepath"
"github.com/juruen/rmapi/model"
)


func getaCmd(ctx *ShellCtxt) *ishell.Cmd {
return &ishell.Cmd{
Name: "geta",
Help: "copy remote file to local with annotations. Optional [svg, pdf]",
Completer: createEntryCompleter(ctx),
Func: func(c *ishell.Context) {
// Parse cmd args
if len(c.Args) == 0 {
c.Err(errors.New("missing source file"))
return
}
srcName := c.Args[0]

// Download document as zip
node, err := ctx.api.Filetree.NodeByPath(srcName, ctx.node)
if err != nil || node.IsDirectory() {
c.Err(errors.New("file doesn't exist"))
return
}

err = getAnnotatedDocument(ctx, node, "")
if err != nil {
c.Err(err)
return
}
},
}
}


func getAnnotatedDocument(ctx *ShellCtxt, node *model.Node, path string) error {
zipFile := fmt.Sprintf(".%s.zip", node.Name())

// Set output name and output file name
output := node.Name()
if(path != ""){
output = fmt.Sprintf("%s/%s", path, node.Name())
}
outputFileName := fmt.Sprintf("%s.pdf", output)

// Parse last modified time of document on api
modifiedClientTime, err := time.Parse(time.RFC3339Nano, node.Document.ModifiedClient)
if err != nil {
// If we could not parse the time correctly, we still continue
// with the execution such that the pdf is downloaded...
fmt.Println(err)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please use the logging facilities instead of standard output/err?

Something like:

  log.Trace.Println(err)

or in this case, if this is just a warning:

  log.Warning.Println(err)

In general, we should try to use the log facilities instead of fmt.Print as much as we can in this file. Either that or use the ishell context to print relevant stuff.

fmt.Println("(Warning) Could not parse modified time. Overwrite existing file.")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

modifiedClientTime = time.Now().Local()
}

// If document has not changed since last update skip pdf convertion
outputFile, err := os.Stat(outputFileName)
if !os.IsNotExist(err) {
outputFileModTime := outputFile.ModTime()
if(outputFileModTime.Equal(modifiedClientTime)){
fmt.Println("Nothing changed since last download. Skip. ")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. This looks like log.Trace candidate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your comments => fixed all invalid log statements

os.Remove(zipFile)
return nil
}
}

// Download document if content has changed
err = ctx.api.FetchDocument(node.Document.ID, zipFile)
if err != nil {
return errors.New(fmt.Sprintf("Failed to download file %s with %s", node.Name(), err.Error()))
}

// Unzip document
tmpFolder := fmt.Sprintf(".%s", node.Document.ID)
_, err = unzip(zipFile, tmpFolder)
if err != nil {
os.Remove(zipFile)
return err
}

// Convert to pdf
exportPdf := os.Getenv("GOPATH") + "/src/github.com/juruen/rmapi/tools/exportAnnotatedPdf"
rM2svg := os.Getenv("GOPATH") + "/src/github.com/juruen/rmapi/tools/rM2svg"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we create a file/function where we encapsulate checks to verify if the required tools are installed. We can then use it in https://github.com/juruen/rmapi/blob/master/shell/shell.go#L54 to optinally add these new commands to the shell.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I will fix a bug that I found right now and afterwards I will start with this verification

_, err = exec.Command(
"/bin/bash",
exportPdf,
tmpFolder,
node.Document.ID,
output,
rM2svg).CombinedOutput()

if err != nil {
fmt.Println(err)
}

if err != nil {
os.Remove(zipFile)
os.RemoveAll(tmpFolder)
return err
}

// Set creation time
err = os.Chtimes(outputFileName, modifiedClientTime, modifiedClientTime)
if err != nil {
fmt.Println("(Warning) Could not set modified time of pdf file.")
}

// Cleanup
os.Remove(zipFile)
os.RemoveAll(tmpFolder)
fmt.Println("Ok.")
return nil
}

// From https://golangcode.com/unzip-files-in-go/
func unzip(src string, dest string) ([]string, error) {

var filenames []string

r, err := zip.OpenReader(src)
if err != nil {
return filenames, err
}
defer r.Close()

for _, f := range r.File {

rc, err := f.Open()
if err != nil {
return filenames, err
}
defer rc.Close()

// Store filename/path for returning and using later on
fpath := filepath.Join(dest, f.Name)

// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return filenames, fmt.Errorf("%s: illegal file path", fpath)
}

filenames = append(filenames, fpath)

if f.FileInfo().IsDir() {

// Make Folder
os.MkdirAll(fpath, os.ModePerm)

} else {

// Make File
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return filenames, err
}

outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return filenames, err
}

_, err = io.Copy(outFile, rc)

// Close the file without defer to close before next iteration of loop
outFile.Close()

if err != nil {
return filenames, err
}

}
}
return filenames, nil
}
66 changes: 66 additions & 0 deletions shell/mgeta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package shell

import (
"errors"
"fmt"
"os"
"path"

"github.com/abiosoft/ishell"
"github.com/juruen/rmapi/filetree"
"github.com/juruen/rmapi/model"
)

func mgetaCmd(ctx *ShellCtxt) *ishell.Cmd {
return &ishell.Cmd{
Name: "mgeta",
Help: "recursively copy remote directory to local with annotations",
Completer: createDirCompleter(ctx),
Func: func(c *ishell.Context) {
if len(c.Args) == 0 {
c.Err(errors.New(("missing source dir")))
return
}

srcName := c.Args[0]

node, err := ctx.api.Filetree.NodeByPath(srcName, ctx.node)

if err != nil || node.IsFile() {
c.Err(errors.New("directory doesn't exist"))
return
}

visitor := filetree.FileTreeVistor{
func(currentNode *model.Node, currentPath []string) bool {
idxDir := 0
if srcName == "." && len(currentPath) > 0 {
idxDir = 1
}

dst := "./" + filetree.BuildPath(currentPath[idxDir:], currentNode.Name())
dir := path.Dir(dst)
os.MkdirAll(dir, 0766)

if currentNode.IsDirectory() {
return filetree.ContinueVisiting
}

c.Printf("downloading [%s]...", dst)

err = getAnnotatedDocument(ctx, currentNode, fmt.Sprintf("%s", dir))

if err == nil {
return filetree.ContinueVisiting
}

c.Err(errors.New(fmt.Sprintf("Failed to downlaod file %s", currentNode.Name())))

return filetree.ContinueVisiting
},
}

filetree.WalkTree(node, visitor)
},
}
}
2 changes: 2 additions & 0 deletions shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func RunShell(apiCtx *api.ApiCtx) error {
shell.AddCmd(cdCmd(ctx))
shell.AddCmd(getCmd(ctx))
shell.AddCmd(mgetCmd(ctx))
shell.AddCmd(getaCmd(ctx))
shell.AddCmd(mgetaCmd(ctx))
shell.AddCmd(mkdirCmd(ctx))
shell.AddCmd(rmCmd(ctx))
shell.AddCmd(mvCmd(ctx))
Expand Down
Loading