この記事を作った動機
2025/11/27 から、2025/12/4 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。
動作の様子
TODOs
2025/12/4 になる間やる項目たち。やりきれなかった分は次に持ち越し。 内部構造を説明する図とかドキュメントを作る OverlayWindow において、リサイズやサイズ制限をオプションで付ける。デフォルトでは無効にして、とりあえず selector においてそれが機能するようにする。 全体的な未保存、保存、バッファーの管理について考える pageType などは現状のままで、増やさず、free と markdown に専念する tasks -> jobs で定期的に実行されるバックエンドの関数群において、pageCleaner.py を実装する。各ノートにある、参照が消され、deleted.json に記録されていて、一定期間たったページを時期が来たら本当にファイルシステム上から削除する実装をする。コード自体は、tasks.py の設定に依存し、現状では10秒ごとに消す時が来ていないかポーリングする簡易的な実装である。 再接続機能が正しく機能しない場合を検証するTODOs
メイン(持ち越し、新規混合)
useDatabaseStoreをuseNetworkStoreに名前を変更する。send関数をuseDatabaseStore(useNetworkStore)内にあるものに置き換え、旧来のsend関数の実装を廃止する。createPage.tsxの実装で、ページタイプを取得するとネットワーク周りがバグってリクエストした履歴が消えない結果、未接続の状態として判定されてしまうというバグを何とかする。
propertiesとは独立して、commonsの一部として実装する。padStartcommonsが正しく色を更新できないバグを何とかする。
updatePageの割り込みがあった時に、どのページがセーブされたかIDをUUIDをデータとして含めるupdatePageがあったら差分を扱えるように、先にセーブされたデータをバッファーにためるようにして、diffをマージできるようにするupdatePageコマンドのUUID検証ロジックについて正しく機能しているか調べる。
前回からの持ち越し
持ち越し分
except asyncio.CancelledError が mainLoop 内で機能していない可能性について調べる。opening handshake failed
Traceback (most recent call last):
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\http11.py", line 138, in parse
request_line = yield from parse_line(read_line)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\http11.py", line 309, in parse_line
line = yield from read_line(MAX_LINE_LENGTH)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\streams.py", line 46, in read_line
raise EOFError(f"stream ends after {p} bytes, before end of line")
EOFError: stream ends after 0 bytes, before end of line
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\server.py", line 545, in parse
request = yield from Request.parse(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\http11.py", line 140, in parse
raise EOFError("connection closed while reading HTTP request line") from exc
EOFError: connection closed while reading HTTP request line
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\asyncio\server.py", line 356, in conn_handler
await connection.handshake(
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\asyncio\server.py", line 207, in handshake
raise self.protocol.handshake_exc
websockets.exceptions.InvalidMessage: did not receive a valid HTTP request
他の事項
やったことの詳細
deleted.json の形式を変更した
正しくページを識別できるようにページのUUIDを項目に追加した。
旧来形態
{
"newnote": [
{
"pageID": "default.md",
"date": "2025/10/5"
},
{
"pageID": "test/test/test/recursively/test.md",
"date": "2025/10/14"
},
...
]
}
新しい定義
{
"newnote": [
{
"UUID": "18e01583-3b79-42de-a544-f2fec7da41d7",
"pageID": "testa.md",
"date": "2025/12/3"
},
{
"UUID": "9d49383e-9bb1-46ec-a631-66e6730c602c",
"pageID": "test.md",
"date": "2025/12/3"
},
{
"UUID": "a20d2903-43e7-47c7-9c73-e213839165c7",
"pageID": "ttstdss.md",
"date": "2025/12/3"
}
...
]
}
deletePage コマンドの挙動を変更
削除されたページを一時的に保持するために、各ノートブック内に、deletedという名前のフォルダを作成するようにした。そして、そこに[ページのUUID.ページの拡張子]という形で平たく階層構造は保持せずに削除されたページのファイルは移動するようにした。
├─deleted
│ 0ed59523-7ec1-4f56-b1e0-17e936238a3e.md
│ a20d2903-43e7-47c7-9c73-e213839165c7.md
│ ...
deletePage コマンドのバグ修正
ちょっと触ってみたところ以下のようなバグがあったので、修正をした。
- ページを作成
- 作ったページを削除
- 同じノートブック内で、削除されたページと同じ階層構造、名前、ページタイプを持つ新しいページを作成する
- 作ったページを削除しようとするが、ノートブックの整合性が取れていないというエラーになる
- 新たにページを作成しようとしても、古いページが
contentsフォルダに残ったままなので、削除されたページと同じノートブック内で、同じ階層構造、名前、ページタイプを持つ新しいページは、重複エラーとなり作成できなくなる。
上記のことから、コードを探ると、deleted.json内部に記録されたPageIDを元に、すでに、削除されたページと同じ階層構造、名前、ページタイプを持つページが削除されていないか検証するコードがあり、それが新しいページとすでに削除されたページを区別できていないことが分かった。
そのため、具体的な解決策として、PageIDを比較するのではなく、ページに付随している識別子であるUUIDを比較することで、問題が再現しないことを確認し、バグを修正したということにした。
修正前
# Check the page has already been deleted or not.
find = False
for aPageInfo in targetDeletedInfo:
if(aPageInfo["PageID"] == pagePathFromContentFolder):
find = True
break
if(find):
await errorResponse(
websocket,
request,
"The page has already been deleted even the notebook metadata still has the ref to the page. The integrality may be corrupted.",
[notebookName,pagePath,pagePathFromContentFolder,deleted]
)
return
修正後
# NOTE: UUID is added to as identifier
# Check the page has already been deleted or not.
find = False
for aPageInfo in targetDeletedInfo:
if(aPageInfo["UUID"] == PageUUID):
find = True
break
if(find):
await errorResponse(
websocket,
request,
"The page has already been deleted even the notebook metadata still has the ref to the page. The integrality may be corrupted.",
[notebookName,pagePath,pagePathFromContentFolder,deleted]
)
return
fileAPI の案に進展があった
以下は簡易的なメモ書き。
fileAPI として実装するのではなく、さらにHTTPサーバも立てて細かいファイルのやり取りはそれに任せるということを思いついた。それなら大きなファイルも楽に既存のプロトコルに乗せてやり取りできるのではないかと思った。
基本的にはファイルサーバは、
http://serverIP:port/Files/ノートブック名 +階層構造を含めたファイルまでのパス
http://serverIP:port/Files/ノートブックUUID +階層構造を含めたファイルまでのパス
というような形で、直感的にリソースを提供できるようにしたい。
またついでに考えると、HTTPプロトコルへのフォールバックとして、
http://serverIP:port/Commands/[CommandName]
のようなエントリーポイントが考えられた。
卒論を書き進めた
- 調査の章について、何を書くか見当をつけることを進めた。
- X11やマインクラフトのサーバークライアントモデルの件について参考になりそうなリソースを探した。
- データサーバについて記述を追加した。どのような役割があるのか、内部構造がどうなっているのかについて、概要を説明するようにした。また図解も作成した。
- フロントエンドについて章を立てて、図解を作っている途中までは行った。ただし、十分説明できているかは定かではない。
参考にしたサイトとか
今回は特になし。