├── ComClient
├── Debug
│ └── ComClient.tlog
│ │ ├── unsuccessfulbuild
│ │ └── ComClient.lastbuildstate
├── ComClient.cpp
├── ComClient.vcxproj.filters
└── ComClient.vcxproj
├── OnenoteAddin
├── RegisterAddIn.reg
├── UnregisterAddIn.reg
├── Resources
│ ├── Settings.ico
│ ├── Settings.png
│ ├── DownloadDoc.ico
│ ├── DownloadDoc.png
│ ├── opacity_grey.png
│ └── loading_spinner.gif
├── packages.config
├── OnenoteAddin.csproj.user
├── app.config
├── ComLocalServer
│ ├── IClassFactory.cs
│ ├── ReferenceCountedObject.cs
│ ├── GarbageCollection.cs
│ └── ClassFactoryBase.cs
├── ribbon.xml
├── PreviewForm.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── PreviewForm.Designer.cs
├── CCOMStreamWrapper.cs
├── PreviewForm.resx
└── SettingsForm.cs
├── OnenoteAddinSetup
└── reMarkableSync.OneNoteAddin.tmp
├── ConsoleTest
├── packages.config
├── App.config
├── Properties
│ └── AssemblyInfo.cs
├── ConsoleTest.csproj
└── Program.cs
├── RemarkableSync
├── document
│ ├── DocumentMetadata.cs
│ ├── content
│ │ ├── IDocumentContent.cs
│ │ ├── DocumentContentV1.cs
│ │ ├── DocumentContentV2.cs
│ │ └── DocumentContent.cs
│ ├── v6
│ │ ├── SceneItems
│ │ │ ├── SceneItem.cs
│ │ │ ├── RmRectangle.cs
│ │ │ ├── ParagraphStyle.cs
│ │ │ ├── RmPoint.cs
│ │ │ ├── GlyphRange.cs
│ │ │ ├── RmLine.cs
│ │ │ ├── RmText.cs
│ │ │ └── Group.cs
│ │ └── RmLines.cs
│ ├── RmPenColor.cs
│ ├── RmPen.cs
│ ├── RmItem.cs
│ ├── v5
│ │ ├── RmLinesDrawer.cs
│ │ └── RmLines.cs
│ ├── Extrametadata.cs
│ ├── PageBinary.cs
│ ├── Crdt.cs
│ ├── RmDocument.cs
│ └── TaggedBinaryReader.cs
├── Interfaces
│ ├── IRmPageBinary.cs
│ ├── IConfigStore.cs
│ ├── ICloudApiClient.cs
│ └── IRmDataSource.cs
├── RmLocalDoc.cs
├── App.config
├── MyScript
│ ├── MyScriptRequest.cs
│ ├── MyScriptResult.cs
│ └── MyScriptClient.cs
├── RmCloudV1DownloadedDoc.cs
├── CloudApiV2Client.cs
├── Properties
│ └── AssemblyInfo.cs
├── RmSftpDownloadedDoc.cs
├── LocalFolderDataSource.cs
├── CloudApiV1Client.cs
├── RmSftpDataSource.cs
├── WinRegistryConfigStore.cs
├── RmCloudV2DownloadedDoc.cs
├── V2HttpHelper.cs
├── RmSftpJsonTypes.cs
├── RmLines.cs
└── RemarkableSync.csproj
├── .gitignore
├── LICENSE
├── RemarkableSync.sln
└── readme.md
/ComClient/Debug/ComClient.tlog/unsuccessfulbuild:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/OnenoteAddin/RegisterAddIn.reg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/RegisterAddIn.reg
--------------------------------------------------------------------------------
/OnenoteAddin/UnregisterAddIn.reg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/UnregisterAddIn.reg
--------------------------------------------------------------------------------
/OnenoteAddin/Resources/Settings.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/Resources/Settings.ico
--------------------------------------------------------------------------------
/OnenoteAddin/Resources/Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/Resources/Settings.png
--------------------------------------------------------------------------------
/OnenoteAddin/Resources/DownloadDoc.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/Resources/DownloadDoc.ico
--------------------------------------------------------------------------------
/OnenoteAddin/Resources/DownloadDoc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/Resources/DownloadDoc.png
--------------------------------------------------------------------------------
/OnenoteAddin/Resources/opacity_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/Resources/opacity_grey.png
--------------------------------------------------------------------------------
/OnenoteAddin/Resources/loading_spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddin/Resources/loading_spinner.gif
--------------------------------------------------------------------------------
/OnenoteAddinSetup/reMarkableSync.OneNoteAddin.tmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesf91/reMarkableSync/HEAD/OnenoteAddinSetup/reMarkableSync.OneNoteAddin.tmp
--------------------------------------------------------------------------------
/ConsoleTest/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/OnenoteAddin/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/RemarkableSync/document/DocumentMetadata.cs:
--------------------------------------------------------------------------------
1 | namespace RemarkableSync.document
2 | {
3 | public class DocumentMetadata
4 | {
5 | }
6 | }
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | */.vs/
2 | */bin/
3 | */obj/
4 | */packages/
5 |
6 | .vs/
7 | packages/
8 |
9 |
10 | *.exe
11 | *.log
12 | *.tlb
13 | *.msi
14 | *.vcxproj.user
15 | /backup
16 | /samplepages
17 |
--------------------------------------------------------------------------------
/ComClient/Debug/ComClient.tlog/ComClient.lastbuildstate:
--------------------------------------------------------------------------------
1 | PlatformToolSet=v142:VCToolArchitecture=Native32Bit:VCToolsVersion=14.28.29333:TargetPlatformVersion=10.0.18362.0:
2 | Debug|Win32|D:\Codebase\reMarkable\RemarkableSync\|
3 |
--------------------------------------------------------------------------------
/RemarkableSync/document/content/IDocumentContent.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace RemarkableSync.document
4 | {
5 | public interface IDocumentContent
6 | {
7 | List getPages();
8 | }
9 | }
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ComClient/ComClient.cpp:
--------------------------------------------------------------------------------
1 | // ComClient.cpp : This file contains the 'main' function. Program execution begins and ends there.
2 | //
3 |
4 | #include
5 | #import "OnenoteAddin.tlb"
6 |
7 | int main()
8 | {
9 | std::cout << "Hello World!\n";
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/SceneItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync.document.v6.SceneItems
8 | {
9 | internal class SceneItem
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/RemarkableSync/Interfaces/IRmPageBinary.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.MyScript;
2 | using System;
3 | using System.Drawing;
4 |
5 | namespace RemarkableSync
6 | {
7 | public interface IRmPageBinary
8 | {
9 | Bitmap GetBitmap();
10 |
11 | Tuple GetMyScriptFormat();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/RmRectangle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.NetworkInformation;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RemarkableSync.document.v6.SceneItems
9 | {
10 | internal class RmRectangle
11 | {
12 | public float x, y, w, h;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RemarkableSync/Interfaces/IConfigStore.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync
8 | {
9 | public interface IConfigStore : IDisposable
10 | {
11 | string GetConfig(string key);
12 |
13 | bool SetConfigs(Dictionary configs);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/RemarkableSync/document/content/DocumentContentV1.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace RemarkableSync.document
5 | {
6 | public class DocumentContentV1 : DocumentContent
7 | {
8 | public string[] pages { get; set; }
9 |
10 | public override List getPages()
11 | {
12 | return pages.ToList();
13 | }
14 | }
15 | }
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/ParagraphStyle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync.document.v6.SceneItems
8 | {
9 | //Text paragraph style.
10 | public enum ParagraphStyle
11 | {
12 | BASIC = 0,
13 | PLAIN = 1,
14 | HEADING = 2,
15 | BOLD = 3,
16 | BULLET = 4,
17 | BULLET2 = 5
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/RmPoint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync.document.v6.SceneItems
8 | {
9 | internal class RmPoint
10 | {
11 | public float x;
12 | public float y;
13 | public int speed;
14 | public int direction;
15 | public int width;
16 | public int pressure;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/RemarkableSync/document/RmPenColor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync.document
8 | {
9 | public enum RmPenColor
10 | {
11 | BLACK = 0,
12 | GREY = 1,
13 | WHITE = 2,
14 | YELLOW = 3,
15 | GREEN = 4,
16 | PINK = 5,
17 | BLUE = 6,
18 | RED = 7,
19 | GRAY_OVERLAP = 8
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/OnenoteAddin/OnenoteAddin.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | publish\
5 |
6 |
7 |
8 |
9 |
10 | en-US
11 | false
12 |
13 |
--------------------------------------------------------------------------------
/RemarkableSync/Interfaces/ICloudApiClient.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync
8 | {
9 | interface ICloudApiClient : IDisposable
10 | {
11 | Task> GetAllItems(CancellationToken cancellationToken, IProgress progress);
12 |
13 | Task DownloadDocument(string ID, CancellationToken cancellationToken, IProgress progress);
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RemarkableSync/Interfaces/IRmDataSource.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync
8 | {
9 | public interface IRmDataSource: IDisposable
10 | {
11 | Task> GetItemHierarchy(CancellationToken cancellationToken, IProgress progress);
12 |
13 | Task DownloadDocument(string ID, CancellationToken cancellationToken, IProgress progress);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/GlyphRange.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.NetworkInformation;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RemarkableSync.document.v6.SceneItems
9 | {
10 | internal class GlyphRange
11 | {
12 | public int start;
13 | public int length;
14 | public String text;
15 | public RmPenColor color;
16 | public List rectangles = new List();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/RemarkableSync/RmLocalDoc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace RemarkableSync
10 | {
11 | class RmLocalDoc : RmDocument
12 | {
13 | private V2HttpHelper _httpHelper;
14 | private object _taskProgress;
15 |
16 | public RmLocalDoc(string id) : base(id)
17 | {
18 | LoadDocumentContent();
19 | }
20 |
21 | public RmLocalDoc(string id, string root_path) : base(id, root_path)
22 | {
23 | LoadDocumentContent();
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/OnenoteAddin/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/OnenoteAddin/ComLocalServer/IClassFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace RemarkableSync.OnenoteAddin
5 | {
6 | // Interface IClassFactory is here to provide a C# definition of the
7 | // COM IClassFactory interface.
8 | [
9 | ComImport, // This interface originated from COM.
10 | ComVisible(false), // It is not hard to imagine that this interface must not be exposed to COM.
11 | InterfaceType(ComInterfaceType.InterfaceIsIUnknown), // Indicate that this interface is not IDispatch-based.
12 | Guid("00000001-0000-0000-C000-000000000046") // This GUID is the actual GUID of IClassFactory.
13 | ]
14 | public interface IClassFactory
15 | {
16 | void CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
17 | void LockServer(bool fLock);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/RemarkableSync/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/RmLine.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using System.Net.NetworkInformation;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace RemarkableSync.document.v6.SceneItems
10 | {
11 | internal class RmLine
12 | {
13 | public RmPenColor penColor;
14 | public RmPen pen;
15 | public List points = new List();
16 | public double thickness_scale;
17 | public float starting_length;
18 |
19 | public bool IsVisible()
20 | {
21 | switch (pen)
22 | {
23 | case RmPen.ERASER:
24 | case RmPen.ERASER_AREA:
25 | case RmPen.ERASER_ALL:
26 | return false;
27 | default:
28 | return true;
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/RemarkableSync/MyScript/MyScriptRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace RemarkableSync.MyScript
4 | {
5 |
6 | public class HwrRequestBundle
7 | {
8 | public HwrRequest Request { get; set; }
9 |
10 | public List Bounds { get; set; }
11 |
12 | }
13 |
14 | public class HwrRequest
15 | {
16 | public int xDPI { get; set; }
17 | public int yDPI { get; set; }
18 | public string contentType { get; set; }
19 | public Configuration configuration { get; set; }
20 | public StrokeGroup[] strokeGroups { get; set; }
21 | }
22 |
23 | public class StrokeGroup
24 | {
25 | public Stroke[] strokes { get; set; }
26 | }
27 |
28 | public class Stroke
29 | {
30 | public int[] x { get; set; }
31 | public int[] y { get; set; }
32 | }
33 |
34 | public class Configuration
35 | {
36 | public string lang { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/OnenoteAddin/ribbon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/RemarkableSync/document/RmPen.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RemarkableSync.document
8 | {
9 | public enum RmPen
10 | {
11 | /**
12 | * """
13 | * Stroke pen id representing reMarkable tablet tools.
14 | * Tool examples: ballpoint, fineliner, highlighter or eraser.
15 | *
16 | * XXX this list is from remt pre-v6
17 | **/
18 | BALLPOINT_1 = 2,
19 | BALLPOINT_2 = 15,
20 | CALIGRAPHY = 21,
21 | ERASER = 6,
22 | ERASER_AREA = 8,
23 | ERASER_ALL = 9,
24 | FINELINER_1 = 4,
25 | FINELINER_2 = 17,
26 | HIGHLIGHTER_1 = 5,
27 | HIGHLIGHTER_2 = 18,
28 | MARKER_1 = 3,
29 | MARKER_2 = 16,
30 | MECHANICAL_PENCIL_1 = 7,
31 | MECHANICAL_PENCIL_2 = 13,
32 | PAINTBRUSH_1 = 0,
33 | PAINTBRUSH_2 = 12,
34 | PENCIL_1 = 1,
35 | PENCIL_2 = 14,
36 | SELECTION_BRUSH_1 = 10,
37 | SELECTION_BRUSH_2 = 11,
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 James Fang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/ComClient/ComClient.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/OnenoteAddin/ComLocalServer/ReferenceCountedObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace RemarkableSync.OnenoteAddin
5 | {
6 | ///
7 | /// Summary description for ReferenceCountedObjectBase.
8 | ///
9 | [ComVisible(false)] // This ComVisibleAttribute is set to false so that TLBEXP and REGASM will not expose it nor COM-register it.
10 | public class ReferenceCountedObjectBase
11 | {
12 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
13 | public ReferenceCountedObjectBase()
14 | {
15 | Logger.Debug("constructor called");
16 | // We increment the global count of objects.
17 | ManagedCOMLocalServer.InterlockedIncrementObjectsCount();
18 | }
19 |
20 | ~ReferenceCountedObjectBase()
21 | {
22 | Logger.Debug("destructor called");
23 | // We decrement the global count of objects.
24 | ManagedCOMLocalServer.InterlockedDecrementObjectsCount();
25 | // We then immediately test to see if we the conditions
26 | // are right to attempt to terminate this server application.
27 | ManagedCOMLocalServer.AttemptToTerminateServer();
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/RmText.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace RemarkableSync.document.v6.SceneItems
5 | {
6 | /**
7 | * Block of text.
8 | *
9 | * `items` are a CRDT sequence of strings. The `item_id` for each string refers
10 | * to its first character; subsequent characters implicitly have sequential
11 | * ids.
12 | *
13 | * When formatting is present, some of `items` have a value of an integer
14 | * formatting code instead of a string.
15 | *
16 | * `styles` are LWW values representing a mapping of character IDs to
17 | * `ParagraphStyle` values. These formats apply to each line of text (until the
18 | * next newline).
19 | *
20 | * `pos_x`, `pos_y` and `width` are dimensions for the text block.
21 | *
22 | */
23 | internal class RmText
24 | {
25 | public CrdtSequence items = new CrdtSequence();
26 | public Dictionary> styles = new Dictionary>();
27 | public double pos_x;
28 | public double pos_y;
29 | public float width;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/RemarkableSync/RmCloudV1DownloadedDoc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 |
6 | namespace RemarkableSync
7 | {
8 | public class RmCloudV1DownloadedDoc: RmDocument
9 | {
10 |
11 | public RmCloudV1DownloadedDoc(ZipArchive archive, string id) : base(id)
12 | {
13 | try
14 | {
15 | archive.ExtractToDirectory(_root_path);
16 | }
17 | catch (Exception err)
18 | {
19 | Logger.Error($"failed to extract archive. Error: {err.Message}");
20 | _root_path = "";
21 | throw err;
22 | }
23 |
24 | try
25 | {
26 | string pageFolder = Path.Combine(_root_path, id);
27 | var rmFiles = new List(Directory.EnumerateFiles(pageFolder, "*.rm", SearchOption.TopDirectoryOnly));
28 | _content.pageCount = rmFiles.Count;
29 | }
30 | catch (Exception err)
31 | {
32 | Logger.Error($"failed to get page count. Error: {err.Message}");
33 | _content.pageCount = 0;
34 | }
35 |
36 | LoadDocumentContent();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/ConsoleTest/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/RemarkableSync/CloudApiV2Client.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Net.Http;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace RemarkableSync
9 | {
10 | class CloudApiV2Client : ICloudApiClient
11 | {
12 | private HttpClient _client;
13 | private V2DocTreeProcessor _docTreeProcessor;
14 |
15 | public CloudApiV2Client(HttpClient client)
16 | {
17 | _client = client;
18 | _docTreeProcessor = new V2DocTreeProcessor(client);
19 | }
20 |
21 | public void Dispose()
22 | {
23 | }
24 |
25 | public async Task DownloadDocument(string ID, CancellationToken cancellationToken, IProgress progress)
26 | {
27 | var fileList = _docTreeProcessor.GetFileListForDocument(ID);
28 |
29 | return await Task.Run(() =>
30 | {
31 | return new RmCloudV2DownloadedDoc(ID, fileList, _client, cancellationToken, progress);
32 | });
33 | }
34 |
35 | public async Task> GetAllItems(CancellationToken cancellationToken, IProgress progress)
36 | {
37 | progress.Report("Checking document list for changes");
38 | await _docTreeProcessor.SyncTreeAsync(cancellationToken, progress);
39 | return _docTreeProcessor.GetAllItems();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/OnenoteAddin/PreviewForm.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 | using System.Windows.Forms;
4 |
5 | namespace RemarkableSync.OnenoteAddin
6 | {
7 | public partial class PreviewForm : Form
8 | {
9 | private List _selectedBitmaps = new List();
10 | public List SelectedBitmaps
11 | {
12 | get { return _selectedBitmaps; }
13 | }
14 | public PreviewForm(List bitmaps)
15 | {
16 | InitializeComponent();
17 |
18 | lvPreviews.View = View.LargeIcon;
19 | lvPreviews.LargeImageList = ilPreviews;
20 | lvPreviews.LargeImageList.ImageSize = new Size(140, 180);
21 |
22 | foreach (Bitmap bitmap in bitmaps)
23 | {
24 | ListViewItem item = new ListViewItem();
25 | item.ImageIndex = lvPreviews.LargeImageList.Images.Count;
26 | lvPreviews.LargeImageList.Images.Add(bitmap);
27 | lvPreviews.Items.Add(item);
28 | }
29 |
30 |
31 | }
32 |
33 | private void btnOk_Click(object sender, System.EventArgs e)
34 | {
35 | _selectedBitmaps.Clear();
36 | foreach (ListViewItem item in lvPreviews.SelectedItems)
37 | {
38 | _selectedBitmaps.Add(item.ImageIndex);
39 | }
40 |
41 | this.DialogResult = DialogResult.OK;
42 | this.Close();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/OnenoteAddin/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("OnenoteAddin")]
8 | [assembly: AssemblyDescription("Description")]
9 | [assembly: AssemblyConfiguration("Configuration")]
10 | [assembly: AssemblyCompany("Company")]
11 | [assembly: AssemblyProduct("OnenoteAddin")]
12 | [assembly: AssemblyCopyright("Copyright © 2023")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("6fa7bd3b-28c8-4b93-b0ac-99f022986488")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("4.0.0.0")]
35 | [assembly: AssemblyFileVersion("4.0.0.0")]
36 |
--------------------------------------------------------------------------------
/ConsoleTest/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ConsoleTest")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ConsoleTest")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("c23909a9-5d87-4e94-b69d-b87150054b3f")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/RemarkableSync/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("RemarkableSync")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("RemarkableSync")]
13 | [assembly: AssemblyCopyright("Copyright © 2023")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("090b923f-25a0-4204-846c-71c0c7f50743")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("4.0.0.0")]
36 | [assembly: AssemblyFileVersion("4.0.0.0")]
37 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/SceneItems/Group.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static RemarkableSync.document.v6.RmPageBinaryV6;
7 |
8 | namespace RemarkableSync.document.v6.SceneItems
9 | {
10 | /****
11 | *
12 | * A Group represents a group of nested items.
13 | *
14 | * Groups are used to represent layers.
15 | *
16 | * node_id is the id that this sub-tree is stored as a "SceneTreeBlock".
17 | *
18 | * children is a sequence of other SceneItems.
19 | *
20 | * `anchor_id` refers to a text character which provides the anchor y-position
21 | * for this group. There are two values that seem to be special:
22 | * - `0xfffffffffffe` seems to be used for lines right at the top of the page?
23 | * - `0xffffffffffff` seems to be used for lines right at the bottom of the page?
24 | *
25 | */
26 | internal class Group : SceneItem
27 | {
28 | public CrdtId node_id;
29 | public CrdtSequence children;
30 | public LwwValue label = new LwwValue(new CrdtId(0, 0), "");
31 | public LwwValue visible = new LwwValue(new CrdtId(0, 0), true);
32 |
33 | public LwwValue anchor_id;
34 | public LwwValue anchor_type;
35 | public LwwValue anchor_threshold;
36 | public LwwValue anchor_origin_x;
37 |
38 | public Group() {
39 |
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/OnenoteAddin/ComLocalServer/GarbageCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace RemarkableSync.OnenoteAddin
5 | {
6 | ///
7 | /// Summary description for GarbageCollection.
8 | ///
9 | class GarbageCollection
10 | {
11 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
12 | protected bool m_bContinueThread;
13 | protected bool m_GCWatchStopped;
14 | protected int m_iInterval;
15 | protected ManualResetEvent m_EventThreadEnded;
16 |
17 | public GarbageCollection(int iInterval)
18 | {
19 | m_bContinueThread = true;
20 | m_GCWatchStopped = false;
21 | m_iInterval = iInterval;
22 | m_EventThreadEnded = new ManualResetEvent(false);
23 | }
24 |
25 | public void GCWatch()
26 | {
27 | Logger.Debug("GarbageCollection.GCWatch() is now running on another thread.");
28 | // Pause for a moment to provide a delay to make threads more apparent.
29 | while (ContinueThread())
30 | {
31 | GC.Collect();
32 | Thread.Sleep(m_iInterval);
33 | }
34 |
35 | Logger.Debug("Goind to call m_EventThreadEnded.Set().");
36 | m_EventThreadEnded.Set();
37 | }
38 |
39 | protected bool ContinueThread()
40 | {
41 | lock(this)
42 | {
43 | return m_bContinueThread;
44 | }
45 | }
46 |
47 | public void StopThread()
48 | {
49 | lock(this)
50 | {
51 | Logger.Debug("Stopping thread.");
52 | m_bContinueThread = false;
53 | }
54 | }
55 |
56 | public void WaitForThreadToStop()
57 | {
58 | Logger.Debug("Called");
59 | m_EventThreadEnded.WaitOne();
60 | m_EventThreadEnded.Reset();
61 | Logger.Debug("Exiting.");
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/RemarkableSync/document/RmItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace RemarkableSync.document
5 | {
6 | public class RmItem
7 | {
8 | public static string CollectionType = "CollectionType";
9 | public static string DocumentType = "DocumentType";
10 |
11 | public RmItem()
12 | {
13 | Children = new List();
14 | }
15 |
16 | public string ID { get; set; }
17 | public int Version { get; set; }
18 | public string Message { get; set; }
19 | public bool Success { get; set; }
20 | public string BlobURLGet { get; set; }
21 | public DateTime BlobURLGetExpires { get; set; }
22 | public DateTime ModifiedClient { get; set; }
23 | public string Type { get; set; }
24 | public string VissibleName { get; set; }
25 | public int CurrentPage { get; set; }
26 | public bool Bookmarked { get; set; }
27 | public string Parent { get; set; }
28 |
29 | public List Children { get; set; }
30 |
31 | public static List SortItems(List collection)
32 | {
33 | foreach (RmItem item in collection)
34 | {
35 | if (item.Children.Count > 0)
36 | {
37 | item.Children = SortItems(item.Children);
38 | }
39 | }
40 |
41 | collection.Sort(
42 | delegate (RmItem p1, RmItem p2)
43 | {
44 | int compareType = p1.Type.CompareTo(p2.Type);
45 | if (compareType == 0)
46 | {
47 | return p1.VissibleName.CompareTo(p2.VissibleName);
48 | }
49 | return compareType;
50 | }
51 | );
52 | return collection;
53 | }
54 | }
55 | }
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/RemarkableSync/document/content/DocumentContentV2.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace RemarkableSync.document
4 | {
5 | public class DocumentContentV2 : DocumentContent
6 | {
7 | public int customZoomCenterX { get; set; }
8 | public int customZoomCenterY { get; set; }
9 | public string customZoomOrientation { get; set; }
10 | public int customZoomPageHeight { get; set; }
11 | public int customZoomPageWidth { get; set; }
12 | public int customZoomScale { get; set; }
13 | public cPages cPages { get; set; }
14 | public string zoomMode { get; set; }
15 |
16 |
17 | public override List getPages()
18 | {
19 | List pages = new List();
20 | foreach (Page item in cPages.pages)
21 | {
22 | pages.Add(item.id);
23 | }
24 | return pages;
25 | }
26 | }
27 |
28 | public class cPages
29 | {
30 | public TimestampedStringValue lastOpened { get; set; }
31 | public TimestampedIntValue original { get; set; }
32 | public Page[] pages { get; set; }
33 | public Uuid[] uuids { get; set; }
34 |
35 | }
36 | public class Uuid
37 | {
38 | public string first { get; set; }
39 | public int second { get; set; }
40 | }
41 | public class Page
42 | {
43 | public string id { get; set; }
44 | public TimestampedStringValue idx { get; set; }
45 | public TimestampedStringValue template { get; set; }
46 | public TimestampedStringValue scrollTime { get; set; }
47 | public TimestampedIntValue verticalScroll { get; set; }
48 | }
49 |
50 | public class TimestampedStringValue
51 | {
52 | public string timestamp { get; set; }
53 | public string value { get; set; }
54 | }
55 |
56 | public class TimestampedIntValue
57 | {
58 | public string timestamp { get; set; }
59 | public int value { get; set; }
60 | }
61 | }
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/RemarkableSync/RmSftpDownloadedDoc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Renci.SshNet;
8 |
9 | namespace RemarkableSync
10 | {
11 | public class RmSftpDownloadedDoc: RmDocument
12 | {
13 | public RmSftpDownloadedDoc(string basePath, string id, SftpClient client, String docContentJsonString) : base (id)
14 | {
15 | try
16 | {
17 | LoadDocumentContent(docContentJsonString);
18 |
19 | //create the temp content folder
20 | Directory.CreateDirectory(GetDocumentFolderPath());
21 |
22 | string sourceContentFolder = $"{basePath}/{id}/";
23 |
24 | for (int i = 0; i < _content.pageCount; i++)
25 | {
26 | string pageUuid = GetPageUUID(i);
27 | if(client.Exists(sourceContentFolder + pageUuid + ".rm"))
28 | {
29 | var file = File.OpenWrite(GetPageBinaryFilePath(i));
30 | client.DownloadFile(sourceContentFolder + pageUuid + ".rm", file);
31 | file.Flush();
32 | file.Close();
33 | }
34 | else
35 | {
36 | Logger.Debug($"Page binary {sourceContentFolder}{pageUuid}.rm not found, probably a blank page");
37 | }
38 | if (client.Exists(sourceContentFolder + pageUuid + "-metadata.json"))
39 | {
40 | var metadataFile = File.OpenWrite(GetPageMetadataFilePath(i));
41 | client.DownloadFile(sourceContentFolder + pageUuid + "-metadata.json", metadataFile);
42 | metadataFile.Flush();
43 | metadataFile.Close();
44 | }
45 | else
46 | {
47 | Logger.Debug($"Page metadata {sourceContentFolder}{pageUuid}-metadata.json not found");
48 | }
49 | }
50 | }
51 | catch (Exception err)
52 | {
53 | Logger.Error($"failed to download content to temp folder. Error: {err.Message}");
54 | throw err;
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v5/RmLinesDrawer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 | using System.Drawing.Drawing2D;
4 | using System.Linq;
5 |
6 | namespace RemarkableSync.document.v5
7 | {
8 | public class RmLinesDrawer
9 | {
10 | static public List DrawPages(List pages)
11 | {
12 | List images = (from page in pages
13 | select DrawPage(page)).ToList();
14 | return images;
15 | }
16 |
17 | static public Bitmap DrawPage(RmPage page)
18 | {
19 | Bitmap image = new Bitmap(RmConstants.X_MAX, RmConstants.Y_MAX);
20 |
21 | Graphics graphics = Graphics.FromImage(image);
22 | graphics.SmoothingMode = SmoothingMode.AntiAlias;
23 | graphics.Clear(Color.White);
24 |
25 | foreach (RmLayer layer in page.Objects)
26 | {
27 | DrawLayer(layer, ref graphics);
28 | }
29 |
30 | return image;
31 | }
32 |
33 | static private void DrawLayer(RmLayer layer, ref Graphics graphics)
34 | {
35 | foreach (RmStroke stroke in layer.Objects)
36 | {
37 | if (stroke.IsVisible())
38 | {
39 | DrawStroke(stroke, ref graphics);
40 | }
41 | }
42 | }
43 |
44 | static private void DrawStroke(RmStroke stroke, ref Graphics graphics)
45 | {
46 | Color color;
47 |
48 | switch (stroke.Colour)
49 | {
50 | case RmPenColor.GREY:
51 | color = Color.Gray;
52 | break;
53 | case RmPenColor.WHITE:
54 | color = Color.White;
55 | break;
56 | case RmPenColor.BLACK:
57 | default:
58 | color = Color.Black;
59 | break;
60 | }
61 |
62 | Pen pen = new Pen(color, stroke.Width);
63 |
64 | GraphicsPath path = new GraphicsPath();
65 | Point[] points = new Point[stroke.Objects.Count];
66 | for (int i = 0; i < stroke.Objects.Count; ++i)
67 | {
68 | RmSegment segment = (RmSegment)stroke.Objects[i];
69 | points[i] = new Point((int)segment.X, (int)segment.Y);
70 | }
71 | path.AddLines(points);
72 | graphics.DrawPath(pen, path);
73 |
74 | pen.Dispose();
75 | }
76 |
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/RemarkableSync/document/content/DocumentContent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.Json;
4 | using System.Text.Json.Nodes;
5 |
6 | namespace RemarkableSync.document
7 | {
8 | public enum RmContentFormat
9 | {
10 | v1 = 1,
11 | v2 = 2
12 | }
13 | public class DocumentContent : IDocumentContent
14 | {
15 | public int coverPageNumber { get; set; }
16 | public DocumentMetadata documentMetadata { get; set; }
17 | public bool dummyDocument { get; set; }
18 | public Extrametadata extraMetadata { get; set; }
19 | public string fileType { get; set; }
20 | public string fontName { get; set; }
21 | public RmContentFormat formatVersion { get; set; }
22 | public int lineHeight { get; set; }
23 | public int margins { get; set; }
24 | public string orientation { get; set; }
25 | public int originalPageCount { get; set; }
26 | public int pageCount { get; set; }
27 | public object[] pageTags { get; set; }
28 | public string sizeInBytes { get; set; }
29 | public object[] tags { get; set; }
30 | public string textAlignment { get; set; }
31 | public float textScale { get; set; }
32 |
33 | public virtual List getPages()
34 | {
35 | return new List();
36 | }
37 |
38 | public static DocumentContent GetDocumentContentFromJson(string contentJsonString)
39 | {
40 | try
41 | {
42 | JsonNode versionCheck = JsonNode.Parse(contentJsonString);
43 | RmContentFormat docVersion = RmContentFormat.v1; //Default to v1
44 | if (versionCheck != null && versionCheck["formatVersion"] != null)
45 | {
46 | docVersion = (RmContentFormat)versionCheck["formatVersion"].GetValue();
47 | }
48 |
49 | switch (docVersion)
50 | {
51 | case RmContentFormat.v1:
52 | return JsonSerializer.Deserialize(contentJsonString);
53 | case RmContentFormat.v2:
54 | default:
55 | return JsonSerializer.Deserialize(contentJsonString);
56 | }
57 |
58 | }
59 |
60 | catch (Exception)
61 | {
62 | throw new Exception("Unsupported content version");
63 | }
64 | }
65 | }
66 | }
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/RemarkableSync/LocalFolderDataSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using RemarkableSync.document;
9 |
10 |
11 | namespace RemarkableSync
12 | {
13 | public partial class LocalFolderDataSource : IRmDataSource
14 | {
15 | private string ContentFolderPath;
16 |
17 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
18 |
19 | public LocalFolderDataSource(string root_path)
20 | {
21 | ContentFolderPath = root_path;
22 | }
23 |
24 | public async Task> GetItemHierarchy(CancellationToken cancellationToken, IProgress progress)
25 | {
26 | List collection = GetAllItems();
27 | return getChildItemsRecursive("", ref collection);
28 | }
29 |
30 | public async Task DownloadDocument(string ID, CancellationToken cancellationToken, IProgress progress)
31 | {
32 | return await Task.Run(() =>
33 | {
34 | // get the .content file for the notebook first
35 | string contentFileFullPath = $"{ContentFolderPath}/{ID}.content";
36 | return new RmLocalDoc(ID, ContentFolderPath);
37 | });
38 | }
39 |
40 | public void Dispose()
41 | {
42 |
43 | }
44 |
45 | private List GetAllItems()
46 | {
47 | List items = new List();
48 |
49 | var files = Directory.GetFiles(ContentFolderPath, "*.metadata");
50 | foreach (var filename in files)
51 | {
52 | using(FileStream file = File.OpenRead(filename))
53 | {
54 | RmSftpDataSource.NotebookMetadata notebookMetadata = RmSftpDataSource.NotebookMetadata.FromStream(file);
55 | if (notebookMetadata.deleted)
56 | continue;
57 | items.Add(notebookMetadata.ToRmItem(Path.GetFileNameWithoutExtension(file.Name)));
58 | }
59 | }
60 |
61 | return items;
62 | }
63 |
64 | private List getChildItemsRecursive(string parentId, ref List items)
65 | {
66 | var children = (from item in items where item.Parent == parentId select item).ToList();
67 | foreach (var child in children)
68 | {
69 | child.Children = getChildItemsRecursive(child.ID, ref items);
70 | }
71 | return children;
72 | }
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/RemarkableSync/document/Extrametadata.cs:
--------------------------------------------------------------------------------
1 | namespace RemarkableSync.document
2 | {
3 | public class Extrametadata
4 | {
5 | public string LastBallpointColor { get; set; }
6 | public string LastBallpointSize { get; set; }
7 | public string LastBallpointv2Color { get; set; }
8 | public string LastBallpointv2Size { get; set; }
9 | public string LastCalligraphyColor { get; set; }
10 | public string LastCalligraphySize { get; set; }
11 | public string LastClearPageColor { get; set; }
12 | public string LastClearPageSize { get; set; }
13 | public string LastEraseSectionColor { get; set; }
14 | public string LastEraseSectionSize { get; set; }
15 | public string LastEraserColor { get; set; }
16 | public string LastEraserSize { get; set; }
17 | public string LastEraserTool { get; set; }
18 | public string LastFinelinerColor { get; set; }
19 | public string LastFinelinerSize { get; set; }
20 | public string LastFinelinerv2Color { get; set; }
21 | public string LastFinelinerv2Size { get; set; }
22 | public string LastHighlighterColor { get; set; }
23 | public string LastHighlighterSize { get; set; }
24 | public string LastHighlighterv2Color { get; set; }
25 | public string LastHighlighterv2Size { get; set; }
26 | public string LastMarkerColor { get; set; }
27 | public string LastMarkerSize { get; set; }
28 | public string LastMarkerv2Color { get; set; }
29 | public string LastMarkerv2Size { get; set; }
30 | public string LastPaintbrushColor { get; set; }
31 | public string LastPaintbrushSize { get; set; }
32 | public string LastPaintbrushv2Color { get; set; }
33 | public string LastPaintbrushv2Size { get; set; }
34 | public string LastPen { get; set; }
35 | public string LastPencilColor { get; set; }
36 | public string LastPencilSize { get; set; }
37 | public string LastPencilv2Color { get; set; }
38 | public string LastPencilv2Size { get; set; }
39 | public string LastReservedPenColor { get; set; }
40 | public string LastReservedPenSize { get; set; }
41 | public string LastSelectionToolColor { get; set; }
42 | public string LastSelectionToolSize { get; set; }
43 | public string LastSharpPencilColor { get; set; }
44 | public string LastSharpPencilSize { get; set; }
45 | public string LastSharpPencilv2Color { get; set; }
46 | public string LastSharpPencilv2Size { get; set; }
47 | public string LastSolidPenColor { get; set; }
48 | public string LastSolidPenSize { get; set; }
49 | public string LastTool { get; set; }
50 | public string LastZoomToolColor { get; set; }
51 | public string LastZoomToolSize { get; set; }
52 | }
53 | }
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/RemarkableSync/document/PageBinary.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document.v5;
2 | using RemarkableSync.document.v6;
3 | using RemarkableSync.MyScript;
4 | using System;
5 | using System.Drawing;
6 | using System.IO;
7 |
8 | namespace RemarkableSync.document
9 | {
10 | enum RmFileVersion
11 | {
12 | V5 = 5,
13 | V6 = 6
14 | }
15 | public class PageBinary
16 | {
17 | private Stream _document;
18 | private TaggedBinaryReader _reader;
19 | private RmFileVersion _doc_version;
20 | private IRmPageBinary _rmPageBinary;
21 |
22 |
23 | public PageBinary(String path)
24 | {
25 | if (File.Exists(path))
26 | {
27 | using (Stream document = File.Open(path, FileMode.Open))
28 | {
29 | _document = document;
30 | _reader = new TaggedBinaryReader(_document);
31 | ReadBinaryDocument();
32 | }
33 | }
34 | else
35 | {
36 | //default create empty page
37 | _rmPageBinary = new RmPageBinaryV6();
38 | }
39 |
40 | }
41 |
42 | public PageBinary(Stream document)
43 | {
44 | _document = document;
45 | _reader = new TaggedBinaryReader(_document);
46 | ReadBinaryDocument();
47 | }
48 |
49 | public Bitmap GetBitmap()
50 | {
51 | return _rmPageBinary.GetBitmap();
52 | }
53 |
54 | public Tuple GetMyScriptFormat()
55 | {
56 | return _rmPageBinary.GetMyScriptFormat();
57 | }
58 | private void ReadBinaryDocument()
59 | {
60 | ReadHeader();
61 |
62 | switch (_doc_version)
63 | {
64 | case RmFileVersion.V5:
65 | _rmPageBinary = RmPage.ParseStream(_reader);
66 | break;
67 | case RmFileVersion.V6:
68 | _rmPageBinary = new RmPageBinaryV6(ref _reader);
69 | break;
70 | }
71 | //Logger.Debug("Not processing file version " + _doc_version);
72 | }
73 |
74 |
75 | /**
76 | * Read header 43 bytes
77 | *
78 | * V6 "reMarkable .lines file, version=6 "
79 | * V5 "reMarkable .lines file, version=5 "
80 | */
81 | private void ReadHeader()
82 | {
83 | String header = new String(_reader.ReadChars(43));
84 |
85 | if (header.Contains("version=6"))
86 | {
87 | _doc_version = RmFileVersion.V6;
88 | } else if(header.Contains("version=5"))
89 | {
90 | _doc_version = RmFileVersion.V5;
91 | } else
92 | {
93 | throw new Exception("Unsupported file version: " + header);
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/RemarkableSync/MyScript/MyScriptResult.cs:
--------------------------------------------------------------------------------
1 | namespace RemarkableSync.MyScript
2 | {
3 | public class MyScriptResult
4 | {
5 | public string type { get; set; }
6 | public BoundingBox boundingbox { get; set; }
7 | public string label { get; set; }
8 | public Word[] words { get; set; }
9 | public string version { get; set; }
10 | public string id { get; set; }
11 | }
12 |
13 | public class BoundingBox
14 | {
15 | public BoundingBox()
16 | {
17 | x = y = float.NaN;
18 | width = height = (float) 0.0;
19 | }
20 |
21 | public float x { get; set; }
22 | public float y { get; set; }
23 | public float width { get; set; }
24 | public float height { get; set; }
25 |
26 | public void Expand(float X, float Y)
27 | {
28 | if (x == float.NaN)
29 | {
30 | x = X;
31 | }
32 | else if (X < x)
33 | {
34 | width += (x - X);
35 | x = X;
36 | }
37 | else if (X > x + width)
38 | {
39 | width = X - x;
40 | }
41 |
42 | if (y == float.NaN)
43 | {
44 | y = Y;
45 | }
46 | else if (Y < y)
47 | {
48 | height += (y - Y);
49 | y = Y;
50 | }
51 | else if (Y > y + height)
52 | {
53 | height = Y - y;
54 | }
55 | }
56 |
57 | public bool Contains(float X, float Y)
58 | {
59 | if ((x - 1 > X) || (x + width + 1 < X))
60 | {
61 | return false;
62 | }
63 | if ((y - 1 > Y) || (y + height + 1 < Y))
64 | {
65 | return false;
66 | }
67 |
68 | return true;
69 | }
70 |
71 | public bool Contains(BoundingBox bound)
72 | {
73 | if (x - 1 > bound.x)
74 | {
75 | return false;
76 | }
77 | if ((x + width + 1) < (bound.x + bound.width))
78 | {
79 | return false;
80 | }
81 | if (y - 1 > bound.y)
82 | {
83 | return false;
84 | }
85 | if ((y + height + 1) < (bound.y + bound.height))
86 | {
87 | return false;
88 | }
89 | return true;
90 | }
91 | }
92 |
93 | public class Word
94 | {
95 | public string label { get; set; }
96 | public string[] candidates { get; set; }
97 | public BoundingBox boundingbox { get; set; }
98 | public Item[] items { get; set; }
99 | }
100 |
101 | public class Item
102 | {
103 | public string timestamp { get; set; }
104 | public float[] X { get; set; }
105 | public float[] Y { get; set; }
106 | public int[] F { get; set; }
107 | public int[] T { get; set; }
108 | public string type { get; set; }
109 | public string id { get; set; }
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/OnenoteAddin/PreviewForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace RemarkableSync.OnenoteAddin
2 | {
3 | partial class PreviewForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.components = new System.ComponentModel.Container();
32 | this.ilPreviews = new System.Windows.Forms.ImageList(this.components);
33 | this.lvPreviews = new System.Windows.Forms.ListView();
34 | this.btnOk = new System.Windows.Forms.Button();
35 | this.SuspendLayout();
36 | //
37 | // ilPreviews
38 | //
39 | this.ilPreviews.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit;
40 | this.ilPreviews.ImageSize = new System.Drawing.Size(50, 50);
41 | this.ilPreviews.TransparentColor = System.Drawing.Color.Transparent;
42 | //
43 | // lvPreviews
44 | //
45 | this.lvPreviews.Dock = System.Windows.Forms.DockStyle.Fill;
46 | this.lvPreviews.HideSelection = false;
47 | this.lvPreviews.Location = new System.Drawing.Point(0, 0);
48 | this.lvPreviews.Name = "lvPreviews";
49 | this.lvPreviews.Size = new System.Drawing.Size(998, 726);
50 | this.lvPreviews.TabIndex = 0;
51 | this.lvPreviews.UseCompatibleStateImageBehavior = false;
52 | //
53 | // btnOk
54 | //
55 | this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
56 | this.btnOk.Location = new System.Drawing.Point(911, 691);
57 | this.btnOk.Name = "btnOk";
58 | this.btnOk.Size = new System.Drawing.Size(75, 23);
59 | this.btnOk.TabIndex = 1;
60 | this.btnOk.Text = "Ok";
61 | this.btnOk.UseVisualStyleBackColor = true;
62 | this.btnOk.Click += new System.EventHandler(this.btnOk_Click);
63 | //
64 | // PreviewForm
65 | //
66 | this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
67 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
68 | this.ClientSize = new System.Drawing.Size(998, 726);
69 | this.Controls.Add(this.btnOk);
70 | this.Controls.Add(this.lvPreviews);
71 | this.Name = "PreviewForm";
72 | this.Text = "PreviewForm";
73 | this.ResumeLayout(false);
74 |
75 | }
76 |
77 | #endregion
78 |
79 | private System.Windows.Forms.ImageList ilPreviews;
80 | private System.Windows.Forms.ListView lvPreviews;
81 | private System.Windows.Forms.Button btnOk;
82 | }
83 | }
--------------------------------------------------------------------------------
/OnenoteAddin/CCOMStreamWrapper.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
3 | */
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 | using System.Runtime.InteropServices.ComTypes;
8 |
9 | namespace RemarkableSync.OnenoteAddin
10 | {
11 | class CCOMStreamWrapper : IStream
12 | {
13 | public CCOMStreamWrapper(System.IO.Stream streamWrap)
14 | {
15 | m_stream = streamWrap;
16 | }
17 |
18 | public void Clone(out IStream ppstm)
19 | {
20 | ppstm = new CCOMStreamWrapper(m_stream);
21 | }
22 |
23 | public void Commit(int grfCommitFlags)
24 | {
25 | m_stream.Flush();
26 | }
27 |
28 | public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
29 | {
30 | }
31 |
32 | public void LockRegion(long libOffset, long cb, int dwLockType)
33 | {
34 | throw new System.NotImplementedException();
35 | }
36 |
37 | public void Read(byte[] pv, int cb, IntPtr pcbRead)
38 | {
39 | Marshal.WriteInt64(pcbRead, m_stream.Read(pv, 0, cb));
40 | }
41 |
42 | public void Revert()
43 | {
44 | throw new System.NotImplementedException();
45 | }
46 |
47 | public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
48 | {
49 | long posMoveTo = 0;
50 | Marshal.WriteInt64(plibNewPosition, m_stream.Position);
51 | switch (dwOrigin)
52 | {
53 | case 0:
54 | {
55 | /* STREAM_SEEK_SET */
56 | posMoveTo = dlibMove;
57 | }
58 | break;
59 | case 1:
60 | {
61 | /* STREAM_SEEK_CUR */
62 | posMoveTo = m_stream.Position + dlibMove;
63 |
64 | }
65 | break;
66 | case 2:
67 | {
68 | /* STREAM_SEEK_END */
69 | posMoveTo = m_stream.Length + dlibMove;
70 | }
71 | break;
72 | default:
73 | return;
74 | }
75 | if (posMoveTo >= 0 && posMoveTo < m_stream.Length)
76 | {
77 | m_stream.Position = posMoveTo;
78 | Marshal.WriteInt64(plibNewPosition, m_stream.Position);
79 | }
80 | }
81 |
82 | public void SetSize(long libNewSize)
83 | {
84 | m_stream.SetLength(libNewSize);
85 | }
86 |
87 | public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
88 | {
89 | pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
90 | pstatstg.cbSize = m_stream.Length;
91 | if ((grfStatFlag & 0x0001/* STATFLAG_NONAME */) != 0)
92 | return;
93 | pstatstg.pwcsName = m_stream.ToString();
94 | }
95 |
96 | public void UnlockRegion(long libOffset, long cb, int dwLockType)
97 | {
98 | throw new System.NotImplementedException();
99 | }
100 |
101 | public void Write(byte[] pv, int cb, IntPtr pcbWritten)
102 | {
103 | Marshal.WriteInt64(pcbWritten, 0);
104 | m_stream.Write(pv, 0, cb);
105 | Marshal.WriteInt64(pcbWritten, cb);
106 | }
107 |
108 | private System.IO.Stream m_stream;
109 | }
110 | }
--------------------------------------------------------------------------------
/OnenoteAddin/ComLocalServer/ClassFactoryBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace RemarkableSync.OnenoteAddin
5 | {
6 | class ClassFactoryBase : IClassFactory
7 | {
8 | // CoRegisterClassObject() is used to register a Class Factory
9 | // into COM's internal table of Class Factories.
10 | [DllImport("ole32.dll")]
11 | static extern int CoRegisterClassObject([In] ref Guid rclsid,
12 | [MarshalAs(UnmanagedType.IUnknown)] object pUnk, uint dwClsContext,
13 | uint flags, out uint lpdwRegister);
14 |
15 | // Called by an COM EXE Server that can register multiple class objects
16 | // to inform COM about all registered classes, and permits activation
17 | // requests for those class objects.
18 | // This function causes OLE to inform the SCM about all the registered
19 | // classes, and begins letting activation requests into the server process.
20 | [DllImport("ole32.dll")]
21 | static extern int CoResumeClassObjects();
22 |
23 | // CoRevokeClassObject() is used to unregister a Class Factory
24 | // from COM's internal table of Class Factories.
25 | [DllImport("ole32.dll")]
26 | static extern int CoRevokeClassObject(uint dwRegister);
27 |
28 | public ClassFactoryBase()
29 | {
30 | }
31 |
32 | protected UInt32 m_locked = 0;
33 | protected uint m_ClassContext = (uint)CLSCTX.CLSCTX_LOCAL_SERVER;
34 | protected Guid m_ClassId;
35 | protected uint m_Flags;
36 | protected uint m_Cookie;
37 |
38 | public virtual void virtual_CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
39 | {
40 | IntPtr nullPtr = new IntPtr(0);
41 | ppvObject = nullPtr;
42 | }
43 |
44 | public uint ClassContext
45 | {
46 | get
47 | {
48 | return m_ClassContext;
49 | }
50 | set
51 | {
52 | m_ClassContext = value;
53 | }
54 | }
55 |
56 | public Guid ClassId
57 | {
58 | get
59 | {
60 | return m_ClassId;
61 | }
62 | set
63 | {
64 | m_ClassId = value;
65 | }
66 | }
67 |
68 | public uint Flags
69 | {
70 | get
71 | {
72 | return m_Flags;
73 | }
74 | set
75 | {
76 | m_Flags = value;
77 | }
78 | }
79 |
80 | public bool RegisterClassObject()
81 | {
82 | // Register the class factory
83 | int i = CoRegisterClassObject
84 | (
85 | ref m_ClassId,
86 | this,
87 | ClassContext,
88 | Flags,
89 | out m_Cookie
90 | );
91 |
92 | if (i == 0)
93 | {
94 | return true;
95 | }
96 | else
97 | {
98 | return false;
99 | }
100 | }
101 |
102 | public bool RevokeClassObject()
103 | {
104 | int i = CoRevokeClassObject(m_Cookie);
105 |
106 | if (i == 0)
107 | {
108 | return true;
109 | }
110 | else
111 | {
112 | return false;
113 | }
114 | }
115 |
116 | public static bool ResumeClassObjects()
117 | {
118 | int i = CoResumeClassObjects();
119 |
120 | if (i == 0)
121 | {
122 | return true;
123 | }
124 | else
125 | {
126 | return false;
127 | }
128 | }
129 |
130 | #region IClassFactory Implementations
131 | public void CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
132 | {
133 | virtual_CreateInstance(pUnkOuter, ref riid, out ppvObject);
134 | }
135 |
136 | public void LockServer(bool bLock)
137 | {
138 | if (bLock)
139 | {
140 | ManagedCOMLocalServer.InterlockedIncrementServerLockCount();
141 | }
142 | else
143 | {
144 | ManagedCOMLocalServer.InterlockedDecrementServerLockCount();
145 | }
146 |
147 | // Always attempt to see if we need to shutdown this server application.
148 | ManagedCOMLocalServer.AttemptToTerminateServer();
149 | }
150 | #endregion
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/RemarkableSync/CloudApiV1Client.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.IO.Compression;
6 | using System.Net;
7 | using System.Net.Http;
8 | using System.Text.Json;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace RemarkableSync
13 | {
14 | class CloudApiV1Client : ICloudApiClient
15 | {
16 | private static string CustomBaseUrlName = "CustomBaseUrl";
17 | private static string DefaultBaseUrl = "https://document-storage-production-dot-remarkable-production.appspot.com";
18 |
19 | private HttpClient _client;
20 | private string _baseUrl;
21 |
22 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
23 |
24 | public CloudApiV1Client(HttpClient client, IConfigStore hiddenConfigStore = null)
25 | {
26 | _client = client;
27 | _baseUrl = hiddenConfigStore?.GetConfig(CustomBaseUrlName) ?? DefaultBaseUrl;
28 | }
29 |
30 | public void Dispose()
31 | {
32 | }
33 |
34 | public async Task DownloadDocument(string ID, CancellationToken cancellationToken, IProgress progress)
35 | {
36 | try
37 | {
38 | // first get the blob url
39 | string url = $"/document-storage/json/2/docs?doc={WebUtility.UrlEncode(ID)}&withBlob=true";
40 | HttpResponseMessage response = await Request(HttpMethod.Get, url, null, null);
41 | if (!response.IsSuccessStatusCode)
42 | {
43 | Logger.Error("request failed with status code " + response.StatusCode.ToString());
44 | return null;
45 | }
46 | List items = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result);
47 | if (items.Count == 0)
48 | {
49 | Logger.Error("Failed to find document with id: " + ID);
50 | return null;
51 | }
52 | string blobUrl = items[0].BlobURLGet;
53 | Stream stream = await _client.GetStreamAsync(blobUrl);
54 | ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read);
55 |
56 | return new RmCloudV1DownloadedDoc(archive, ID);
57 | }
58 | catch (Exception err)
59 | {
60 | Logger.Error($"failed for id {ID}. Error: {err.Message}");
61 | return null;
62 | }
63 | }
64 |
65 | public async Task> GetAllItems(CancellationToken cancellationToken, IProgress progress)
66 | {
67 | HttpResponseMessage response = await Request(
68 | HttpMethod.Get,
69 | "/document-storage/json/2/docs",
70 | null,
71 | null);
72 |
73 | string responseContent = await response.Content.ReadAsStringAsync();
74 |
75 | if (!response.IsSuccessStatusCode)
76 | {
77 | string errMsg = "GetAllItems request failed with status code " + response.StatusCode.ToString();
78 | Logger.Error($"Request failed with status code: {response.StatusCode.ToString()} and content: {responseContent}");
79 | throw new Exception(errMsg);
80 | }
81 |
82 | List collection = JsonSerializer.Deserialize>(responseContent);
83 | return collection;
84 | }
85 |
86 | private async Task Request(HttpMethod method, string url, Dictionary header, HttpContent content)
87 | {
88 | if (!url.StartsWith("http"))
89 | {
90 | if (!url.StartsWith("/"))
91 | url = "/" + url;
92 | url = _baseUrl + url;
93 | }
94 |
95 | Logger.Debug($"url is: {url}");
96 | var request = new HttpRequestMessage();
97 | request.RequestUri = new Uri(url);
98 | request.Method = method;
99 | if (content != null)
100 | {
101 | request.Content = content;
102 | }
103 |
104 | // add/replace the supplied headers
105 | if (header != null)
106 | {
107 | foreach (var key in header.Keys)
108 | {
109 | request.Headers.Add(key, header[key]);
110 | }
111 | }
112 |
113 | HttpResponseMessage response = await _client.SendAsync(request);
114 | return response;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/RemarkableSync/RmSftpDataSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using RemarkableSync.document;
10 | using Renci.SshNet;
11 | using Renci.SshNet.Common;
12 |
13 |
14 | namespace RemarkableSync
15 | {
16 | public partial class RmSftpDataSource : IRmDataSource
17 | {
18 | public static readonly string SshHostConfig = "SshHost";
19 |
20 | public static readonly string SshPasswordConfig = "SshPassword";
21 |
22 | private static readonly string ContentFolderPath = "/home/root/.local/share/remarkable/xochitl";
23 |
24 | private SftpClient _client;
25 |
26 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
27 |
28 | public RmSftpDataSource(IConfigStore configStore)
29 | {
30 | string host = configStore.GetConfig(SshHostConfig);
31 | string password = configStore.GetConfig(SshPasswordConfig);
32 |
33 | if (password == null)
34 | {
35 | string errMsg = "Unable to get SSH password from config store";
36 | Logger.Error($"{errMsg}");
37 | throw new Exception(errMsg);
38 | }
39 |
40 | ConnectToRm(host, password);
41 | }
42 |
43 | public RmSftpDataSource(string host, string password)
44 | {
45 | ConnectToRm(host, password);
46 | }
47 |
48 | private void ConnectToRm(string host, string password)
49 | {
50 | const string username = "root";
51 | const int port = 22;
52 |
53 | try
54 | {
55 | _client = new SftpClient(host, port, username, password);
56 | _client.Connect();
57 | }
58 | catch (SshAuthenticationException err)
59 | {
60 | Logger.Error($"authentication error on SFTP connection: err: {err.Message}");
61 | throw new Exception("reMarkable device SSH login failed");
62 | }
63 | catch (Exception err)
64 | {
65 | Logger.Error($"error on SFTP connection: err: {err.Message}");
66 | throw new Exception("Failed to connect to reMarkable device via SSH");
67 | }
68 | }
69 |
70 | public async Task> GetItemHierarchy(CancellationToken cancellationToken, IProgress progress)
71 | {
72 | List collection = await Task.Run(GetAllItems);
73 | return getChildItemsRecursive("", ref collection);
74 | }
75 |
76 | public async Task DownloadDocument(string ID, CancellationToken cancellationToken, IProgress progress)
77 | {
78 | return await Task.Run(() =>
79 | {
80 | // get the .content file for the notebook first
81 | string contentFileFullPath = $"{ContentFolderPath}/{ID}.content";
82 | string content = _client.ReadAllText(contentFileFullPath);
83 | RmSftpDownloadedDoc downloadedDoc = new RmSftpDownloadedDoc(ContentFolderPath, ID, _client, content);
84 |
85 | return downloadedDoc;
86 | });
87 | }
88 |
89 | public void Dispose()
90 | {
91 | _client?.Dispose();
92 | }
93 |
94 | private List GetAllItems()
95 | {
96 | List items = new List();
97 |
98 | foreach (var file in _client.ListDirectory(ContentFolderPath))
99 | {
100 | if (file.IsDirectory)
101 | continue;
102 |
103 | if (!file.Name.EndsWith(".metadata"))
104 | continue;
105 |
106 | MemoryStream stream = new MemoryStream();
107 | _client.DownloadFile(file.FullName, stream);
108 | NotebookMetadata notebookMetadata = NotebookMetadata.FromStream(stream);
109 |
110 | if (notebookMetadata.deleted)
111 | continue;
112 |
113 | items.Add(notebookMetadata.ToRmItem(file.Name));
114 | }
115 |
116 | return items;
117 | }
118 |
119 | private List getChildItemsRecursive(string parentId, ref List items)
120 | {
121 | var children = (from item in items where item.Parent == parentId select item).ToList();
122 | foreach (var child in children)
123 | {
124 | child.Children = getChildItemsRecursive(child.ID, ref items);
125 | }
126 | return children;
127 | }
128 | }
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/RemarkableSync/WinRegistryConfigStore.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Security.Cryptography;
6 | using System.Text;
7 |
8 | namespace RemarkableSync
9 | {
10 | public class WinRegistryConfigStore : IConfigStore
11 | {
12 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
13 | private string _regKeyPath;
14 | private bool _encrypt;
15 |
16 | public WinRegistryConfigStore(string regKeyPath, bool encrypt = true)
17 | {
18 | _regKeyPath = regKeyPath;
19 | _encrypt = encrypt;
20 | }
21 |
22 | public string GetConfig(string configName)
23 | {
24 | string regValue = null; ;
25 | try
26 | {
27 | var settingsKey = Registry.CurrentUser.OpenSubKey(_regKeyPath);
28 | regValue = (string)settingsKey.GetValue(configName, null);
29 | }
30 | catch (Exception err)
31 | {
32 | Logger.Error($"Failed to access registry for config \"{configName}\". Error: {err.Message}");
33 | return null;
34 | }
35 |
36 | if (regValue == null)
37 | {
38 | Logger.Error($"Getting value from registry for config \"{configName}\" failed");
39 | return null;
40 | }
41 |
42 | try
43 | {
44 | if (_encrypt)
45 | {
46 | return Encoding.UTF8.GetString(DecryptData(Convert.FromBase64String(regValue)));
47 | }
48 | else
49 | {
50 | return regValue;
51 | }
52 | }
53 | catch (Exception err)
54 | {
55 | Logger.Error($"Failed to get config \"{configName}\". Error: {err.Message}");
56 | return null;
57 | }
58 | }
59 |
60 | public bool SetConfigs(Dictionary configs)
61 | {
62 | List> processedConfigs = null;
63 | if (_encrypt)
64 | {
65 | try
66 | {
67 | // to byte[], encrypt, then to base 64 string
68 | var rawData = from config in configs
69 | select new KeyValuePair(
70 | config.Key,
71 | Convert.ToBase64String(EncryptData(Encoding.UTF8.GetBytes(config.Value))));
72 | processedConfigs = rawData.ToList();
73 | }
74 | catch (Exception err)
75 | {
76 | Logger.Error($"Failed to encrypt all configs. Error: {err.Message}");
77 | return false;
78 | }
79 | }
80 | else
81 | {
82 | processedConfigs = (from config in configs
83 | select new KeyValuePair(config.Key, config.Value)).ToList();
84 | }
85 |
86 | try
87 | {
88 | Registry.CurrentUser.CreateSubKey(_regKeyPath);
89 | var settingsKey = Registry.CurrentUser.OpenSubKey(_regKeyPath, true);
90 | foreach (var processedConfig in processedConfigs)
91 | {
92 | settingsKey.SetValue(processedConfig.Key, processedConfig.Value);
93 | }
94 | }
95 | catch (Exception err)
96 | {
97 | Logger.Error($"Failed to write to registry. Error: {err.Message}");
98 | return false;
99 | }
100 |
101 | return true;
102 | }
103 |
104 | public void Dispose()
105 | {
106 | return;
107 | }
108 |
109 | private byte[] EncryptData(byte[] rawData)
110 | {
111 | try
112 | {
113 | return ProtectedData.Protect(rawData, null, DataProtectionScope.LocalMachine);
114 | }
115 | catch (Exception err)
116 | {
117 | Logger.Error("Encrypt failed with err: " + err.Message);
118 | throw err;
119 | }
120 | }
121 |
122 | private byte[] DecryptData(byte[] encryptedData)
123 | {
124 | try
125 | {
126 | return ProtectedData.Unprotect(encryptedData, null, DataProtectionScope.LocalMachine);
127 | }
128 | catch (Exception err)
129 | {
130 | Logger.Error("Encrypt failed with err: " + err.Message);
131 | throw err;
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/RemarkableSync/RmCloudV2DownloadedDoc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace RemarkableSync
10 | {
11 | class RmCloudV2DownloadedDoc : RmDocument
12 | {
13 | private V2HttpHelper _httpHelper;
14 | private object _taskProgress;
15 |
16 | public RmCloudV2DownloadedDoc(string id, List fileList, HttpClient client, CancellationToken cancellationToken, IProgress progress) : base(id)
17 | {
18 | Logger.Debug($"Creating downloaded doc for id: {id}");
19 | _httpHelper = new V2HttpHelper(client);
20 | _taskProgress = 0;
21 |
22 | try
23 | {
24 | // find and download .content file first to get page mapping
25 | var contentDocFile = fileList.FirstOrDefault(docfile => docfile.DocumentID == (id + ".content"));
26 | if (contentDocFile == null)
27 | {
28 | throw new Exception("Content file not found, unable to parse");
29 | }
30 |
31 | progress.Report($"Getting page info for document...");
32 | if (!DownloadFile(contentDocFile, 0, progress).Result)
33 | {
34 | throw new Exception("Could not load file contents");
35 |
36 | }
37 |
38 | RmDocument doc = new RmDocument(id, _root_path);
39 |
40 | lock (_taskProgress)
41 | {
42 | _taskProgress = 0;
43 | }
44 |
45 | ParallelOptions po = new ParallelOptions
46 | {
47 | CancellationToken = cancellationToken
48 | };
49 |
50 | //todo: only need to download the binary pages?
51 |
52 | Parallel.ForEach(fileList, po,
53 | file =>
54 | {
55 | var result = DownloadFile(file, fileList.Count, progress).Result;
56 | });
57 | }
58 | catch (Exception err)
59 | {
60 | Logger.Error($"failed to download document content for id: {id}. Error: {err.Message}");
61 | _root_path = "";
62 | throw err;
63 | }
64 |
65 | try
66 | {
67 | string pageFolder = Path.Combine(_root_path, id);
68 | var rmFiles = new List(Directory.EnumerateFiles(pageFolder, "*.rm", SearchOption.TopDirectoryOnly));
69 | Logger.Debug($"Downloaded {rmFiles.Count} of {fileList.Count} files");
70 |
71 | }
72 | catch (Exception err)
73 | {
74 | Logger.Error($"failed to get page count. Error: {err.Message}");
75 |
76 | }
77 |
78 | Logger.Debug($"Finished creating downloaded doc for id: {id}");
79 | LoadDocumentContent();
80 | }
81 |
82 | private async Task DownloadFile(DocFile file, int totalFileCount, IProgress progress)
83 | {
84 | string dir = _root_path;
85 | string filename = file.DocumentID;
86 | Logger.Debug($"Downloading file: {file.DocumentID}, hash: {file.Hash} - Start");
87 |
88 | if (filename.Contains("/"))
89 | {
90 | // change to windows path notation
91 | filename = filename.Replace('/', '\\');
92 | dir = Path.Combine(dir, Path.GetDirectoryName(filename));
93 | filename = Path.GetFileName(filename);
94 | }
95 |
96 | Directory.CreateDirectory(dir);
97 | string fullPathFilename = Path.Combine(dir, filename);
98 |
99 | Stream stream = await _httpHelper.GetStreamFromHashAsync(file.Hash);
100 | if (stream == null)
101 | {
102 | Logger.Debug($"Downloading file: {file.DocumentID}, hash: {file.Hash} - returned null stream");
103 | return false;
104 | }
105 |
106 | var fileStream = File.Create(fullPathFilename);
107 | stream.Seek(0, SeekOrigin.Begin);
108 | stream.CopyTo(fileStream);
109 | fileStream.Close();
110 |
111 | if (totalFileCount > 0)
112 | {
113 | int taskProgress = 0;
114 | lock (_taskProgress)
115 | {
116 | taskProgress = (int)_taskProgress + 1;
117 | _taskProgress = taskProgress;
118 | }
119 | progress.Report($"Downloading document content... {taskProgress} out of {totalFileCount} files complete.");
120 | }
121 | Logger.Debug($"Downloading file: {file.DocumentID}, hash: {file.Hash} - Finished");
122 | return true;
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/RemarkableSync.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33213.308
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemarkableSync", "RemarkableSync\RemarkableSync.csproj", "{090B923F-25A0-4204-846C-71C0C7F50743}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleTest", "ConsoleTest\ConsoleTest.csproj", "{C23909A9-5D87-4E94-B69D-B87150054B3F}"
9 | ProjectSection(ProjectDependencies) = postProject
10 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488} = {6FA7BD3B-28C8-4B93-B0AC-99F022986488}
11 | {090B923F-25A0-4204-846C-71C0C7F50743} = {090B923F-25A0-4204-846C-71C0C7F50743}
12 | EndProjectSection
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnenoteAddin", "OnenoteAddin\OnenoteAddin.csproj", "{6FA7BD3B-28C8-4B93-B0AC-99F022986488}"
15 | ProjectSection(ProjectDependencies) = postProject
16 | {090B923F-25A0-4204-846C-71C0C7F50743} = {090B923F-25A0-4204-846C-71C0C7F50743}
17 | EndProjectSection
18 | EndProject
19 | Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "OnenoteAddinSetup", "OnenoteAddinSetup\OnenoteAddinSetup.vdproj", "{166B26BF-F8F8-4298-8D8A-A27420646FE6}"
20 | EndProject
21 | Global
22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
23 | Debug|Any CPU = Debug|Any CPU
24 | Debug|x64 = Debug|x64
25 | Debug|x86 = Debug|x86
26 | Release|Any CPU = Release|Any CPU
27 | Release|x64 = Release|x64
28 | Release|x86 = Release|x86
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {090B923F-25A0-4204-846C-71C0C7F50743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {090B923F-25A0-4204-846C-71C0C7F50743}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {090B923F-25A0-4204-846C-71C0C7F50743}.Debug|x64.ActiveCfg = Debug|x64
34 | {090B923F-25A0-4204-846C-71C0C7F50743}.Debug|x64.Build.0 = Debug|x64
35 | {090B923F-25A0-4204-846C-71C0C7F50743}.Debug|x86.ActiveCfg = Debug|x86
36 | {090B923F-25A0-4204-846C-71C0C7F50743}.Debug|x86.Build.0 = Debug|x86
37 | {090B923F-25A0-4204-846C-71C0C7F50743}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {090B923F-25A0-4204-846C-71C0C7F50743}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {090B923F-25A0-4204-846C-71C0C7F50743}.Release|x64.ActiveCfg = Release|x64
40 | {090B923F-25A0-4204-846C-71C0C7F50743}.Release|x64.Build.0 = Release|x64
41 | {090B923F-25A0-4204-846C-71C0C7F50743}.Release|x86.ActiveCfg = Release|x86
42 | {090B923F-25A0-4204-846C-71C0C7F50743}.Release|x86.Build.0 = Release|x86
43 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Debug|x64.ActiveCfg = Debug|x64
46 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Debug|x64.Build.0 = Debug|x64
47 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Debug|x86.ActiveCfg = Debug|x86
48 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Debug|x86.Build.0 = Debug|x86
49 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Release|x64.ActiveCfg = Release|x64
52 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Release|x86.ActiveCfg = Release|x86
53 | {C23909A9-5D87-4E94-B69D-B87150054B3F}.Release|x86.Build.0 = Release|x86
54 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Debug|x64.ActiveCfg = Debug|x64
57 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Debug|x64.Build.0 = Debug|x64
58 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Debug|x86.ActiveCfg = Debug|x86
59 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Debug|x86.Build.0 = Debug|x86
60 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Release|x64.ActiveCfg = Release|x64
63 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Release|x64.Build.0 = Release|x64
64 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Release|x86.ActiveCfg = Release|x86
65 | {6FA7BD3B-28C8-4B93-B0AC-99F022986488}.Release|x86.Build.0 = Release|x86
66 | {166B26BF-F8F8-4298-8D8A-A27420646FE6}.Debug|Any CPU.ActiveCfg = Debug
67 | {166B26BF-F8F8-4298-8D8A-A27420646FE6}.Debug|x64.ActiveCfg = Debug
68 | {166B26BF-F8F8-4298-8D8A-A27420646FE6}.Debug|x86.ActiveCfg = Debug
69 | {166B26BF-F8F8-4298-8D8A-A27420646FE6}.Release|Any CPU.ActiveCfg = Release
70 | {166B26BF-F8F8-4298-8D8A-A27420646FE6}.Release|x64.ActiveCfg = Release
71 | {166B26BF-F8F8-4298-8D8A-A27420646FE6}.Release|x86.ActiveCfg = Release
72 | EndGlobalSection
73 | GlobalSection(SolutionProperties) = preSolution
74 | HideSolutionNode = FALSE
75 | EndGlobalSection
76 | GlobalSection(ExtensibilityGlobals) = postSolution
77 | SolutionGuid = {F5BFB75C-3002-4750-92C0-6D80B004C4A7}
78 | EndGlobalSection
79 | EndGlobal
80 |
--------------------------------------------------------------------------------
/RemarkableSync/V2HttpHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using System.Net.Http.Json;
6 | using System.Threading.Tasks;
7 |
8 | namespace RemarkableSync
9 | {
10 | class V2HttpHelper
11 | {
12 | private static string BlobHost = "https://internal.cloud.remarkable.com";
13 | private static string DownloadUrl = BlobHost + "/api/v1/signed-urls/downloads";
14 | private static string HeaderGeneration = "x-goog-generation";
15 |
16 | private HttpClient _client;
17 |
18 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
19 |
20 | public V2HttpHelper(HttpClient client)
21 | {
22 | _client = client;
23 | }
24 |
25 | private async Task GetUrlAsync(string hash)
26 | {
27 | try
28 | {
29 | var requestContent = new BlobStorageRequest
30 | {
31 | http_method = "GET",
32 | relative_path = hash
33 | };
34 | HttpResponseMessage response = await HttpClientJsonExtensions.PostAsJsonAsync(_client, new Uri(DownloadUrl), requestContent);
35 | if (!response.IsSuccessStatusCode)
36 | {
37 | throw new Exception($"Request failed with status code {response.StatusCode}");
38 | }
39 |
40 | BlobStorageResponse blobResponse = await HttpContentJsonExtensions.ReadFromJsonAsync(response.Content);
41 | return blobResponse.url;
42 | }
43 | catch (Exception err)
44 | {
45 | Logger.Error($"Failed to get url for hash: {hash}. err: {err.ToString()} ");
46 | return "";
47 | }
48 | }
49 |
50 | public async Task GetBlobStreamFromHashAsync(string hash)
51 | {
52 | Logger.Debug($"Entering: .. hash = {hash}");
53 | try
54 | {
55 | string url = await GetUrlAsync(hash);
56 | if (url == "")
57 | {
58 | throw new Exception($"Failed to determine GET url");
59 | }
60 |
61 | var request = new HttpRequestMessage
62 | {
63 | RequestUri = new Uri(url),
64 | Method = HttpMethod.Get
65 | };
66 |
67 | HttpResponseMessage response = await _client.SendAsync(request);
68 | if (!response.IsSuccessStatusCode)
69 | {
70 | throw new Exception($"Request failed with status code {response.StatusCode}");
71 | }
72 |
73 | BlobStream blobStream = new BlobStream
74 | {
75 | Generation = long.Parse(response.Headers.GetValues(HeaderGeneration).First()),
76 | Blob = await response.Content.ReadAsStringAsync()
77 | };
78 |
79 | return blobStream;
80 | }
81 | catch (Exception err)
82 | {
83 | Logger.Error($"Failed to complete GET for hash: {hash}. err: {err.ToString()} ");
84 | return null;
85 | }
86 | }
87 |
88 | public async Task GetStreamFromHashAsync(string hash)
89 | {
90 | Logger.Debug($"Entering: .. hash = {hash}");
91 | try
92 | {
93 | string url = await GetUrlAsync(hash);
94 | if (url == "")
95 | {
96 | throw new Exception($"Failed to determine GET url");
97 | }
98 |
99 | var request = new HttpRequestMessage
100 | {
101 | RequestUri = new Uri(url),
102 | Method = HttpMethod.Get
103 | };
104 |
105 | HttpResponseMessage response = await _client.SendAsync(request);
106 | if (!response.IsSuccessStatusCode)
107 | {
108 | throw new Exception($"Request failed with status code {response.StatusCode}");
109 | }
110 |
111 | return await response.Content.ReadAsStreamAsync();
112 | }
113 | catch (Exception err)
114 | {
115 | Logger.Error($"Failed to complete GET for hash: {hash}. err: {err.ToString()} ");
116 | return null;
117 | }
118 | }
119 | }
120 |
121 | class BlobStream
122 | {
123 | public string Blob { get; set; }
124 | public long Generation { get; set; }
125 | }
126 |
127 | class BlobStorageRequest
128 | {
129 | public string http_method { get; set; }
130 | public string relative_path { get; set; }
131 | }
132 |
133 | class BlobStorageResponse
134 | {
135 | public string relative_path { get; set; }
136 | public string url { get; set; }
137 | public string expires { get; set; }
138 | public string method { get; set; }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/ConsoleTest/ConsoleTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {C23909A9-5D87-4E94-B69D-B87150054B3F}
8 | Exe
9 | RemarkbaleSync
10 | ConsoleTest
11 | v4.8
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 | true
37 | bin\x86\Debug\
38 | DEBUG;TRACE
39 | full
40 | x86
41 | 7.3
42 | prompt
43 | true
44 |
45 |
46 | bin\x86\Release\
47 | TRACE
48 | true
49 | pdbonly
50 | x86
51 | 7.3
52 | prompt
53 | true
54 |
55 |
56 | true
57 | bin\x64\Debug\
58 | DEBUG;TRACE
59 | full
60 | x64
61 | 7.3
62 | prompt
63 | true
64 |
65 |
66 | bin\x64\Release\
67 | TRACE
68 | true
69 | pdbonly
70 | x64
71 | 7.3
72 | prompt
73 | true
74 |
75 |
76 |
77 | ..\packages\NLog.5.1.3\lib\net46\NLog.dll
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | {6fa7bd3b-28c8-4b93-b0ac-99f022986488}
102 | OnenoteAddin
103 |
104 |
105 | {090b923f-25a0-4204-846c-71c0c7f50743}
106 | RemarkableSync
107 |
108 |
109 |
110 |
111 | {0EA692EE-BB50-4E3C-AEF0-356D91732725}
112 | 1
113 | 1
114 | 0
115 | tlbimp
116 | False
117 | True
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # reMarkable OneNote Addin
2 | This is a OneNote AddIn for importing digitized notes from the reMarkable tablet.
3 |
4 | ## Beta version 4.0
5 | Updated the original version to support the new reMarkable file format.
6 | Decoding the new format was based on the work of [Rick Lupton](https://github.com/ricklupton/rmscene).
7 |
8 | * There is no support for the new text yet.
9 | * When text is added to a notebook, the layout of the generated images is not correct.
10 |
11 |
12 | ## New in Version 3
13 | Version 3 is a major version update that adds support for the new (2022) reMarkable cloud API.
14 |
15 | Big shoutout to [juruen/rmapi](https://github.com/juruen/rmapi) for working out all the nitty gritty of the new API.
16 |
17 | Please note that while the new cloud API is now supported, due to the way it works, it is **SIGNIFICANTLY** slower than the previous cloud API. For example, getting the whole document tree using the previous API required only a single HTTPS request, regardless of how many documents are in the tree. Using the new API, it now requires 4 HTTPS requests for **EACH** document/folder (i.e. if your device has 200 document, 800 HTTPS requests are now needed to build up the document tree). While optimizations such as caching and multithreaded requests are used, it is still much much slower to populate the document tree, especially the first time the AddIn syncs to the device using the new API.
18 |
19 | ## Installation
20 | Latest release installer available at
21 |
22 |
23 | Choose either the 32-bit or the 64-bit Windows installer depending on whether the OneNote version is 32-bit or 64-bits:
24 | In OneNote, go to File menu -> Account -> About OneNote. The first line in the About window should say something like "Microsoft OneNote ... 64-bit or 32-bit"
25 |
26 | Once the installer completed, start OneNote. You should see a new tab in the ribbon bar called "reMarkable".
27 |
28 | ## Setup
29 | Before using the addin, there is a one time setup to link the addin to your reMarkable table and configure the hand writing recognition service.
30 |
31 | ### Linking reMarkable tablet
32 | Go to the reMarkable tab in the OneNote ribbon and click on the Settings icon. The Settings window should appear.
33 | Go to for a new desktop client one-time code. Copy it into the "One Time Code" field in Settings window and click Apply. This will link OneNote as a new desktop client to your reMarkable Cloud account.
34 |
35 | ### Configure hand writing recognition
36 | The reMarkable OneNote addin uses MyScript () as an external service to convert the handwritten stroke from reMarkable tablet into text. It is the same service used by the reMarkable device itself when selecting "Convert to text and send".
37 |
38 | Luckily MyScript provides 2000 free conversions a month, which is plenty for normal usage. All we have to do is register for a free account at . Once registered and logged in, go to , select Web platform and click "Send email" to receive your private application key via email. In the email you should receive 2 keys: the application key and the HMAC key. Copy each key into the corresponding field in the Settings windows of the addin and hit Apply to save the configuration.
39 |
40 | ## Troubleshooting
41 | This addin does not produce any log by default. To generate log for troubleshooting, open up Windows Registry and navigate to the following key:
42 | * "Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\OneNote\AddInsData\RemarkableSync.OnenoteAddin" for 64 bit-install or,
43 | * "Computer\HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\Microsoft\Office\OneNote\AddInsData\RemarkableSync.OnenoteAddin" for 32-bit install
44 |
45 | Then add a new string-valued entry named "LogFile". The value should be a full file path to the log file to be generated, e.g. "C:\temp\remarkable_sync_log.txt".
46 | Once OneNote is restarted, the addin will start generating log.
47 |
48 | ## Technical details
49 | Since OneNote only supports out of process COM addin, this addin was created as a C# local server COM class. While this addin is loaded, you will see an instance of RemarkableSync.OnenoteAddin.exe process running at the same time as OneNote.exe.
50 |
51 | When the Fetch window is opened, the addin will retrieve all the notebooks that have been synced to reMarkable cloud and show them in a tree structure according to the folder they are in. Selecting a notebook in the tree and clicking Enter will download the .lines files for all the pages in that notebook, parse these files into individual pen strokes and send them to MyScript for conversion into plain text. The converted text is then inserted as a new page into the current OneNote notebook and tab.
52 |
53 | ## Acknowledgement
54 | This is a personal project of mine that was created out of what to me was a missing piece in the overall workflow of using reMarkable tablet for taking work and study notes.
55 |
56 | I'd like to thanks for following people and/or sites for inspiration, information and help:
57 | - [MyScript](https://www.myscript.com) for providing the awesome free hand writing recongnitions services.
58 | - [reHackable/awesome-reMarkable](https://github.com/reHackable/awesome-reMarkable) for compiling the list of existing reMarkable hacks out there.
59 | - [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) for introducing MyScipt as the hand writing recognition services used by reMarkable.
60 | - [subutux/rmapy](https://github.com/subutux/rmapy) for showing me how to interface with the reMarkable cloud.
61 | - [bsdz/remarkable-layers](https://github.com/bsdz/remarkable-layers) for the Python parser for the .line format used by reMarkable, which I ported to C# for this project.
62 | - [Lim Bio Liong at CodeProject](https://www.codeproject.com/Articles/12579/Building-COM-Servers-in-NET) for providing the boilerplate code for creating a C# .NET COM server.
63 | - [juruen/rmapi](https://github.com/juruen/rmapi) for working out how the new cloud api works.
64 | - [ricklupton/rmscene](https://github.com/ricklupton/rmscene) for decoding the new binary format.
--------------------------------------------------------------------------------
/OnenoteAddin/PreviewForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
--------------------------------------------------------------------------------
/OnenoteAddin/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace RemarkableSync.OnenoteAddin.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RemarkableSync.OnenoteAddin.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to RemarkableAddin.
65 | ///
66 | internal static string AddInTitle {
67 | get {
68 | return ResourceManager.GetString("AddInTitle", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized resource of type System.Drawing.Bitmap.
74 | ///
75 | internal static System.Drawing.Bitmap DownloadDoc {
76 | get {
77 | object obj = ResourceManager.GetObject("DownloadDoc", resourceCulture);
78 | return ((System.Drawing.Bitmap)(obj));
79 | }
80 | }
81 |
82 | ///
83 | /// Looks up a localized resource of type System.Drawing.Bitmap.
84 | ///
85 | internal static System.Drawing.Bitmap loading_spinner {
86 | get {
87 | object obj = ResourceManager.GetObject("loading_spinner", resourceCulture);
88 | return ((System.Drawing.Bitmap)(obj));
89 | }
90 | }
91 |
92 | ///
93 | /// Looks up a localized resource of type System.Drawing.Bitmap.
94 | ///
95 | internal static System.Drawing.Bitmap opacity_grey {
96 | get {
97 | object obj = ResourceManager.GetObject("opacity_grey", resourceCulture);
98 | return ((System.Drawing.Bitmap)(obj));
99 | }
100 | }
101 |
102 | ///
103 | /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" ?>
104 | ///<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" loadImage="GetImage">
105 | /// <ribbon>
106 | /// <tabs>
107 | /// <!--
108 | /// <tab idMso="TabHome">
109 | /// <group id="groupVanillaAddIn" label="VanillaAddIn">
110 | /// <button id="buttonVanillaAddIn" label="VanillaAddIn" size="large" screentip="VanillaAddIn" onAction="VanillaAddInButtonClicked" image="Logo.png"/>
111 | /// </group>
112 | /// </tab>
113 | /// -->
114 | /// <tab id="tabRemarkable" label="reMarkable">
/// [rest of string was truncated]";.
115 | ///
116 | internal static string ribbon {
117 | get {
118 | return ResourceManager.GetString("ribbon", resourceCulture);
119 | }
120 | }
121 |
122 | ///
123 | /// Looks up a localized resource of type System.Drawing.Bitmap.
124 | ///
125 | internal static System.Drawing.Bitmap Settings {
126 | get {
127 | object obj = ResourceManager.GetObject("Settings", resourceCulture);
128 | return ((System.Drawing.Bitmap)(obj));
129 | }
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/RemarkableSync/RmSftpJsonTypes.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System.IO;
3 | using System.Text;
4 | using System.Text.Json;
5 |
6 | namespace RemarkableSync
7 | {
8 | public partial class RmSftpDataSource : IRmDataSource
9 | {
10 | public class NotebookMetadata
11 | {
12 | public static NotebookMetadata FromStream(Stream stream)
13 | {
14 | string content = "";
15 | stream.Position = 0;
16 | using (var reader = new StreamReader(stream, Encoding.UTF8))
17 | {
18 | content = reader.ReadToEnd();
19 | }
20 | return JsonSerializer.Deserialize(content);
21 | }
22 |
23 | public RmItem ToRmItem(string filename)
24 | {
25 | RmItem item = new RmItem();
26 |
27 | // strip extension from filename to use as ID
28 | int pos = filename.IndexOf('.');
29 | item.ID = pos == -1 ? filename : filename.Substring(0, pos);
30 | item.Version = version;
31 | item.Type = type;
32 | item.VissibleName = visibleName;
33 | item.Parent = parent;
34 | return item;
35 | }
36 |
37 | public bool deleted { get; set; }
38 | public string lastModified { get; set; }
39 | public int lastOpenedPage { get; set; }
40 | public bool metadatamodified { get; set; }
41 | public bool modified { get; set; }
42 | public string parent { get; set; }
43 | public bool pinned { get; set; }
44 | public bool synced { get; set; }
45 | public string type { get; set; }
46 | public int version { get; set; }
47 | public string visibleName { get; set; }
48 | }
49 |
50 | public class NotebookContent
51 | {
52 | public static NotebookContent FromStream(Stream stream)
53 | {
54 | string content = "";
55 | stream.Position = 0;
56 | using (var reader = new StreamReader(stream, Encoding.UTF8))
57 | {
58 | content = reader.ReadToEnd();
59 | }
60 | return JsonSerializer.Deserialize(content);
61 | }
62 |
63 | public int coverPageNumber { get; set; }
64 | public bool dummyDocument { get; set; }
65 | public Extrametadata extraMetadata { get; set; }
66 | public string fileType { get; set; }
67 | public string fontName { get; set; }
68 | public int lineHeight { get; set; }
69 | public int margins { get; set; }
70 | public string orientation { get; set; }
71 | public int pageCount { get; set; }
72 | public string[] pages { get; set; }
73 | public string textAlignment { get; set; }
74 | public int textScale { get; set; }
75 | public Transform transform { get; set; }
76 | }
77 |
78 | public class Extrametadata
79 | {
80 | public string LastBallpointColor { get; set; }
81 | public string LastBallpointSize { get; set; }
82 | public string LastBallpointv2Color { get; set; }
83 | public string LastBallpointv2Size { get; set; }
84 | public string LastCalligraphyColor { get; set; }
85 | public string LastCalligraphySize { get; set; }
86 | public string LastClearPageColor { get; set; }
87 | public string LastClearPageSize { get; set; }
88 | public string LastEraseSectionColor { get; set; }
89 | public string LastEraseSectionSize { get; set; }
90 | public string LastEraserColor { get; set; }
91 | public string LastEraserSize { get; set; }
92 | public string LastEraserTool { get; set; }
93 | public string LastFinelinerColor { get; set; }
94 | public string LastFinelinerSize { get; set; }
95 | public string LastFinelinerv2Color { get; set; }
96 | public string LastFinelinerv2Size { get; set; }
97 | public string LastHighlighterColor { get; set; }
98 | public string LastHighlighterSize { get; set; }
99 | public string LastHighlighterv2Color { get; set; }
100 | public string LastHighlighterv2Size { get; set; }
101 | public string LastMarkerColor { get; set; }
102 | public string LastMarkerSize { get; set; }
103 | public string LastMarkerv2Color { get; set; }
104 | public string LastMarkerv2Size { get; set; }
105 | public string LastPaintbrushColor { get; set; }
106 | public string LastPaintbrushSize { get; set; }
107 | public string LastPaintbrushv2Color { get; set; }
108 | public string LastPaintbrushv2Size { get; set; }
109 | public string LastPen { get; set; }
110 | public string LastPencilColor { get; set; }
111 | public string LastPencilSize { get; set; }
112 | public string LastPencilv2Color { get; set; }
113 | public string LastPencilv2Size { get; set; }
114 | public string LastReservedPenColor { get; set; }
115 | public string LastReservedPenSize { get; set; }
116 | public string LastSelectionToolColor { get; set; }
117 | public string LastSelectionToolSize { get; set; }
118 | public string LastSharpPencilColor { get; set; }
119 | public string LastSharpPencilSize { get; set; }
120 | public string LastSharpPencilv2Color { get; set; }
121 | public string LastSharpPencilv2Size { get; set; }
122 | public string LastSolidPenColor { get; set; }
123 | public string LastSolidPenSize { get; set; }
124 | public string LastTool { get; set; }
125 | public string LastZoomToolColor { get; set; }
126 | public string LastZoomToolSize { get; set; }
127 | }
128 |
129 | public class Transform
130 | {
131 | public int m11 { get; set; }
132 | public int m12 { get; set; }
133 | public int m13 { get; set; }
134 | public int m21 { get; set; }
135 | public int m22 { get; set; }
136 | public int m23 { get; set; }
137 | public int m31 { get; set; }
138 | public int m32 { get; set; }
139 | public int m33 { get; set; }
140 | }
141 |
142 | }
143 | }
144 |
145 |
--------------------------------------------------------------------------------
/ConsoleTest/Program.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Drawing.Imaging;
5 | using System.IO;
6 | using System.Threading;
7 |
8 | namespace RemarkableSync
9 | {
10 | class Program
11 | {
12 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
13 | private static string startPath = @"C:\dev\reMarkableSync\backup";
14 | private static string outputPath = @"C:\dev\reMarkableSync\backup";
15 | private static string SftpIP = "192.168.0.204";
16 | private static string SftPass = "***";
17 |
18 | static void Main(string[] args)
19 | {
20 | //ProcessSource(0);
21 | doSomethingDifferent();
22 |
23 | Logger.Debug("Done! Press key to exit");
24 | Console.ReadKey();
25 | }
26 |
27 | static void doSomethingDifferent()
28 | {
29 | string ID = "c06ef5e1-76aa-40fa-97fd-5533253b1d02";
30 | int pageNr = 3;
31 | CancellationToken _cancellationToken = new CancellationToken();
32 | //IRmDataSource source = new LocalFolderDataSource(startPath);
33 | IRmDataSource source = new RmSftpDataSource(SftpIP, SftPass);
34 | RmDocument doc = source.DownloadDocument(ID, _cancellationToken, null).Result;
35 | doc.GetPageAsImage(pageNr).Save(Path.Combine(outputPath, $"{ID}_{pageNr + 1}.png"), ImageFormat.Png);
36 | }
37 |
38 | static void ProcessSource(int source, string ID = null)
39 | {
40 | IRmDataSource _rmDataSource = null;
41 | switch (source)
42 | {
43 | case 0:
44 | Logger.Debug("Using local data source");
45 | _rmDataSource = new LocalFolderDataSource(startPath);
46 | break;
47 | case 1:
48 | Logger.Debug("Using SFTP data source");
49 | _rmDataSource = new RmSftpDataSource(SftpIP, SftPass);
50 | break;
51 | case 2:
52 | Logger.Debug("Using Online data source");
53 | _rmDataSource = new RmCloudDataSource(new WinRegistryConfigStore(@"Software\Microsoft\Office\OneNote\AddInsData\RemarkableSync.OnenoteAddin"));
54 | break;
55 | default:
56 | return;
57 | }
58 | if (ID != null)
59 | {
60 | SavePageImages(_rmDataSource, ID);
61 | }
62 | else
63 | {
64 | NavigateRootItems(_rmDataSource);
65 | }
66 | }
67 |
68 | static void SavePageImages(IRmDataSource source, string ID)
69 | {
70 | Progress progress = new Progress((string updateText) =>
71 | {
72 | Logger.Debug($"Progressing:\n{updateText}");
73 | });
74 | CancellationToken _cancellationToken = new CancellationToken();
75 |
76 | RmDocument doc = source.DownloadDocument(ID, _cancellationToken, progress).Result;
77 | for (int i = 0; i < doc.PageCount; i++)
78 | {
79 | doc.GetPageAsImage(i).Save(Path.Combine(outputPath, $"{ID}_{i + 1}.png"), ImageFormat.Png);
80 | }
81 | }
82 |
83 | static void NavigateRootItems(IRmDataSource source)
84 | {
85 | Progress progress = new Progress((string updateText) =>
86 | {
87 | Logger.Debug($"Getting document list:\n{updateText}");
88 | });
89 | CancellationToken _cancellationToken = new CancellationToken();
90 | var rootItems = source.GetItemHierarchy(_cancellationToken, progress).Result;
91 | Logger.Debug($"Found {rootItems.Count} items");
92 |
93 | rootItems = RmItem.SortItems(rootItems);
94 | var currentSelection = rootItems;
95 |
96 | PrintDirectory(rootItems);
97 | string currentPath = "root";
98 | Console.WriteLine("Current Directory: {0}", currentPath);
99 |
100 | while (true)
101 | {
102 | Console.WriteLine("Enter a number to navigate to a subdirectory (or q to quit, r to restart):");
103 | string input = Console.ReadLine();
104 | int selection = 0;
105 |
106 | if (input == "q")
107 | {
108 | break;
109 | }
110 | if (input == "r")
111 | {
112 | currentSelection = rootItems;
113 | currentPath = "root";
114 | Console.Clear();
115 | Console.WriteLine("Current Directory: {0}", currentPath);
116 | PrintDirectory(currentSelection);
117 | continue;
118 |
119 | }
120 | if (!int.TryParse(input, out selection))
121 | {
122 | Console.WriteLine("Invalid input!");
123 | continue;
124 | }
125 |
126 | if (selection < 1 || selection > currentSelection.Count)
127 | {
128 | Console.WriteLine("Invalid selection!");
129 | continue;
130 | }
131 |
132 | RmItem selected = currentSelection[selection - 1];
133 | if (selected.Type == RmItem.CollectionType)
134 | {
135 | currentPath = Path.Combine(currentPath, selected.VissibleName);
136 | currentSelection = selected.Children;
137 | }
138 | else
139 | {
140 | SavePageImages(source, selected.ID);
141 | }
142 |
143 | Console.Clear();
144 | Console.WriteLine("Current Directory: {0}", currentPath);
145 | PrintDirectory(currentSelection);
146 | }
147 | }
148 |
149 | static void PrintDirectory(List collection)
150 | {
151 | Console.WriteLine("Folder:");
152 | for (int i = 0; i < collection.Count; i++)
153 | {
154 | if (collection[i].Type == RmItem.CollectionType)
155 | {
156 | Console.WriteLine("\t{0}. {1}/", i + 1, collection[i].VissibleName);
157 | }
158 | else
159 | {
160 | Console.WriteLine("\t{0}. {1}", i + 1, collection[i].VissibleName);
161 | }
162 |
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v6/RmLines.cs:
--------------------------------------------------------------------------------
1 | using Renci.SshNet;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using RemarkableSync.document.v6.SceneItems;
10 |
11 | // TODO: Exception handling
12 | namespace RemarkableSync.document.v6
13 | {
14 | class RmConstants
15 | {
16 | public static int X_MAX = 1404;
17 | public static int Y_MAX = 1872;
18 | }
19 |
20 | public abstract class ByteableList
21 | {
22 | protected List _objects;
23 |
24 | public ByteableList()
25 | {
26 | _objects = new List();
27 | }
28 |
29 | public abstract ByteableList CreateChild();
30 |
31 | public void Append(ByteableList child)
32 | {
33 | _objects.Add(child);
34 | }
35 |
36 | public void Log()
37 | {
38 | Logger.LogMessage(ToString());
39 | foreach(var child in _objects)
40 | {
41 | child.Log();
42 | }
43 | }
44 |
45 | public virtual void FromStream(MemoryStream buffer, List layerNames)
46 | {
47 | byte[] numberBytes = new byte[4];
48 | buffer.Read(numberBytes, 0, numberBytes.Length);
49 | int numChildren = BitConverter.ToInt32(numberBytes, 0);
50 | for(int i = 0; i < numChildren; ++i)
51 | {
52 | ByteableList child = CreateChild();
53 | child.FromStream( buffer, layerNames);
54 | Append(child);
55 | }
56 | }
57 |
58 | public override string ToString()
59 | {
60 | return $"Unimplemented base type";
61 | }
62 |
63 | public List Objects
64 | {
65 | get { return _objects; }
66 | }
67 | }
68 |
69 | public class RmPage: ByteableList
70 | {
71 | public static RmPage ParseStream(MemoryStream contentStream, List layerNames)
72 | {
73 | RmPage page = new RmPage();
74 | contentStream.Seek(0, SeekOrigin.Begin);
75 | page.FromStream(contentStream, layerNames);
76 | return page;
77 | }
78 |
79 | public string Header { get; set; }
80 | protected int fileVersion;
81 |
82 | public override ByteableList CreateChild()
83 | {
84 | return new RmLayer();
85 | }
86 |
87 | public override void FromStream( MemoryStream buffer, List layerNames)
88 | {
89 | byte[] headerBytes = new byte[43];
90 | buffer.Read(headerBytes, 0, headerBytes.Length);
91 | Header = Encoding.ASCII.GetString(headerBytes, 0, headerBytes.Length);
92 |
93 | int.TryParse(Header.Split('=').ElementAt(1), out fileVersion);
94 | base.FromStream(buffer, layerNames);
95 | }
96 |
97 | public override string ToString()
98 | {
99 | return $"Lines: header=\"{ Header}\", nobjs={_objects.Count}";
100 | }
101 | }
102 |
103 | class RmLayer:ByteableList
104 | {
105 | public Color? LayerColor { get; set; }
106 |
107 | public override ByteableList CreateChild()
108 | {
109 | return new RmStroke();
110 | }
111 |
112 | public override void FromStream(MemoryStream buffer, List layerNames)
113 | {
114 | LayerColor = null;
115 | if (layerNames.Count > 0)
116 | {
117 | var color = Color.FromName(layerNames[0]);
118 | if (color.IsKnownColor)
119 | {
120 | LayerColor = color;
121 | }
122 | layerNames.RemoveAt(0);
123 | }
124 |
125 | base.FromStream(buffer, layerNames);
126 | }
127 |
128 | public override string ToString()
129 | {
130 | return $"Layer: nobjs={_objects.Count}";
131 | }
132 | }
133 |
134 | class RmStroke: ByteableList
135 | {
136 | public RmPen Pen { get; set; }
137 | public RmColor Colour { get; set; }
138 | public float Width { get; set; }
139 |
140 | public override ByteableList CreateChild()
141 | {
142 | return new RmSegment();
143 | }
144 |
145 | public override void FromStream(MemoryStream buffer, List layerNames)
146 | {
147 | byte[] strokeHeaderBytes = new byte[20];
148 | buffer.Read(strokeHeaderBytes, 0, strokeHeaderBytes.Length);
149 |
150 | Pen = (RmPen)BitConverter.ToInt32(strokeHeaderBytes, 0);
151 | Colour = (RmColor)BitConverter.ToInt32(strokeHeaderBytes, 4);
152 | Width = BitConverter.ToSingle(strokeHeaderBytes, 12);
153 |
154 | base.FromStream(buffer, layerNames);
155 | }
156 |
157 | public override string ToString()
158 | {
159 | return $"Stroke: pen={Pen.ToString()}, color={Colour.ToString()}, width={Width,-5: F4}, nobjs={_objects.Count}";
160 | }
161 |
162 | public bool IsVisible()
163 | {
164 | switch(Pen)
165 | {
166 | case RmPen.ERASER:
167 | case RmPen.ERASER_AREA:
168 | default:
169 | return true;
170 | }
171 | }
172 | }
173 |
174 | class RmSegment: ByteableList
175 | {
176 | public float X { get; set; }
177 | public float Y { get; set; }
178 | public float Speed { get; set; }
179 | public float Tilt { get; set; }
180 | public float Width { get; set; }
181 | public float Pressure { get; set; }
182 |
183 | public override ByteableList CreateChild()
184 | {
185 | throw (new Exception("Segment has no children type"));
186 | }
187 |
188 | public override void FromStream( MemoryStream buffer, List layerNames)
189 | {
190 | byte[] segmentBytes = new byte[24];
191 | buffer.Read(segmentBytes, 0, segmentBytes.Length);
192 |
193 | X = BitConverter.ToSingle(segmentBytes, 0);
194 | Y = BitConverter.ToSingle(segmentBytes, 4);
195 | Speed = BitConverter.ToSingle(segmentBytes, 8);
196 | Tilt = BitConverter.ToSingle(segmentBytes, 12);
197 | Width = BitConverter.ToSingle(segmentBytes, 16);
198 | Pressure = BitConverter.ToSingle(segmentBytes, 20);
199 | }
200 |
201 | public override string ToString()
202 | {
203 | return $"Segment: X={X,-6: F1}, Y={Y,-6: F1}, Speed={Speed,-6: F1}, Tilt={Width,-6: F4}, Width={Width,-6: F4}, Pressure={Pressure,-6: F4}, nobjs={_objects.Count}";
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/OnenoteAddin/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | RemarkableAddin
122 |
123 |
124 |
125 | ..\Resources\DownloadDoc.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
126 |
127 |
128 | ..\Resources\loading_spinner.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
129 |
130 |
131 | ..\Resources\opacity_grey.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
132 |
133 |
134 | ..\ribbon.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
135 |
136 |
137 | ..\Resources\Settings.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
138 |
139 |
--------------------------------------------------------------------------------
/OnenoteAddin/SettingsForm.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.MyScript;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 | using System.Windows.Forms;
6 |
7 | namespace RemarkableSync.OnenoteAddin
8 | {
9 | public partial class SettingsForm : Form
10 | {
11 | public enum RmConnectionMethod
12 | {
13 | RmCloud = 0,
14 | Ssh = 1
15 | }
16 |
17 | public static readonly string RmConnectionMethodConfig = "RmConnectionMethod";
18 |
19 | private IConfigStore _configStore;
20 |
21 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
22 |
23 | public SettingsForm(string settingsRegPath)
24 | {
25 | _configStore = new WinRegistryConfigStore(settingsRegPath);
26 | InitializeComponent();
27 | getConnectionMethodFromConfig();
28 | }
29 |
30 | private async void btnRemarkableApply_Click(object sender, EventArgs e)
31 | {
32 | if (textOtc.Text.Length == 0)
33 | {
34 | MessageBox.Show(this, "Please enter One Time Code for setting up a new reMarkable client.", "Enter One Time Code");
35 | return;
36 | }
37 |
38 | var dialogResult = MessageBox.Show(this, "Are you sure you want to register a new reMarkable tablet to import from and replace any existing devices?",
39 | "Confirm", MessageBoxButtons.YesNo);
40 |
41 | if (dialogResult != DialogResult.Yes)
42 | {
43 | return;
44 | }
45 |
46 | ToggleLoadingIcon(true);
47 |
48 | string otc = textOtc.Text;
49 | RmCloudDataSource rmClient = new RmCloudDataSource(_configStore);
50 | bool registerResult = await rmClient.RegisterWithOneTimeCode(otc);
51 |
52 | ToggleLoadingIcon(false);
53 |
54 | if (registerResult)
55 | {
56 | MessageBox.Show(this, "New reMarkable device registered successfully", "Success");
57 | textOtc.Text = "";
58 | return;
59 | }
60 | else
61 | {
62 | MessageBox.Show(this, "Error registering new reMarkable device", "Failed");
63 | textOtc.Text = "";
64 | return;
65 | }
66 | }
67 |
68 | private async void btnMyScriptApply_Click(object sender, EventArgs e)
69 | {
70 | if (textAppKey.Text.Length == 0 || textHmacKey.Text.Length == 0)
71 | {
72 | MessageBox.Show(this, "Please enter your MyScript App key and HMAC key", "Enter MyScript Keys");
73 | return;
74 | }
75 |
76 | var dialogResult = MessageBox.Show(this, "Are you sure you want to replace any existing MyScript configuration?",
77 | "Confirm", MessageBoxButtons.YesNo);
78 |
79 | if (dialogResult != DialogResult.Yes)
80 | {
81 | return;
82 | }
83 |
84 | string appKey = textAppKey.Text;
85 | string hmacKey = textHmacKey.Text;
86 |
87 | ToggleLoadingIcon(true);
88 | await Task.Run(() =>
89 | {
90 | MyScriptClient myScriptClient = new MyScriptClient(_configStore);
91 | myScriptClient.SetConfig(appKey, hmacKey);
92 | });
93 | ToggleLoadingIcon(false);
94 |
95 | MessageBox.Show(this, "MyScript configuration updated", "Success");
96 | textAppKey.Text = "";
97 | textHmacKey.Text = "";
98 | }
99 |
100 | private void ToggleLoadingIcon(bool show)
101 | {
102 | if (show)
103 | {
104 | Cursor.Current = Cursors.WaitCursor;
105 | btnMyScriptApply.Enabled = false;
106 | btnRemarkableCloudApply.Enabled = false;
107 | }
108 | else
109 | {
110 | Cursor.Current = Cursors.Default;
111 | btnMyScriptApply.Enabled = true;
112 | btnRemarkableCloudApply.Enabled = true;
113 | }
114 | }
115 |
116 | private void radioButtonRmCloud_CheckedChanged(object sender, EventArgs e)
117 | {
118 | tableLayoutRmCloud.Enabled = radioButtonRmCloud.Checked;
119 | if (radioButtonRmCloud.Checked && sender != null)
120 | {
121 | setRmConnectionMethod(RmConnectionMethod.RmCloud);
122 | }
123 | }
124 |
125 | private void radioButtonRmSsh_CheckedChanged(object sender, EventArgs e)
126 | {
127 | tableLayoutRmSsh.Enabled = radioButtonRmSsh.Checked;
128 | if (radioButtonRmSsh.Checked && sender != null)
129 | {
130 | setRmConnectionMethod(RmConnectionMethod.Ssh);
131 | }
132 | }
133 |
134 | private void btnRemarkableSshApply_Click(object sender, EventArgs e)
135 | {
136 | if (textSshPassword.Text.Length == 0)
137 | {
138 | MessageBox.Show(this, "Please enter your reMarkable SSH password", "Enter SSH Password");
139 | return;
140 | }
141 |
142 | Dictionary mapConfigs = new Dictionary();
143 | mapConfigs[RmSftpDataSource.SshPasswordConfig] = textSshPassword.Text;
144 | mapConfigs[RmSftpDataSource.SshHostConfig] = "10.11.99.1"; // hard-coded to USB connection IP
145 |
146 | if (_configStore.SetConfigs(mapConfigs))
147 | {
148 | MessageBox.Show(this, "SSH password saved.", "Success");
149 | return;
150 | }
151 | else
152 | {
153 | MessageBox.Show(this, "Error saving SSH password.", "Error");
154 | return;
155 | }
156 | }
157 |
158 | private void setRmConnectionMethod(RmConnectionMethod connectionMethod)
159 | {
160 | Logger.Debug($"Setting connection method to {connectionMethod.ToString()}");
161 | Dictionary mapConfigs = new Dictionary();
162 | mapConfigs[RmConnectionMethodConfig] = connectionMethod.ToString("d");
163 | _configStore.SetConfigs(mapConfigs);
164 | }
165 |
166 | private void getConnectionMethodFromConfig()
167 | {
168 | int connMethod = -1;
169 | try
170 | {
171 | string connMethodString = _configStore.GetConfig(RmConnectionMethodConfig);
172 | connMethod = Convert.ToInt32(connMethodString);
173 | }
174 | catch (Exception err)
175 | {
176 | Logger.Error($"Failed to get RmConnectionMethod config with err: {err.Message}");
177 | // will default to cloud
178 | }
179 |
180 | switch (connMethod)
181 | {
182 | case (int)SettingsForm.RmConnectionMethod.Ssh:
183 | radioButtonRmCloud.Checked = false;
184 | radioButtonRmSsh.Checked = true;
185 | break;
186 | case (int)SettingsForm.RmConnectionMethod.RmCloud:
187 | default:
188 | radioButtonRmCloud.Checked = true;
189 | radioButtonRmSsh.Checked = false;
190 | break;
191 | }
192 | radioButtonRmCloud_CheckedChanged(null, null);
193 | radioButtonRmSsh_CheckedChanged(null, null);
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/RemarkableSync/RmLines.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | // TODO: Exception handling
10 | namespace RemarkableSync.RmLine
11 | {
12 | class RmConstants
13 | {
14 | public static int X_MAX = 1404;
15 | public static int Y_MAX = 1872;
16 | }
17 |
18 | public enum PenEnum
19 | {
20 | // see https://github.com/ax3l/lines-are-beautiful/blob/develop/include/rmlab/Line.hpp
21 | BRUSH = 0,
22 | PENCIL_TILT = 1,
23 | BALLPOINT_PEN_1 = 2,
24 | MARKER_1 = 3,
25 | FINELINER_1 = 4,
26 | HIGHLIGHTER = 5,
27 | RUBBER = 6, // used in version 5
28 | PENCIL_SHARP = 7,
29 | RUBBER_AREA = 8,
30 | ERASE_ALL = 9,
31 | SELECTION_BRUSH_1 = 10,
32 | SELECTION_BRUSH_2 = 11,
33 | // below used for version 5
34 | PAINT_BRUSH_1 = 12,
35 | MECHANICAL_PENCIL_1 = 13,
36 | PENCIL_2 = 14,
37 | BALLPOINT_PEN_2 = 15,
38 | MARKER_2 = 16,
39 | FINELINER_2 = 17,
40 | HIGHLIGHTER_2 = 18,
41 | DEFAULT = FINELINER_2
42 | }
43 |
44 | public enum ColourEnum
45 | {
46 | BLACK = 0,
47 | GREY = 1,
48 | WHITE = 2
49 | }
50 |
51 | public abstract class ByteableList
52 | {
53 | protected List _objects;
54 |
55 | public ByteableList()
56 | {
57 | _objects = new List();
58 | }
59 |
60 | public abstract ByteableList CreateChild();
61 |
62 | public void Append(ByteableList child)
63 | {
64 | _objects.Add(child);
65 | }
66 |
67 | public void Log()
68 | {
69 | Logger.LogMessage(ToString());
70 | foreach(var child in _objects)
71 | {
72 | child.Log();
73 | }
74 | }
75 |
76 | public virtual void FromStream(MemoryStream buffer, List layerNames)
77 | {
78 | byte[] numberBytes = new byte[4];
79 | buffer.Read(numberBytes, 0, numberBytes.Length);
80 | int numChildren = BitConverter.ToInt32(numberBytes, 0);
81 | for(int i = 0; i < numChildren; ++i)
82 | {
83 | ByteableList child = CreateChild();
84 | child.FromStream( buffer, layerNames);
85 | Append(child);
86 | }
87 | }
88 |
89 | public override string ToString()
90 | {
91 | return $"Unimplemented base type";
92 | }
93 |
94 | public List Objects
95 | {
96 | get { return _objects; }
97 | }
98 | }
99 |
100 | public class RmPage: ByteableList
101 | {
102 | public static RmPage ParseStream(MemoryStream contentStream, List layerNames)
103 | {
104 | RmPage page = new RmPage();
105 | contentStream.Seek(0, SeekOrigin.Begin);
106 | page.FromStream(contentStream, layerNames);
107 | return page;
108 | }
109 |
110 | public string Header { get; set; }
111 |
112 | public override ByteableList CreateChild()
113 | {
114 | return new RmLayer();
115 | }
116 |
117 | public override void FromStream( MemoryStream buffer, List layerNames)
118 | {
119 | byte[] headerBytes = new byte[43];
120 | buffer.Read(headerBytes, 0, headerBytes.Length);
121 | Header = Encoding.ASCII.GetString(headerBytes, 0, headerBytes.Length);
122 | base.FromStream(buffer, layerNames);
123 | }
124 |
125 | public override string ToString()
126 | {
127 | return $"Lines: header=\"{ Header}\", nobjs={_objects.Count}";
128 | }
129 | }
130 |
131 | class RmLayer:ByteableList
132 | {
133 | public Color? LayerColor { get; set; }
134 |
135 | public override ByteableList CreateChild()
136 | {
137 | return new RmStroke();
138 | }
139 |
140 | public override void FromStream(MemoryStream buffer, List layerNames)
141 | {
142 | LayerColor = null;
143 | if (layerNames.Count > 0)
144 | {
145 | var color = Color.FromName(layerNames[0]);
146 | if (color.IsKnownColor)
147 | {
148 | LayerColor = color;
149 | }
150 | layerNames.RemoveAt(0);
151 | }
152 |
153 | base.FromStream(buffer, layerNames);
154 | }
155 |
156 | public override string ToString()
157 | {
158 | return $"Layer: nobjs={_objects.Count}";
159 | }
160 | }
161 |
162 | class RmStroke: ByteableList
163 | {
164 | public PenEnum Pen { get; set; }
165 | public ColourEnum Colour { get; set; }
166 | public float Width { get; set; }
167 |
168 | public override ByteableList CreateChild()
169 | {
170 | return new RmSegment();
171 | }
172 |
173 | public override void FromStream(MemoryStream buffer, List layerNames)
174 | {
175 | byte[] strokeHeaderBytes = new byte[20];
176 | buffer.Read(strokeHeaderBytes, 0, strokeHeaderBytes.Length);
177 |
178 | Pen = (PenEnum) BitConverter.ToInt32(strokeHeaderBytes, 0);
179 | Colour = (ColourEnum)BitConverter.ToInt32(strokeHeaderBytes, 4);
180 | Width = BitConverter.ToSingle(strokeHeaderBytes, 12);
181 |
182 | base.FromStream(buffer, layerNames);
183 | }
184 |
185 | public override string ToString()
186 | {
187 | return $"Stroke: pen={Pen.ToString()}, color={Colour.ToString()}, width={Width,-5: F4}, nobjs={_objects.Count}";
188 | }
189 |
190 | public bool IsVisible()
191 | {
192 | switch(Pen)
193 | {
194 | case PenEnum.RUBBER:
195 | case PenEnum.RUBBER_AREA:
196 | case PenEnum.ERASE_ALL:
197 | return false;
198 | default:
199 | return true;
200 | }
201 | }
202 | }
203 |
204 | class RmSegment: ByteableList
205 | {
206 | public float X { get; set; }
207 | public float Y { get; set; }
208 | public float Speed { get; set; }
209 | public float Tilt { get; set; }
210 | public float Width { get; set; }
211 | public float Pressure { get; set; }
212 |
213 | public override ByteableList CreateChild()
214 | {
215 | throw (new Exception("Segment has no children type"));
216 | }
217 |
218 | public override void FromStream( MemoryStream buffer, List layerNames)
219 | {
220 | byte[] segmentBytes = new byte[24];
221 | buffer.Read(segmentBytes, 0, segmentBytes.Length);
222 |
223 | X = BitConverter.ToSingle(segmentBytes, 0);
224 | Y = BitConverter.ToSingle(segmentBytes, 4);
225 | Speed = BitConverter.ToSingle(segmentBytes, 8);
226 | Tilt = BitConverter.ToSingle(segmentBytes, 12);
227 | Width = BitConverter.ToSingle(segmentBytes, 16);
228 | Pressure = BitConverter.ToSingle(segmentBytes, 20);
229 | }
230 |
231 | public override string ToString()
232 | {
233 | return $"Segment: X={X,-6: F1}, Y={Y,-6: F1}, Speed={Speed,-6: F1}, Tilt={Width,-6: F4}, Width={Width,-6: F4}, Pressure={Pressure,-6: F4}, nobjs={_objects.Count}";
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/RemarkableSync/document/v5/RmLines.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.MyScript;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.IO;
6 | using System.Runtime.Remoting.Messaging;
7 |
8 | // TODO: Exception handling
9 | namespace RemarkableSync.document.v5
10 | {
11 | class RmConstants
12 | {
13 | public static int X_MAX = 1404;
14 | public static int Y_MAX = 1872;
15 | }
16 |
17 | public abstract class ByteableList
18 | {
19 | protected List _objects;
20 |
21 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
22 |
23 | public ByteableList()
24 | {
25 | _objects = new List();
26 | }
27 |
28 | public abstract ByteableList CreateChild();
29 |
30 | public void Append(ByteableList child)
31 | {
32 | _objects.Add(child);
33 | }
34 |
35 | public void Log()
36 | {
37 | Logger.Debug(ToString());
38 | foreach (var child in _objects)
39 | {
40 | child.Log();
41 | }
42 | }
43 |
44 | public virtual void FromStream(TaggedBinaryReader reader)
45 | {
46 | try
47 | {
48 | int numChildren = reader.ReadInt32();
49 | for (int i = 0; i < numChildren; ++i)
50 | {
51 | ByteableList child = CreateChild();
52 | child.FromStream(reader);
53 | Append(child);
54 | }
55 |
56 | }catch (Exception ex)
57 | {
58 | throw ex;
59 | }
60 |
61 | }
62 |
63 | public override string ToString()
64 | {
65 | return $"Unimplemented base type";
66 | }
67 |
68 | public List Objects
69 | {
70 | get { return _objects; }
71 | }
72 | }
73 |
74 | public class RmPage : ByteableList, IRmPageBinary
75 | {
76 | public static RmPage ParseStream(TaggedBinaryReader reader)
77 | {
78 | RmPage page = new RmPage();
79 | page.FromStream(reader);
80 | return page;
81 | }
82 |
83 | public override ByteableList CreateChild()
84 | {
85 | return new RmLayer();
86 | }
87 |
88 | public override void FromStream(TaggedBinaryReader reader)
89 | {
90 | base.FromStream(reader);
91 | }
92 |
93 | public override string ToString()
94 | {
95 | return $"Lines: nobjs={_objects.Count}";
96 | }
97 |
98 | public Bitmap GetBitmap()
99 | {
100 | return RmLinesDrawer.DrawPage(this);
101 | }
102 |
103 | public Tuple GetMyScriptFormat()
104 | {
105 | int yOffset = 0;
106 |
107 | List strokes = new List();
108 | BoundingBox bound = new BoundingBox();
109 |
110 | foreach (RmLayer rmLayer in Objects)
111 | {
112 | foreach (RmStroke rmStroke in rmLayer.Objects)
113 | {
114 | if (!rmStroke.IsVisible())
115 | {
116 | continue;
117 | }
118 |
119 | Stroke stroke = new Stroke();
120 | int count = rmStroke.Objects.Count;
121 | List xList = new List(count);
122 | List yList = new List(count);
123 | int i = 0;
124 | foreach (RmSegment rmSegment in rmStroke.Objects)
125 | {
126 | int x = (int)Math.Round(rmSegment.X);
127 | int y = (int)Math.Round(rmSegment.Y) + yOffset;
128 | if ((i > 0) && (x == xList[i - 1]) && (y == yList[i - 1]))
129 | {
130 | continue;
131 | }
132 | xList.Add(x);
133 | yList.Add(y);
134 | bound.Expand(x, y);
135 | i++;
136 | }
137 | xList.TrimExcess();
138 | yList.TrimExcess();
139 | stroke.x = xList.ToArray();
140 | stroke.y = yList.ToArray();
141 | strokes.Add(stroke);
142 | }
143 | }
144 |
145 | StrokeGroup strokeGroup = new StrokeGroup();
146 | strokeGroup.strokes = strokes.ToArray();
147 |
148 | return Tuple.Create(strokeGroup, bound);
149 | }
150 | }
151 |
152 | class RmLayer : ByteableList
153 | {
154 | public override ByteableList CreateChild()
155 | {
156 | return new RmStroke();
157 | }
158 |
159 | public override void FromStream(TaggedBinaryReader reader)
160 | {
161 | base.FromStream(reader);
162 | }
163 |
164 | public override string ToString()
165 | {
166 | return $"Layer: nobjs={_objects.Count}";
167 | }
168 | }
169 |
170 | class RmStroke : ByteableList
171 | {
172 | public RmPen Pen { get; set; }
173 | public RmPenColor Colour { get; set; }
174 | public float Width { get; set; }
175 |
176 | public override ByteableList CreateChild()
177 | {
178 | return new RmSegment();
179 | }
180 |
181 | public override void FromStream(TaggedBinaryReader reader)
182 | {
183 | Pen = (RmPen)reader.ReadInt32();
184 | Colour = (RmPenColor)reader.ReadInt32();
185 | reader.BaseStream.Position += 4; //advance 4 bytes
186 | Width = reader.ReadSingle();
187 | reader.BaseStream.Position += 4; //advance 4 bytes
188 |
189 | base.FromStream(reader);
190 | }
191 |
192 | public override string ToString()
193 | {
194 | return $"Stroke: pen={Pen}, color={Colour}, width={Width,-5: F4}, nobjs={_objects.Count}";
195 | }
196 |
197 | public bool IsVisible()
198 | {
199 | switch (Pen)
200 | {
201 | case RmPen.ERASER:
202 | case RmPen.ERASER_AREA:
203 | case RmPen.ERASER_ALL:
204 | return false;
205 | default:
206 | return true;
207 | }
208 | }
209 | }
210 |
211 | class RmSegment : ByteableList
212 | {
213 | public float X { get; set; }
214 | public float Y { get; set; }
215 | public float Speed { get; set; }
216 | public float Tilt { get; set; }
217 | public float Width { get; set; }
218 | public float Pressure { get; set; }
219 | public override ByteableList CreateChild()
220 | {
221 | throw (new Exception("Segment has no children type"));
222 | }
223 |
224 | public override void FromStream(TaggedBinaryReader reader)
225 | {
226 | X = reader.ReadSingle();
227 | Y = reader.ReadSingle();
228 | Speed = reader.ReadSingle();
229 | Tilt = reader.ReadSingle();
230 | Width = reader.ReadSingle();
231 | Pressure = reader.ReadSingle();
232 | }
233 |
234 | public override string ToString()
235 | {
236 | return $"Segment: X={X,-6: F1}, Y={Y,-6: F1}, Speed={Speed,-6: F1}, Tilt={Width,-6: F4}, Width={Width,-6: F4}, Pressure={Pressure,-6: F4}, nobjs={_objects.Count}";
237 | }
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/RemarkableSync/document/Crdt.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace RemarkableSync.document
6 | {
7 | /**
8 | * Data structure representing CRDT sequence.
9 | */
10 | internal class CrdtSequenceItem
11 | {
12 | public CrdtId item_id;
13 | public CrdtId left_id;
14 | public CrdtId right_id;
15 | public int deleted_length;
16 | public T value;
17 | }
18 |
19 | /***
20 | * Ordered CRDT Sequence container.
21 | *
22 | * The Sequence contains `CrdtSequenceItem`s, each of which has an ID and
23 | * left/right IDs establishing a partial order.
24 | *
25 | * Iterating through the `CrdtSequence` yields IDs following this order.
26 | *
27 | */
28 | internal class CrdtSequence
29 | {
30 | //List> _items = new List>();
31 | private Dictionary> _items = new Dictionary>();
32 | public CrdtSequence() { }
33 |
34 | public CrdtSequence(IEnumerable> items = null)
35 | {
36 | if (items == null)
37 | {
38 | items = new List>();
39 | }
40 |
41 | _items = items.ToDictionary(item => item.item_id, item => item);
42 | }
43 |
44 | public bool Equals(CrdtSequence other)
45 | {
46 | if (other == null)
47 | {
48 | return false;
49 | }
50 |
51 | return _items.SequenceEqual(other._items);
52 | }
53 |
54 | public override bool Equals(object obj)
55 | {
56 | if (obj is CrdtSequence sequence)
57 | {
58 | return Equals(sequence);
59 | }
60 |
61 | if (obj is IEnumerable> items)
62 | {
63 | return Equals(new CrdtSequence(items));
64 | }
65 |
66 | return false;
67 | }
68 |
69 | public override int GetHashCode()
70 | {
71 | return _items.GetHashCode();
72 | }
73 |
74 | public override string ToString()
75 | {
76 | return $"CrdtSequence({_items.Values})";
77 | }
78 |
79 | public IEnumerable GetSequence()
80 | {
81 | return ToposortItems(_items.Values);
82 | }
83 |
84 | public List GetKeys()
85 | {
86 | return GetSequence().ToList();
87 | }
88 |
89 | public List GetValues()
90 | {
91 | return GetKeys().Select(key => _items[key].value).ToList();
92 | }
93 |
94 | public IEnumerable<(CrdtId, T)> GetItems()
95 | {
96 | return GetKeys().Select(key => (key, _items[key].value));
97 | }
98 |
99 | public T GetItem(CrdtId key)
100 | {
101 | if (_items.ContainsKey(key))
102 | {
103 | return _items[key].value;
104 | }
105 |
106 | throw new KeyNotFoundException();
107 | }
108 |
109 | public List> GetSequenceItems()
110 | {
111 | return _items.Values.ToList();
112 | }
113 |
114 | public void Add(CrdtSequenceItem item)
115 | {
116 | if (_items.ContainsKey(item.item_id))
117 | {
118 | throw new ArgumentException($"Already have item {item.item_id}");
119 | }
120 |
121 | _items[item.item_id] = item;
122 | }
123 |
124 | private IEnumerable ToposortItems(IEnumerable> items)
125 | {
126 | CrdtId END_MARKER = new CrdtId(0, 0);
127 | CrdtId LEFT_MARKER = new CrdtId(0, "__start".GetHashCode());
128 | CrdtId RIGHT_MARKER = new CrdtId(0, "__end".GetHashCode());
129 |
130 | var item_dict = items.ToDictionary(item => item.item_id);
131 | if (!item_dict.Any())
132 | {
133 | yield break; //nothing to do
134 | }
135 |
136 | CrdtId GetSideId(CrdtSequenceItem item, string side)
137 | {
138 | var side_id = (CrdtId)item.GetType().GetProperty($"{side}_id").GetValue(item);
139 | return side_id == END_MARKER ? (side == "left" ? LEFT_MARKER : RIGHT_MARKER) : side_id;
140 | }
141 |
142 |
143 | // build dictionary: key "comes after" values
144 | var data = new Dictionary>();
145 | foreach (CrdtSequenceItem item in item_dict.Values)
146 | {
147 | var left_id = GetSideId(item, "left");
148 | var right_id = GetSideId(item, "right");
149 | if (!data.ContainsKey(item.item_id))
150 | {
151 | data[item.item_id] = new HashSet();
152 | }
153 | data[item.item_id].Add(left_id);
154 | if (!data.ContainsKey(right_id))
155 | {
156 | data[right_id] = new HashSet();
157 | }
158 | data[right_id].Add(item.item_id);
159 | }
160 |
161 | // fill in sources not explicitly included
162 | /*var sourcesNotInData = data.Values.SelectMany(deps => deps).ToHashSet() - data.Keys.ToHashSet();*/
163 | HashSet sourcesNotInData = new HashSet();
164 | foreach (var deps in data.Values)
165 | {
166 | foreach (var dep in deps)
167 | {
168 | if (!data.ContainsKey(dep))
169 | {
170 | sourcesNotInData.Add(dep);
171 | }
172 | }
173 | }
174 | foreach (var source in sourcesNotInData)
175 | {
176 | data.Add(source, new HashSet());
177 | }
178 |
179 | while (true)
180 | {
181 | var next_items = new HashSet(data.Where(kv => kv.Value.Count == 0).Select(kv => kv.Key));
182 | if (next_items.Count == 1 && next_items.Contains(new CrdtId(0, "__end".GetHashCode())))
183 | {
184 | break;
185 | }
186 | if (next_items.Count == 0)
187 | {
188 | throw new ArgumentException("cyclic dependency");
189 | }
190 | foreach (var item_id in next_items.OrderBy(id => id))
191 | {
192 | if (item_dict.ContainsKey(item_id))
193 | {
194 | yield return item_id;
195 | }
196 | }
197 | data = data.Where(kv => !next_items.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
198 | foreach (var deps in data.Values)
199 | {
200 | deps.ExceptWith(next_items);
201 | }
202 | }
203 | }
204 |
205 | }
206 | public class CrdtId
207 | {
208 | public int part1;
209 | public int part2;
210 | public CrdtId(int part1, int part2)
211 | {
212 | this.part1 = part1;
213 | this.part2 = part2;
214 | }
215 | }
216 |
217 | /**
218 | * Container for a last-write-wins value.
219 | */
220 | public class LwwValue
221 | {
222 | public CrdtId timestamp;
223 | public T value;
224 |
225 | public LwwValue(CrdtId timestamp, T value)
226 | {
227 | this.timestamp = timestamp;
228 | this.value = value;
229 | }
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/RemarkableSync/document/RmDocument.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using RemarkableSync.MyScript;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Drawing;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text.Json;
9 |
10 | namespace RemarkableSync
11 | {
12 | /****
13 | * Rm document is split in multiple files containing info about the file
14 | * All starts in the root folder
15 | * Files have a UUID as filename
16 | * root\{FILE_UUID}.content
17 | * root\{FILE_UUID}.metadata
18 | * root\{FILE_UUID}.pagedata
19 | * root\{FILE_UUID}.thumbnails\{page_1_UUID}.png
20 | * root\{FILE_UUID}.thumbnails\{page_2_UUID}.png
21 | * root\{FILE_UUID}\{page_1_UUID}.rm
22 | * root\{FILE_UUID}\{page_1_UUID}-metadata.json
23 | * root\{FILE_UUID}\{page_2_UUID}.rm
24 | * root\{FILE_UUID}\{page_2_UUID}-metadata.json
25 | *
26 | * root\{FILE_UUID}.textconversion\{page_1_UUID}.json
27 | * root\{FILE_UUID}.highlights\
28 | * */
29 | public class RmDocument : IDisposable
30 | {
31 | protected string _root_path;
32 | protected string _id;
33 | protected DocumentContent _content;
34 | private Dictionary _pages = new Dictionary();
35 |
36 | protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
37 |
38 | internal class RmPageMetadata
39 | {
40 | public List layers { get; set; }
41 | }
42 | internal class RmPageMetadataLayer
43 | {
44 | public string name { get; set; }
45 | }
46 |
47 | public RmDocument(string id) : this(id, Path.Combine(Path.GetTempPath(), id)) { }
48 |
49 | public RmDocument(string id, string root_path)
50 | {
51 | _id = id;
52 | _root_path = root_path;
53 | }
54 |
55 | protected void LoadDocumentContent(string docContentJsonString = null)
56 | {
57 | try
58 | {
59 | if (docContentJsonString == null)
60 | {
61 | string filepath = GetDocumentContentFilePath();
62 | Logger.Debug($"Reading content file {filepath} for page mapping");
63 | if (!File.Exists(filepath))
64 | {
65 | throw new FileNotFoundException("DocumentContent file not found", filepath);
66 | }
67 | docContentJsonString = File.ReadAllText(filepath);
68 | }
69 | _content = DocumentContent.GetDocumentContentFromJson(docContentJsonString);
70 | }
71 | catch (Exception)
72 | {
73 | throw new Exception($"Unsupported format");
74 | }
75 | }
76 |
77 | public Bitmap GetPageAsImage(int pageNumber)
78 | {
79 | return GetPageAsBinary(pageNumber).GetBitmap();
80 | }
81 |
82 | public List GetPagesAsImage()
83 | {
84 | List images = new List();
85 | for (int i = 0; i < PageCount; i++)
86 | {
87 | images.Add(GetPageAsImage(i));
88 | }
89 |
90 | return images;
91 | }
92 |
93 | public HwrRequestBundle GetPageAsMyScriptHwrRequestBundle(int pageNumber, string language)
94 | {
95 | HwrRequest request = new HwrRequest();
96 |
97 | request.xDPI = request.yDPI = 226;
98 | request.contentType = "Text";
99 | request.configuration = new Configuration() { lang = language };
100 | request.strokeGroups = new StrokeGroup[1];
101 |
102 | var strokeGroupBundle = GetPageAsBinary(pageNumber).GetMyScriptFormat();
103 | request.strokeGroups[0] = strokeGroupBundle.Item1;
104 |
105 | return new HwrRequestBundle()
106 | {
107 | Request = request,
108 | Bounds = new List() { strokeGroupBundle.Item2 }
109 | };
110 | }
111 |
112 | private PageBinary GetPageAsBinary(int pageNumber)
113 | {
114 | if(_pages.ContainsKey(pageNumber)) { return _pages[pageNumber]; }
115 |
116 | if (_root_path == "" || _content == null)
117 | {
118 | string error = $"Document content not available.";
119 | Logger.Error(error);
120 | throw new Exception(error);
121 | }
122 |
123 | if (pageNumber >= _content.pageCount)
124 | {
125 | string error = $"unexpected page number {pageNumber} for pageCount {_content.pageCount}";
126 | Logger.Error(error);
127 | throw new Exception(error);
128 | }
129 |
130 | try
131 | {
132 | string binPath = GetPageBinaryFilePath(pageNumber);
133 | _pages.Add(pageNumber, new PageBinary(binPath));
134 | return _pages[pageNumber];
135 | }
136 | catch (Exception err)
137 | {
138 | string error = $"Parsing page content to Page object failed with err: {err.Message}";
139 | Logger.Error(error);
140 | throw new Exception(error);
141 | }
142 | }
143 |
144 | public void Dispose()
145 | {
146 | if (_root_path.Length > 0)
147 | {
148 | try
149 | {
150 | Directory.Delete(_root_path, true);
151 | }
152 | catch (Exception err)
153 | {
154 | Logger.Error($"failed to remove folder: {_root_path}. Error: {err.Message}");
155 | }
156 |
157 | }
158 | }
159 |
160 | public List Pages
161 | {
162 | get { return _content.getPages(); }
163 | }
164 |
165 | public int PageCount
166 | {
167 | get { return _content.pageCount; }
168 | }
169 |
170 | protected string GetDocumentContentFilePath()
171 | {
172 | return Path.Combine(_root_path, $"{_id}.content");
173 | }
174 |
175 | protected string GetPageBinaryFilePath(int pageNumber)
176 | {
177 | string pageFilename = GetPageUUID(pageNumber);
178 | return Path.Combine(_root_path, _id, $"{pageFilename}.rm");
179 | }
180 |
181 | protected string GetPageMetadataFilePath(int pageNumber)
182 | {
183 | string pageFilename = GetPageUUID(pageNumber);
184 | return Path.Combine(_root_path, _id, $"{pageFilename}-metadata.json");
185 | }
186 |
187 | protected string GetDocumentFolderPath()
188 | {
189 | return Path.Combine(_root_path, _id);
190 | }
191 |
192 | protected string GetPageUUID(int pageNumber)
193 | {
194 | return _content.getPages().ElementAt(pageNumber);
195 | }
196 |
197 | protected List GetPageLayerNames(int pageNumber)
198 | {
199 | var layerNames = new List();
200 | var metadataJsonString = File.ReadAllText(GetPageMetadataFilePath(pageNumber));
201 | try
202 | {
203 | RmPageMetadata metadata = JsonSerializer.Deserialize(metadataJsonString);
204 | layerNames = (from layer in metadata.layers select layer.name).ToList();
205 | }
206 | catch (Exception err)
207 | {
208 | Logger.Error($"RmPageMetadata json deseralizing failed with: {err.Message}.\n Content:\n{metadataJsonString}");
209 | }
210 | return layerNames;
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/RemarkableSync/RemarkableSync.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {090B923F-25A0-4204-846C-71C0C7F50743}
8 | Library
9 | RemarkableSync
10 | RemarkableSync
11 | v4.8
12 | 512
13 | true
14 | true
15 |
16 |
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 | true
39 | ..\..\..\Program Files\reMarkableSync\OneNoteAddin\
40 | DEBUG;TRACE
41 | full
42 | x64
43 | 7.3
44 | prompt
45 | true
46 |
47 |
48 | bin\x64\Release\
49 | TRACE
50 | true
51 | pdbonly
52 | x64
53 | 7.3
54 | prompt
55 | true
56 |
57 |
58 |
59 |
60 |
61 | true
62 | bin\x86\Debug\
63 | DEBUG;TRACE
64 | full
65 | x86
66 | 7.3
67 | prompt
68 |
69 |
70 | bin\x86\Release\
71 | TRACE
72 | true
73 | pdbonly
74 | x86
75 | 7.3
76 | prompt
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | 5.1.3
145 |
146 |
147 | 2020.0.2
148 |
149 |
150 | 6.22.0
151 |
152 |
153 | 4.3.0
154 |
155 |
156 | 4.3.0
157 |
158 |
159 | 6.0.0
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/RemarkableSync/MyScript/MyScriptClient.cs:
--------------------------------------------------------------------------------
1 | using RemarkableSync.document;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Net.Http;
6 | using System.Security.Cryptography;
7 | using System.Text;
8 | using System.Text.Json;
9 | using System.Threading.Tasks;
10 |
11 | namespace RemarkableSync.MyScript
12 | {
13 | public class MyScriptClient
14 | {
15 | static readonly string Url = "https://cloud.myscript.com/api/v4.0/iink/batch";
16 | static readonly string JiixContentType = "application/vnd.myscript.jiix,application/json";
17 |
18 | private HttpClient _client;
19 |
20 | private static string AppKeyName = "appkey";
21 | private static string HmacKeyName = "hmackey";
22 | private static string EmptyKey = "****";
23 |
24 | private string _appKey;
25 | private string _hmacKey;
26 | private IConfigStore _configStore;
27 | private bool _saveHwrData = false;
28 |
29 | private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
30 |
31 | public MyScriptClient(IConfigStore configStore)
32 | {
33 | #if DEBUG
34 | _saveHwrData = true;
35 | #endif
36 |
37 | _appKey = "";
38 | _hmacKey = "";
39 | _configStore = configStore;
40 | _client = new HttpClient();
41 | LoadConfig();
42 | }
43 |
44 | public async Task> RequestHwr(RmDocument doc, int pageIndex, string language)
45 | {
46 | Tuple resultTuple = Tuple.Create(pageIndex, null);
47 |
48 | if (_appKey == "" || _hmacKey == "")
49 | {
50 | Logger.Debug("Unable to send request due to appkey or hmac kay being empty");
51 | return resultTuple;
52 | }
53 |
54 | string responseContentString = "";
55 | HwrRequestBundle requestBundle = doc.GetPageAsMyScriptHwrRequestBundle(pageIndex, language);
56 |
57 | try
58 | {
59 | bool _testing = false;
60 | string outFile = Path.Combine(Path.GetTempPath(), $"HwrResponse_{pageIndex}.json");
61 | if (_testing && File.Exists(outFile))
62 | {
63 | Logger.Debug("IN TEST MODE NOT REQUESTING MyScript data, but reading from disk");
64 | responseContentString = File.ReadAllText(outFile);
65 | }
66 | else
67 | {
68 | string reqString = JsonSerializer.Serialize(requestBundle.Request);
69 | if (_saveHwrData)
70 | {
71 | File.WriteAllText(Path.Combine(Path.GetTempPath(), "HwrRequest.json"), reqString);
72 | }
73 | byte[] requestContent = Encoding.Unicode.GetBytes(reqString);
74 |
75 | HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, Url);
76 | requestMessage.Content = new ByteArrayContent(requestContent);
77 | requestMessage.Content.Headers.Add("Content-Type", "application/json");
78 | requestMessage.Headers.Add("Accept", JiixContentType);
79 | requestMessage.Headers.Add("applicationKey", _appKey);
80 |
81 | using (HMACSHA512 hmac = new HMACSHA512(Encoding.ASCII.GetBytes(_appKey + _hmacKey)))
82 | {
83 | byte[] hash = hmac.ComputeHash(requestContent);
84 | StringBuilder sBuilder = new StringBuilder();
85 |
86 | for (int i = 0; i < hash.Length; i++)
87 | sBuilder.Append(hash[i].ToString("x2"));
88 |
89 | requestMessage.Headers.Add("hmac", sBuilder.ToString());
90 | }
91 |
92 | HttpResponseMessage response = await _client.SendAsync(requestMessage);
93 | if (!response.IsSuccessStatusCode)
94 | {
95 | Logger.Debug($"Request was not successful. Return status: {response.StatusCode}, {response.ReasonPhrase}");
96 | return resultTuple;
97 | }
98 |
99 | responseContentString = await response.Content.ReadAsStringAsync();
100 | }
101 |
102 | }
103 | catch (Exception err)
104 | {
105 | Logger.Error($"HWR request exception: {err.Message}.\n {err.StackTrace}");
106 | return resultTuple;
107 | }
108 |
109 | try
110 | {
111 | if (_saveHwrData)
112 | {
113 | File.WriteAllText(Path.Combine(Path.GetTempPath(), $"HwrResponse_{pageIndex}.json"), responseContentString);
114 | }
115 | MyScriptResult result = JsonSerializer.Deserialize(responseContentString);
116 | return Tuple.Create(pageIndex, result.label);
117 | }
118 | catch (Exception err)
119 | {
120 | Logger.Error($"MyScriptResult json deseralizing failed with: {err.Message}.\n Content:\n{responseContentString}");
121 | return resultTuple;
122 | }
123 | }
124 |
125 | public void SetConfig(string appKey, string hmacKey)
126 | {
127 | _appKey = appKey;
128 | _hmacKey = hmacKey;
129 | WriteConfig();
130 | }
131 |
132 | private void LoadConfig()
133 | {
134 | string appKey = _configStore.GetConfig(AppKeyName);
135 | string hmacKey = _configStore.GetConfig(HmacKeyName);
136 | _appKey = appKey == EmptyKey ? "" : appKey;
137 | _hmacKey = hmacKey == EmptyKey ? "" : hmacKey;
138 | }
139 |
140 | private void WriteConfig()
141 | {
142 | Dictionary mapConfigs = new Dictionary();
143 | mapConfigs[AppKeyName] = _appKey?.Length > 0 ? _appKey : EmptyKey;
144 | mapConfigs[HmacKeyName] = _hmacKey?.Length > 0 ? _hmacKey : EmptyKey;
145 | _configStore.SetConfigs(mapConfigs);
146 | }
147 |
148 | private List ParseResult(HwrRequestBundle requestBundle, MyScriptResult result)
149 | {
150 | List resultList = new List();
151 | var groupBounds = requestBundle.Bounds;
152 | Queue wordQueue = new Queue(result.words);
153 | foreach (var bound in groupBounds)
154 | {
155 | StringBuilder sb = new StringBuilder();
156 | while (true)
157 | {
158 | if (wordQueue.Count == 0)
159 | {
160 | break;
161 | }
162 |
163 | var currWord = wordQueue.Peek();
164 |
165 | // always append whitespace to current group
166 | if (currWord.boundingbox is null)
167 | {
168 | sb.Append(wordQueue.Dequeue().label);
169 | continue;
170 | }
171 |
172 | if (bound.Contains(currWord.boundingbox))
173 | {
174 | sb.Append(wordQueue.Dequeue().label);
175 | continue;
176 | }
177 | else
178 | {
179 | break;
180 | }
181 | }
182 |
183 | resultList.Add(sb.ToString());
184 | }
185 |
186 | for (int i = 0; i < resultList.Count; ++i)
187 | {
188 | Logger.Debug($"MyScriptClient::ParseResult() - result item {i}: {resultList[i]}");
189 | }
190 |
191 | return resultList;
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/ComClient/ComClient.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | 16.0
23 | Win32Proj
24 | {dd68e543-e03a-46be-b48e-32349ffa5c9d}
25 | ComClient
26 | 10.0
27 |
28 |
29 |
30 | Application
31 | true
32 | v142
33 | Unicode
34 |
35 |
36 | Application
37 | false
38 | v142
39 | true
40 | Unicode
41 |
42 |
43 | Application
44 | true
45 | v142
46 | Unicode
47 |
48 |
49 | Application
50 | false
51 | v142
52 | true
53 | Unicode
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | true
75 |
76 |
77 | false
78 |
79 |
80 | true
81 |
82 |
83 | false
84 |
85 |
86 |
87 | Level3
88 | true
89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
90 | true
91 |
92 |
93 | Console
94 | true
95 |
96 |
97 | xcopy $(ProjectDir)..\OnenoteAddin\OnenoteAddin.tlb $(ProjectDir) \RY
98 |
99 |
100 |
101 |
102 | Level3
103 | true
104 | true
105 | true
106 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
107 | true
108 |
109 |
110 | Console
111 | true
112 | true
113 | true
114 |
115 |
116 | xcopy $(ProjectDir)..\OnenoteAddin\OnenoteAddin.tlb $(ProjectDir) \RY
117 |
118 |
119 |
120 |
121 | Level3
122 | true
123 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
124 | true
125 |
126 |
127 | Console
128 | true
129 |
130 |
131 | xcopy $(ProjectDir)..\OnenoteAddin\OnenoteAddin.tlb $(ProjectDir) \RY
132 |
133 |
134 |
135 |
136 | Level3
137 | true
138 | true
139 | true
140 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
141 | true
142 |
143 |
144 | Console
145 | true
146 | true
147 | true
148 |
149 |
150 | xcopy $(ProjectDir)..\OnenoteAddin\OnenoteAddin.tlb $(ProjectDir) \RY
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/RemarkableSync/document/TaggedBinaryReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 |
5 | namespace RemarkableSync.document
6 | {
7 | public class TaggedBinaryReader : BinaryReader
8 | {
9 | //Tag type representing the type of following data.
10 | public enum TagType : ushort
11 | {
12 | ID = 0xF,
13 | Length4 = 0xC,
14 | Byte8 = 0x8,
15 | Byte4 = 0x4,
16 | Byte1 = 0x1
17 | }
18 | public TaggedBinaryReader(Stream input) : base(input)
19 | {
20 | }
21 |
22 | public TaggedBinaryReader(Stream input, Encoding encoding) : base(input, encoding)
23 | {
24 | }
25 |
26 | public TaggedBinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen)
27 | {
28 | }
29 |
30 | public int ReadVarUint()
31 | {
32 | int shift = 0;
33 | int result = 0;
34 |
35 | while (true)
36 | {
37 | int i = Convert.ToInt32(ReadByte());
38 | result |= (i & 0x7F) << shift;
39 | shift += 7;
40 | if ((i & 0x80) == 0)
41 | {
42 | break;
43 | }
44 | }
45 |
46 | return result;
47 | }
48 |
49 | public byte[] ReadSubBlock(int index)
50 | {
51 | ReadTag(index, TagType.Length4);
52 | int subblockLength = (int)ReadUInt32();
53 |
54 | return ReadBytes(subblockLength);
55 | }
56 |
57 | public TaggedBinaryReader GetSubBlockAsBinaryReader(int index)
58 | {
59 | byte[] subblock = ReadSubBlock(index);
60 | return new TaggedBinaryReader(new MemoryStream(subblock));
61 | }
62 |
63 | public CrdtId ReadTaggedId(int index)
64 | {
65 | ReadTag(index, TagType.ID);
66 |
67 | return ReadId();
68 | }
69 |
70 | public CrdtId ReadId()
71 | {
72 | int part1 = (int)ReadByte();
73 | int part2 = ReadVarUint();
74 |
75 |
76 | return new CrdtId(part1, part2);
77 | }
78 |
79 | public (int, TagType) ReadTag(int index, TagType expected_type)
80 | {
81 | int x = ReadVarUint();
82 |
83 | // First part is an index number that identifies if this is the right
84 | // data we're expecting
85 | var tagIndex = x >> 4;
86 |
87 | // Second part is a tag type that identifies what kind of data it is
88 | var tagType = (TagType)(x & 0xF);
89 |
90 |
91 | if (tagIndex != index)
92 | {
93 | throw new Exception("Unexpected block index");
94 | }
95 | if (tagType != expected_type)
96 | {
97 | throw new Exception("Unexpected subblock tag type");
98 | }
99 |
100 | return (tagIndex, tagType);
101 | }
102 |
103 | public bool HasSubblock(int index)
104 | {
105 | return CheckTag(index, TagType.Length4);
106 | }
107 |
108 | /**
109 | * Check that INDEX and TAG_TYPE are next.
110 | *
111 | * Returns True if the expected index and tag type are found. Does not
112 | * advance the stream.
113 | */
114 | public bool CheckTag(int index, TagType expected_type)
115 | {
116 | long pos = BaseStream.Position;
117 | try
118 | {
119 | ReadTag(index, expected_type);
120 |
121 | //throws error when not expected
122 | return true;
123 | }catch (Exception e)
124 | {
125 | return false;
126 | }
127 | finally {
128 | BaseStream.Position = pos;
129 | }
130 | }
131 |
132 |
133 |
134 | public int ReadTaggedUInt32(int index)
135 | {
136 | ReadTag(index, TagType.Byte4);
137 | return (int)ReadUInt32();
138 | }
139 | public bool ReadTaggedBool(int index)
140 | {
141 | ReadTag(index, TagType.Byte1);
142 | return ReadBoolean();
143 | }
144 |
145 | public byte ReadTaggedByte(int index)
146 | {
147 | ReadTag(index, TagType.Byte1);
148 | return ReadByte();
149 | }
150 |
151 | public float ReadTaggedSingle(int index)
152 | {
153 | ReadTag(index, TagType.Byte4);
154 | return ReadSingle();
155 | }
156 |
157 | public double ReadTaggedDouble(int index)
158 | {
159 | ReadTag(index, TagType.Byte8);
160 | return ReadDouble();
161 | }
162 |
163 | public String ReadString(int index)
164 | {
165 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
166 | {
167 | int length = subBlock.ReadVarUint();
168 | bool is_ascii = subBlock.ReadBoolean();
169 |
170 | return new String(subBlock.ReadChars(length));
171 | }
172 | }
173 |
174 | public (String, int) ReadStringWithFormat(int index)
175 | {
176 | using(TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index)){
177 | int length = subBlock.ReadVarUint();
178 | bool is_ascii = subBlock.ReadBoolean();
179 |
180 | String s = new String(subBlock.ReadChars(length));
181 | int format = -1;
182 | try
183 | {
184 | format = subBlock.ReadTaggedUInt32(2);
185 | }catch (Exception e){
186 |
187 | }
188 |
189 | return (s, format);
190 | }
191 | }
192 |
193 | public LwwValue ReadLwwBool(int index)
194 | {
195 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
196 | {
197 | return new LwwValue(
198 | subBlock.ReadTaggedId(1),
199 | subBlock.ReadTaggedBool(2)
200 | );
201 | }
202 | }
203 |
204 | public LwwValue ReadLwwByte(int index)
205 | {
206 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
207 | {
208 | return new LwwValue(
209 | subBlock.ReadTaggedId(1),
210 | subBlock.ReadTaggedByte(2)
211 | );
212 | }
213 | }
214 |
215 | public LwwValue ReadLwwInt(int index)
216 | {
217 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
218 | {
219 | return new LwwValue(
220 | subBlock.ReadTaggedId(1),
221 | subBlock.ReadTaggedByte(2)
222 | );
223 | }
224 | }
225 |
226 | public LwwValue ReadLwwFloat(int index)
227 | {
228 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
229 | {
230 | return new LwwValue(
231 | subBlock.ReadTaggedId(1),
232 | subBlock.ReadTaggedSingle(2)
233 | );
234 | }
235 | }
236 | public LwwValue ReadLwwDouble(int index)
237 | {
238 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
239 | {
240 | return new LwwValue(
241 | subBlock.ReadTaggedId(1),
242 | subBlock.ReadTaggedDouble(2)
243 | );
244 | }
245 | }
246 |
247 | public LwwValue ReadLwwId(int index)
248 | {
249 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
250 | {
251 | return new LwwValue(
252 | subBlock.ReadTaggedId(1),
253 | subBlock.ReadTaggedId(2)
254 | );
255 | }
256 | }
257 | public LwwValue ReadLwwString(int index)
258 | {
259 | using (TaggedBinaryReader subBlock = GetSubBlockAsBinaryReader(index))
260 | {
261 | return new LwwValue(
262 | subBlock.ReadTaggedId(1),
263 | subBlock.ReadString(2)
264 | );
265 | }
266 | }
267 | }
268 | }
269 |
--------------------------------------------------------------------------------