まだ「概ね」で いろいろ問題はあるけど、以下のように、僕が欲しかった機能はすべて実現した。

  • 指定した期間内または前回のバックアップ後に更新されたノートを指定ディレクトリにバックアップ(ダウンロードと保存)する。
  • ノートの本体(テキスト)だけでなく、添付された画像など(リソース)もバックアップする。
    • 純正Evernoteアプリの全ノートのエクスポートと違い、ノートごとに分けてバックアップする。また、本体と各リソースも別のファイルにしている。(その方が扱いやすいと考えたため。)
  • ノートの最新版がバックアップ済みの場合は、ダウンロードしない。
  • バックアップしたノートは、webブラウザでプレビュー可能。
    • トップページにはノート一覧(ノートのタイトル、更新日時、ノートブック、GUID)が表示され、タイトルをクリックするとプレビューされる。プレビューは、Web版Evernoteにまあまあ近づけた(というか、見栄えはまったく凝ってない(テキトーにやったらそれなりに見えた)。: 例に挙げたノートも目をひかないw)。

(5/23 20:19) その後、プレビュープログラムを改良し、トップページは「(バックアップした)Evernoteのブラウザ」のようになって来た。以下のような機能を追加したほか、見栄えを少し良くした。やっぱり、PHPだと開発効率が10倍はいい感じだし、やる気も出るw

    • フィルタリング機能: 更新年月、作成年月、バックアップ年月、ノートブック名で表示するノートを絞り込める。また、左の各フィールドに対して正規表現でフィルタリングできる。
    • ソート機能: タイトル、更新日時、作成日時、バックアップ日時、ノートブック名でソートできる。ソートの昇順・降順は、フィールド名を押すことで反転する。ソートに使っているフィールドは太字になり、ソート順は▲(昇順)/▼(降順)で示される。
    • 編集補助機能: トップページまたはプレビューページのノート名横の"Edit"を押すと、web版Evernoteでノートの編集ができる。 (あとから思い付いて追加したので、キャプチャ画像にはない。)

改良したトップページ (ノートブック名でフィルタリングした状態)

これくらいできると、僕には、ノートを探すことなどに関してはweb版Evernoteより使いやすい。Web版Evernoteはマウスの加減で画面がちょろちょろ動くのが鬱陶しいし、ノートが多いと下の方がなかなか出て来なくて(しかも、下を出すと上を出すのが遅い・・・)不便なのだ。技術的には高度だろうけど、本当に使いやすいのかと思う。

構想や調査・準備(・他のこと)に時間が掛かったが、ここまでの実装は5日くらいでできた(または、5日も掛かった)。気付くと寝食が犠牲になっていて、なかなかハードだったw

問題や不満な点や残件はいろいろある。

  • 多くのノートをバックアップしようとすると、Evernote APIのrate limit(レート制限: APIを使い過ぎるとエラーになってしまう)に掛かって、しばらく(例: 30分-1時間)使えなくなる。一旦制限に掛かると、制限期間が終わってもすぐに駄目になることが多く、かなり間隔を空ける必要がありそう。(← プログラムのバグのため、無限にダウンロードしていたせいだった。) → 僕の全ノートのバックアップは時間的に困難かも知れない。 → (5/22 16:02) 調整やデバッグをしたら調子が良くなって、1年分を1時間くらい(レート制限解除の待ち時間を含む。正味の時間はもっと短い)でダウンロードできるようになった。 → (5/22 19:14) 無事、全ノート(2011年から今年まで)がバックアップできた。なお、(多過ぎて)過不足や内容の確認はできない模様w (バックアップのサイズ: 562MB, ノート数: 725)
    • あるアプリを初めて使う場合は、使い始めてから24時間は無制限("Initial Sync Boost")なのだが、当然ながら開発中に終わってしまった。。。
    • 開発用認証キーを取得すると、それで「最初の24時間」が使えて良さそうだが、今は配布が停止中。
      • 開発用キーとAPIのキーは別なので、後者を取得すればいいようだが、ちょっと躊躇している。
    • 他のサービスのように、APIの推奨実行間隔や制限に掛かる条件を明記してくれたり、APIの戻り値にレート制限に掛かりそうな情報(例: 「あとどのくらいで制限になる」)を出してくれるとありがたいのだが、そうではないようだ。その点ではSpotifyはまともだ。
  • そもそも、Evernote APIは無駄に凝っている。複数プログラミング言語対応などを目指したのか、Thriftとかいうのを使っているが、複雑で面倒でどうしようもない。センスが悪い。最高でなくていいので、もっと「普通」の、楽なやり方にすればいいのに。この点でもSpotifyは随分良かった。
  • Evernote APIでは、ノートのリソース(画像など)だけの追加や置き換えができず、そうしたい時には、ノート全体(すべてのリソースを含む)を再びアップロードし直すことになるようで、スマフォでは大きいノートの編集が重くなるのも分かる気がした。通信データ量も増えてしまうから、とてもイマイチな仕様だ。 (5/25 16:40)
  • バックアップ期間の日時指定がUTCなので、今一つ不便。(単に変換をサボっているだけ)
  • 元のプログラム(clinote)の内部構造が大変イマイチで、すごく保守性が悪い。
    • 同じ名前や似た名前のファイルが違うディレクトリに何個もあって、今自分がどれを見ているのか分からなくなる。
    • 関数名や型の名前も同様に同名や似たような名前のものが多く、それらの間で頻繁に行き来するので、何がどこにあるのか把握できない。その割には、かなり細かいこともやっていて、本人が間違えないのが不思議。逆に、大雑把なところも多く、バランスが悪い気がする。
    • 作者は僕とは違う感性のようだ。GMBもちょっと似た雰囲気だった。USの人とは違う気がするので、どちらもヨーロッパの人?
    • することは大体分かったので、いつかはPHPで0から書き直したい! (いつになることやら・・・)
  • GO言語もイマイチ。
    • やっぱり、コンパイラ・静的な言語は不便だ。
      • 例えば、変数が静的なので、配列への要素の追加・削除が普通にできない。昔のCでの苦労を思い出す・・・ そういう処理をしようとして、「あ・・・」と気が重くなって、手抜きをする。
      • 変数の型にも厳格で、実体が同じだってエラーにするからいちいち面倒だ。
    • 変態的かつ不便なだけで、全然メリットが感じられない。
      • ;だの{}だの()を省けるようにしたって、そういうのは本質じゃないから全然良くない。逆に、様式が多いのは戸惑いを生じるから面倒だ。
      • 日時のフォーマットの指定方法なんて、アフォとしか思えない。これがGoogleの乗り?
      • あと、今時ポインタなんて要らないんだよ、クソジジイ!!!
        • ↑ 変数は「参照している間はなくならない」そうで、Cでのスタックとヒープの違いがないとのこと。だから、関数内のローカル変数のアドレスを呼び側に渡しても何も問題ないそうで、すごく気持ち悪い。その点でも、やっぱりポインタやアドレスをなくせば良かったのにと思う。 (5/25 16:40)
      • 重複した機能のライブラリがあって(例: osとioutilのファイルアクセス)、新しい言語なのに既にごちゃついている感じ。
    • この点でも、いつかはPHPで書き直したい!
  • まだリストア(アップロード)機能を作っていないので、本当にリストアできるかが不明(プレビューできているので、おそらく大丈夫とは思うが・・・)。最悪でも、本文とリソースはバックアップできているから何とかなるはず。
    • → (5/25 16:40) とりあえずできた。バックアッププログラムを作る時には気付かなかったのだが、ノートをアップロードする時にはリソースのタイプ(MIME)やハッシュ値(MD5)や、画像の場合にはサイズ(幅・高さ)も要るので、面倒だったがGoのライブラリで認識・生成した。
    • 今回も、Goの変態さに気付いてしまった(上に書いた)。

次に、細かい機能や実装などについてのメモを以下に書く。

  • コマンドライン版のEvernoteクライアントclinoteを改造した本体と、それを呼ぶシェルスクリプト(en_backup.sh)、バックアップしたノートのプレビューを行うCGIプログラム(enb_viewer.php)で構成した。
  • clinoteの改造概要
    • ノート一覧をCSV形式で出せるようにした。CSV形式の他に、元々の形式で出る余計な区切り(線)を出さないようにすることができるようにした。
      • 元々の表示は昔のDOSやPC98のプログラムみたいで、使い勝手も趣味もとても悪い。
    • ノート本体だけでなくリソースもダウンロード・保存できるようにした。保存時にノートを変更せずに、Evernoteからダウンロードしたままの形式(ENML形式)で保存することもできるようにした。
    • ノート情報(タイトル、更新日時、作成日時、ノートブック、GUID)を情報ファイルに記録するようにした。
      • バックアップ時に、これに記録された更新日時とEvernoteから取得した更新日時を比較して、再度のダウンロードや上書きをしないようにしている。
      • プレビュー時も、この情報を使ってタイトルなどを表示している。
    • リソースのファイル名はリソースのハッシュ値(Evernoteが計算してノート本体から参照される値)にしたので、変更が簡単に検出できる(はず)。
      • (5/22 13:25) その後、ノート内の各リソースが更新されたかを調べると、リソース(例: 画像)数の多いノートでAPIの実行数が増えてレート制限が起こりやすくなることが分かった。Evernoteはデータ転送量よりAPIの実行の方が重い(コストが高い)と考えているようだ。それも分かるが、何となく釈然としなかった(僕は転送量を減らそうとして失敗した)。 → 結局、ノートが更新されていたら、各リソースの更新はチェックせず、ノートと一緒に全部ダウンロードするようにした。
        • APIの実行数を最小にするという点では、ノート本体の更新チェックもせず、とにかく全部ダウンロードするのがいいが、さすがにかなり遅くなるので止めた。
    • ノート(本体、リソース、ノート情報ファイル)はノート本体のGUIDの名前を持つディレクトリに保存するようにした。
    • APIレート制限の状態を簡単に知ることができるように、同期状態(GetSyncState)を表示するサブコマンド(sync-state)を追加した。
      • clinoteは、レート制限エラーが起こっても他と同様のエラーにするだけで、制限が解除されるまで待つべき時間(rateLimitDuration)が分からない。
      • 一方、レート制限エラーはさまざまなところ(API呼び出し)で起こり得るので、そのすべてに表示処理を追加することは容易でない。GOの機能でできるのかも知れないが、慣れていないのとプログラムが複雑で、僕には無理だった。
      • そのため、追加したsync-stateサブコマンドではレート制限エラーでの待つべき時間をエラーメッセージに表示するようにした。そして、プログラム実行時にレート制限エラーが起こったあとでsync-stateを実行すれば、そこでもレート制限エラーが起こるはずなので、そのエラーメッセージからレート制限が解除されるまでに待つべき時間を抽出できるようにした。
    • バックアップしたノートのリストア用に、clinoteでノートを新規作成するサブコマンド(note new)にノートのアップロードを行うオプションを追加した。 (5/25 16:40)
      • そのオプションに、アップロードするノートとリソースファイルの格納されたディレクトリを指定する。
      • リソースのアップロード時に、タイプ(MIME)やハッシュ値(MD5)や、画像の場合にはサイズ(幅・高さ)が要るので、リソースファイルから生成するようにした。
      • ノートのタイトルを指定しない場合には、ノートのディレクトリに(バックアップ時に作成された)ノート情報ファイルがあれば、そこに記録されたタイトルが使用される。
      • ただし、既存のノート(同名で存在する可能性がある)と容易に区別できるように、指定しない限り、アップロード専用のノートブックに入れ、タイトルにアップロードしたという識別記号を前置するようにした。
  • バックアップ実行用スクリプト(en_backup.sh)の概要
    • バックアップ対象期間、保存ディレクトリ、APIレート制限時の自動リトライなどが指定可能
      • うまい方法がなかったので、レート制限エラーかどうかとレート制限が解除されるまでの時間は、前述のようにログ(clinoteの出力)から抽出している。
    • 最後にバックアップした日時をファイルに保存しておき、次回以降にバックアップ対象期間を指定しない場合には、前回以降に更新されたノートをバックアップすることが可能。 → crontabに書いておいて、定期的に自動バックアップすることも簡単にできる。
    • バックアップ期間の指定は検索条件(例: "updated:20200501T000000Z -updated:20200520T000000Z")としてclinoteに渡し、Evernote APIで行っている。
  • Webプレビュープログラム(enb_viewer.php)の概要
    • バックアップしたノートをHTMLに変換して表示する。
      • 変換といっても、リソース(画像など)が表示できるようにタグを置換している程度。以下に主な規則を書く。
        • プレビューページの先頭に<!DOCTYPE html><html>を入れる。
        • ENML → HTML簡易変換 (PHPの正規表現(PREG))
          • <!DOCTYPE(\s)en-note\s[^>]+> → 削除
          • <(/)?en-note> → 削除
          • <en-media\s+'<img '
          • hash="([^"]+)"'src="'.$img_dir.'$1"' (注: $img_dirはノートのディレクトリ)
        • Evernote内リンクの変換
          • "https://www.evernote.com/shard/([^/]+/){3}([^/^\?]+)(\?[^"]+)?" + → (そのノートのGUID($2)ををプレビュープログラムに指定して表示させる)
          • evernote:///view/[^/]+/[^/]+/([^/]+)(/[^"]+)? → (そのノートのGUID($1)をプレビュープログラムに指定して表示させる): これはNixnote2独自のものかも知れない。
        • プレビューページの最後に</html>を入れる。
    • まだCSSなどは使用していないので(多分、今後もやらない)、とりあえず見える程度のもの。

最後にその他の情報などを書く。

  • Web版Evernoteでノートを変更してから数分(2-3分)経たないと、APIにその更新が反映されない。
    • ノートをバックアップする時、編集中のものをバックアップするのを防ぐため、更新後少し経ったものを対象にしようと思って居たので、この仕様は偶然にも都合がいい。
  • これを作っていて、今までに試したEverenote互換アプリで、最初の同期が終わらず、全然使い始められないないものがあった理由が分かった。ノートが多過ぎて、最初の同期中にレート制限が掛かり、待ちと制限の繰り返しになっていたのではないか。
    • それならそのように表示すればいいと思うが、なぜかそういうアプリは少ない。出てもすぐに消えてしまったり、意味不明なエラーを出して、今どうなっているのかが分からないものが多い。
  • 想像だが、NixNote2は余計なことをし過ぎていて(どういう訳か、Evernoteの普通のやり方から外れて独自のことをしている感じ。Evernoteのクライアントなのに・・・)、ノートが編集できなくなったり、同期が失敗したり、スマフォで開く時にノートが重くなったり、フォーマットが乱れたり(純正アプリと違うことが多い)するのではないかと思う。 ← 今読み返すと、全く使い物にならない。
  • バックアップしたノートのサイズを比べてみたら、僕のが約560MBに対して、純正アプリのは約900MB(ただし、昔のもの)と約2倍だった※。一方、NixNote2のは約3.2GBと馬鹿のように大きい。その点でも糞アプリだったようだ。 (5/23 6:32)
    • ※僕のは保存するデータが何か欠けているのかも知れない。あるいは、画像などを文字にエンコードせずにそのまま保存しているから小さいのかも知れない。

 

余談

バックアップしたノートのビューアをPHPで作ったら、もう、水を得た魚のようにスラっと気持ち良く作れて、サクッと動いた。いくらでも機能を追加できる気分になれるのは、すごくいいことだ。どっかのモグラや蛇や真珠もどき(駱駝・玉ねぎ)とは全然違うw もちろん、赤い宝石なんてお呼びでないwww (5/22 19:28, 22:14)

 

(5/23 5:48, 6:32 加筆; 8:37 最初の文章が消えていたので復元した。若干加筆。; 5/23 20:19, 21:00 Webプレビュープログラムの機能追加を追記; 5/25 16:40 アップロードを作った件と、Evernote APIとGoのおかしさを追記)

  •   0
  •   1

コメントを書く / Write a comment

名前 / Name    

メール / Mail 

URL