この記事を作った動機
2025/12/11 から、2025/12/18 に至るまでの間で 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
他の事項
やったことの詳細
データサーバのfindNotes helper 関数でノートブックがないときの挙動について修正した
卒論のために記録を取るために、実装の違いによるバグを避けるため、ノートブックデータをまっさらにした。そのところ、findNotes関数がノートブックのルートフォルダに何もない場合、エラーを返していることが分かった。infoやcreateNotebookコマンドなどの挙動がエラーになったりして、それでは正常に動作しないことが分かったため、ノートブックがない場合について、実装を追加した。
以下は追加した実装である。単にノートブックのフォルダ内に何もない場合、空のdictを返すようにした。これによって、まっさらな状態でノートブックを新規作成できないなどのバグが改善された。
if(os.listdir(root).__len__() == 0):
print("findNotes helper: There is no notebooks. This may not be critical.")
return {}
フロントエンドのsend関数にタイムアウトの項目を追加
要求が破棄されても致命的ではない要求については、一定時間後に要求の履歴を破棄するように設定できるようにした。また、逆に要求の履歴が破棄されるのが不都合なものに関しては、timeoutをnullにすることにより、attempt回分だけ要求を再試行するまで要求の履歴が破棄されないこととした。
変更後の実装 (src\modules\helper\network.tsx)
send: (request:string, attempt, timeout = null) => {
if(attempt == null) attempt = 5
const websocket = get().websocket
if(websocket == null){
// retry
setTimeout(() => { get().send(request,attempt - 1,timeout) },interval * 1000)
return
}
const requestUUID = JSON.parse(request).UUID
let findRequestHistory = false
for(const history of requestHistory){
if(history.UUID == requestUUID){
findRequestHistory = true
break
}
}
const CurrentHistory:Arequest = {
requestJSONstring: request,
UUID: requestUUID,
requestTimestamp: new Date,
timeout: timeout
}
if(!findRequestHistory){
requestHistory.push(CurrentHistory)
}
if(attempt == 0){
console.log("on websocket send data, all attempts are failed. stop")
console.log(request)
return
}
if(
websocket.readyState != WebSocket.CONNECTING &&
websocket.readyState != WebSocket.CLOSED &&
websocket.readyState != WebSocket.CLOSING
){
console.log(request)
websocket.send(request)
// check the request on the timeout state
setTimeout(() => {
console.log(requestHistory)
for(const history of requestHistory){
if(history.UUID == CurrentHistory.UUID){
// when failed request
set({ isDisconnect:true })
// retry
setTimeout(() => { get().send(request,attempt - 1,timeout) },interval * 1000)
break
}
}
},timeoutInterval * 1000)
return
}
// retry
setTimeout(() => { get().send(request,attempt - 1,timeout) },interval * 1000)
},
const removeReceivedRequest = (event:MessageEvent) => {
// remove history depend on the timeout
try{
const newHistory = []
for(const oldHistory of requestHistory){
const requestTime = oldHistory.requestTimestamp.getTime() / 1000.0 // sec
const currentTime = new Date().getTime() / 1000.0 // sec
const timeout = oldHistory.timeout
if(timeout == null) continue
if(currentTime - requestTime > timeout) continue
newHistory.push(oldHistory)
}
requestHistory = newHistory
}catch(error){
console.log("Unable to remove a request. (depend on timeout)")
console.log(error)
console.log(event.data)
console.log(event)
}
// remove history depend on the received data from the dataserver
console.log("removeReceivedRequest is working")
try{
const jsondata = JSON.parse(event.data)
if(jsondata.responseType != "commandResponse") return
const newHistory = []
for(const oldHistory of requestHistory){
if(oldHistory.UUID == jsondata.UUID) continue
newHistory.push(oldHistory)
}
requestHistory = newHistory
console.log(jsondata.UUID)
console.log(newHistory)
}catch (error){
console.log("Unable to remove a request. (depend on received data)")
console.log(error)
console.log(event.data)
console.log(event)
}
}
変更前の実装 (src\modules\helper\network.tsx)
send: (request:string, attempt) => {
if(attempt == null) attempt = 5
const websocket = get().websocket
if(websocket == null){
// retry
setTimeout(() => { get().send(request,attempt - 1) },interval * 1000)
return
}
const requestUUID = JSON.parse(request).UUID
let findRequestHistory = false
for(const history of requestHistory){
if(history.UUID == requestUUID){
findRequestHistory = true
break
}
}
const CurrentHistory:Arequest = {
requestJSONstring: request,
UUID: requestUUID,
requestTimestamp: new Date
}
if(!findRequestHistory){
requestHistory.push(CurrentHistory)
}
if(attempt == 0){
console.log("on websocket send data, all attempts are failed. stop")
console.log(request)
return
}
if(
websocket.readyState != WebSocket.CONNECTING &&
websocket.readyState != WebSocket.CLOSED &&
websocket.readyState != WebSocket.CLOSING
){
console.log(request)
websocket.send(request)
// check the request on the timeout state
setTimeout(() => {
console.log(requestHistory)
for(const history of requestHistory){
if(history.UUID == CurrentHistory.UUID){
// when failed request
set({ isDisconnect:true })
// retry
setTimeout(() => { get().send(request,attempt - 1) },interval * 1000)
break
}
}
},timeoutInterval * 1000)
return
}
// retry
setTimeout(() => { get().send(request,attempt - 1) },interval * 1000)
},
const removeReceivedRequest = (event:MessageEvent) => {
// remove history depend on the received data from the dataserver
console.log("removeReceivedRequest is working")
try{
const jsondata = JSON.parse(event.data)
if(jsondata.responseType != "commandResponse") return
const newHistory = []
for(const oldHistory of requestHistory){
if(oldHistory.UUID == jsondata.UUID) continue
newHistory.push(oldHistory)
}
requestHistory = newHistory
console.log(jsondata.UUID)
console.log(newHistory)
}catch (error){
console.log("Unable to remove a request. (depend on received data)")
console.log(error)
console.log(event.data)
console.log(event)
}
}
フロントエンドの接続状態管理について変更
データサーバからメッセージがあった場合に、データサーバに接続されているとみなして、isDisconnectをfalse(接続状態に変更)するようにした。
変更後の実装 (src\modules\helper\network.tsx)
// when a message is received from the dataserver, change network status
if(isDisconnect)
setWebsocket({isDisconnect:false})
const removeReceivedRequest = (event:MessageEvent) => {
// remove history depend on the timeout
try{
const newHistory = []
for(const oldHistory of requestHistory){
const requestTime = oldHistory.requestTimestamp.getTime() / 1000.0 // sec
const currentTime = new Date().getTime() / 1000.0 // sec
const timeout = oldHistory.timeout
if(timeout == null) continue
if(currentTime - requestTime > timeout) continue
newHistory.push(oldHistory)
}
requestHistory = newHistory
}catch(error){
console.log("Unable to remove a request. (depend on timeout)")
console.log(error)
console.log(event.data)
console.log(event)
}
// remove history depend on the received data from the dataserver
console.log("removeReceivedRequest is working")
try{
const jsondata = JSON.parse(event.data)
if(jsondata.responseType != "commandResponse") return
const newHistory = []
for(const oldHistory of requestHistory){
if(oldHistory.UUID == jsondata.UUID) continue
newHistory.push(oldHistory)
}
requestHistory = newHistory
console.log(jsondata.UUID)
console.log(newHistory)
}catch (error){
console.log("Unable to remove a request. (depend on received data)")
console.log(error)
console.log(event.data)
console.log(event)
}
// when a message is received from the dataserver, change network status
if(isDisconnect)
setWebsocket({isDisconnect:false})
}
getPageTypeコマンドだけなぜか、フロントエンドの要求履歴から消えない理由が分かった
フロントエンドでページを新規作成するUIを開くと、同時にどのタイプのページを作成できるかフロントエンドがデータサーバに対して、getPageTypeコマンドを要求することがある。しかし、そのコマンドが実装されたときだけ、返答が返ってきているにも関わらず、ネットワーク要求履歴から消えないということがあった。
それで最終的には、データサーバのgetPageTypeコマンドの実装が、旧来のままになっており、responseTypeを返していないことが分かった。このためにフロントエンドから要求履歴を削除しようとしたときに、getPageTypeコマンドの要求だけ、コマンド応答ではないとして、無視されていたことが分かった。そのため、getPageTypeコマンドの実装にて、コマンド応答として、responseTypeを返すように改修した。
今回のことから、応答するための共通のhelper関数を作って、それで応答に必要な項目が書けていないか、チェックした方がミスが防げて良いかもしれないということがあった。
修正前
# list pageType
from helper.common import NotImplementedResponse, dataKeyChecker
import type.pages.controller as controller
from helper import loadSettings
import json
async def getPageType(request,websocket):
responseString = json.dumps({
# responseType がここにない
"status" : "ok",
"errorMessage" : "nothing",
"UUID" : request["UUID"],
"command" : "getPageType",
"data" : list(controller.pages.keys())
})
await websocket.send(responseString)
print(">>> " + responseString)
修正後
# list pageType
from helper.common import NotImplementedResponse, dataKeyChecker
import type.pages.controller as controller
from helper import loadSettings
import json
async def getPageType(request,websocket):
responseString = json.dumps({
"responseType" : "commandResponse", # コマンドレスポンスを返すようにしたので、フロントエンドでバグを起こさないようになった。
"status" : "ok",
"errorMessage" : "nothing",
"UUID" : request["UUID"],
"command" : "getPageType",
"data" : list(controller.pages.keys())
})
await websocket.send(responseString)
print(">>> " + responseString)
卒論を書いていた
- 結果のところについて、ページの表示と新規作成について書いた
- 考察のところについて大体何を書くかについて検討をつけた。
- はじめにの目的のところで、理想と研究内で取り扱うことの二つに分けて、何に向かっているか、何をしようとしているかについて書いた。
参考にしたサイトとか
今回は特になし。