搶救 OneTab 資料 Save my OneTab data

Posted by 每特17劃 on 2023-01-12

搶救 OneTab 資料 Save my OneTab data

昨晚在如往常按下 OneTab 的圖示後,Chrome 瀏覽器的 OneTab 分頁卻進入了無窮等待載入狀態, 再也顯示不出 OneTab 的頁面了。在網路上能查到 OneTab 在 Linux 上的資料目前並不多, 只好自力求生。

OneTab 是什麼?

OneTab 是一個瀏覽器外掛,它的功能是將瀏覽器的分頁們整批收起來儲存到一頁式的顯示頁面。它主打的訴求是 “節省記憶體”,但原理其實只是關掉分頁,從而釋放出其佔用的記憶體。因爲它提供了一個對整批分頁的 Save / Restore 的方便操作方式,對於瀏覽器常常有大量分頁的使用者來說,是個不錯的工具。

進一步可參考: OneTab extension for Google Chrome and Firefox - save up to 95% memory and reduce tab clutter

資料存在哪?

網路上找到的線索主要有:

  • C:/Users/《Username》/AppData/Local/Google/Chrome/User Data/Default/Local Storage/leveldb
  • chrome-extension_chphlpgkkbolifaimnlloiipkdnihall_0.localstorage
  • 資料庫爲 LevelDB , 副檔名爲 .ldb

然而,實際在 Linux 系統下卻找不到目標檔案。

爲找出真實位置,我採取下述方法:

  1. 首先,建一個新的 Profile 。 方式可參考說明: Create Chrome browser profiles

  2. 在新的 Profile 下安裝 OneTab。可參考: OneTab - Chrome 線上應用程式商店

  3. 進到 Profile 的目錄下,持續偵測監看某個目標網址(ex: 6bcf7279.info)。例如:

    ```
    cd $HOME/.config/google-chrome/《NEW PROFILE》/
    watch 'grep -nHr "https://6bcf7279.info" . | cut -c-80'
    ```
    
  4. 開啓目標網址,並觀察底下檔案的動靜。 註: 此時異動的主要是瀏覽歷史記錄,還不是 OneTab 資料庫。

  5. 將目標網址加進 OneTab,並觀察此操作所觸發的新增異動。此時的異動,才是 OneTab 資料庫真正的位置。

透過這個方法,最終找出實際位置在:

$HOME/.config/google-chrome/《NEW PROFILE》/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/

如何讀取?

OneTab 的資料以 LevelDB 資料庫方式儲存。一開始試了幾種工具都沒成功,後來試成功的有兩個:

Method: ldbdump

在 Linux 系統下, 須先安裝 python-leveldb 套件。之後可執行下述指令存取 OneTab 的資料:

# 註: 將 "《NEW PROFILE》" 更換爲實際的目標名稱
# NOTE: Replace "《NEW PROFILE》" with your target profile
LDB_DIR="$HOME/.config/google-chrome/《NEW PROFILE》/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/"

LDBDUMP_SRC_URL="https://gist.github.com/mkorthof/412e3cb64785c4f136bbb7f6a9d3a71c/raw/e407eda812448e711a1a496cfd480c227eebacfb/ldbdump.py"
pushd "$LDB_DIR"
python <(curl -skL "$LDBDUMP_SRC_URL") -8j "$(pwd)"
popd

NOTE: 若出現下述失敗訊息,則建議先關閉 Chrome 後再執行。

Traceback (most recent call last):
  File "/dev/fd/63", line 74, in <module>
leveldb.LevelDBError: IO error: lock /home/user/.config/google-chrome/Default/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/LOCK: Resource temporarily unavailable

執行成功的話,輸出的內容大致如下:

{
"installDate": 1674487233973.0, "lastSeenVersion": "1.58", "settings": "{\"oneTabTabState\":{\"index\":0,\"pinned\":true,\"active\":true,\"updateDate\":1675574927466,\"updateEvent\":\"onRemoved\"}}", "state": "{\"tabGroups\":[{\"id\":\"api8W1May_apwfeZc3Ulw3\",\"tabsMeta\":[...],\"createDate\":1675489094057},{\"id\":\"1eydQsFZj7QgGOoAQ6jt1c\",\"tabsMeta\":[...],\"createDate\":1675335420561},{\"id\":\"J2-OWp5GcgzhgZu7ZN6yqx\",\"tabsMeta\":[...],\"createDate\":1675264586285}]}"
}

Method: LevelDB Dumper

LevelDB Dumper 在 Arch Linux 有現成套件: leveldb-dumper 。 安裝此程式後, 可執行指令如下:

# 註: 將 "《NEW PROFILE》" 更換爲實際的目標名稱
# NOTE: Replace "《NEW PROFILE》" with your target profile
LDB_DIR="$HOME/.config/google-chrome/《NEW PROFILE》/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/"

TMPD=$(mktemp -d)
pushd "$LIB_DIR"
LevelDBDumper -d "$(pwd)" -o $TMPD -b --outputType json
cat $TMPD/LevelDBDumper.json
popd
rm -r "$TMPD"

NOTE:

執行成功的話,輸出的內容大致如下:

[
 {
  "modified_timestamp": "2023-02-05T05:31:21 UTC",
  "path": "/home/user/.config/google-chrome/Default/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall",
  "data": {
   "installDate": "1.674487233973e+12",
   "lastSeenVersion": "\"1.58\"",
   "settings": "\"{\\\"oneTabTabState\\\":{\\\"id\\\":1392050038,\\\"index\\\":0,\\\"windowId\\\":1392049925,\\\"pinned\\\":true,\\\"active\\\":false,\\\"updateDate\\\":1675575512019,\\\"updateEvent\\\":\\\"onUpdated\\\"}}\"",
   "state": "\"{\\\"tabGroups\\\":[{\\\"id\\\":\\\"api8W1May_apwfeZc3Ulw3\\\",\\\"tabsMeta\\\":[...],\\\"createDate\\\":1675489094057},{\\\"id\\\":\\\"1eydQsFZj7QgGOoAQ6jt1c\\\",\\\"tabsMeta\\\":[...],\\\"createDate\\\":1675335420561},{\\\"id\\\":\\\"J2-OWp5GcgzhgZu7ZN6yqx\\\",\\\"tabsMeta\\\":[...],\\\"createDate\\\":1675264586285}]}\""
  }
 }
]

NOTE: 輸出內容和 ldbdump 結構相同,差異在於 LevelDB Dumper 的內容多了 \\ 的跳脫字元。

匯出 Tab 群組資料(in Markdown)

確定能讀取 OneTab 資料後,下一步是嘗試匯出每一筆 Tab 群組資料,打算依易讀性佳的 Markdown 格式輸出。先安裝 jq 程式,用於處理 JSON 格式。之後寫好的 Shell script 如下:

#!/usr/bin/env bash

# Method: ldbdump
dump_data_with_ldbdump() {
	python <(curl -skL "$LDBDUMP_SRC_URL") -8j "$(pwd)" | jq -Mr .state
}

# Method: LevelDBDumper
dump_data_with_LevelDBDumper() {
	# NOTE: Need to remove extra escape chars and quotes
	cat $TMPD/LevelDBDumper.json | jq -Mr .[0].data.state |
		sed -e 's/\\"/"/g' -e 's/\\\\/\\/g' -e 's/^"//g' -e 's/"$//g'
}

dump_data() {
	# NOTE: Choose and uncomment one of following command

	#dump_with_ldbdump
	#dump_with_LevelDBDumper
}

dump_data | jq -c -Mr .tabGroups[] | while read -r json_TabGroup; do
	var_time=$(echo "$json_TabGroup" | jq -Mr .createDate | sed -e 's/...$//g') # Get the tab group's create time
	var_time=$(date --date="@${var_time}" --iso-8601=s)                         # Convert it to ISO 8601 time formate

	# Add a heading level 2 section and use datetime as section name
	echo -e "## $var_time\n"

	echo "$json_TabGroup" | jq -c -Mr .tabsMeta[] | while read -r json_Tab; do
		var_url=$(echo "$json_Tab" | jq -Mr .url)
		var_title=$(echo "$json_Tab" | jq -Mr .title)

		# Output the tab data with Markdown's link and unordered lists syntax
		echo "- [$var_title]($var_url)"
	done

	echo -e "\n" # Add empty lines to end section
done

NOTE:

  • 關於 jq -c -Mr 中選項
    • -c, 讓同一個 Tab group 的資料壓縮成同一行輸出,以便 shell script 進行處理
    • -M, monochrome, 避免輸出的內容夾雜 colorize code
    • -r, raw, 避開 JSON 輸出字串是多出 " 額外的雙引號

輸出的結果示意如下:

## 2019-09-01T08:25:24+08:00

- [Link title 1](Link url 1)
- [Link title 2](Link url 2)
- [Link title 3](Link url 3)


## 2019-09-04T01:49:51+08:00

- [Link title 4](Link url 4)
- [Link title 5](Link url 5)
- [Link title 6](Link url 6)

重新檢視每天使用流程

在確定能匯出資料後,開始重新思考:”我想要作到什麼樣子?“

回想 OneTab 會累積到 3000 多筆資料,很大部分的原因是,自己一直把 OneTab 當作臨時安置的倉庫。只有把東西存進去,暫時卸下眼前的負擔,卻沒有考慮到後續 Review 消化的流程。這樣的行爲模式,必然導致今天這樣的結果。

反思了一下自己的使用場景, 得到一些想法:

Q: 這些資料是在那裏產生的?

產生地點主要是:

  • 手機瀏覽器(Chrome)
  • Laptop 瀏覽器(Chrome)
  • RSS reader (Feedly)
    • 加在 “Reader Later” 的清單
    • 另開瀏覽器
  • YouTube App
    • 加在 “Watch Later” 的清單
  • 其他,大多是以瀏覽器開啓連接而回到上述的地點

Q: 產生後的流向爲何?

  • 手機瀏覽器 => 加 Bookmark => 無下文
  • 手機瀏覽器 => 用 “Share” 功能傳到 Obsidian => 進到 Obsidian 筆記
    • => 進到當天的 note => 每隔一陣子消化 => 進到對應主題項目的 note
    • => 進到 Queue note => 每隔一陣子消化 => 進到對應主題項目的 note
    • => 進到對應主題項目的 note
      • => 未來查資料、研究、回憶時最容易用到。
  • 手機瀏覽器 => 分頁同步到 Laptop 瀏覽器 => OneTab => 無下文
  • 手機瀏覽器 => 分頁一直累積 => 受不了 => 同步到 Laptop 瀏覽器 => 之後關閉分頁
  • Laptop 瀏覽器 => Copy-n-paste 到 Obsidian => 進到 Obsidian 筆記
  • Laptop 瀏覽器 => 加 Bookmark => 無下文
  • Laptop 瀏覽器 => OneTab => 無下文
  • YouTube App => 進 “Watch Later” => 無下文
  • 其他 => 以瀏覽器開啓連結 => 進到手機瀏覽器或 Laptop 瀏覽器

嘗試改善設計

觀察到:

  • 進到 Obsidian 筆記的資料,會回頭再消化跟重複使用的比例較高
  • 進到 OneTab 或是 Bookmark 的資料,則幾乎就沒有下文了

那麼,若能做到一鍵(或甚至自動)將 OneTab 的資料傳送到 Obsidian 的筆記區,是否就能改善這個問題呢?(示意圖如下)

Imgur

依此想法初步列出目標 :

  • 能將 OneTab 資料同步到 ~/Notebook/Notes/Mobile/OneTab.md
  • 每一個 OneTab 的 Tab group 對應一個 Markdown heading level 2 的 Section
    • 以 ISO 8601 的日期時間格式作標題
    • 網址以 Unordered Lists 列出
  • 比對並跳過已匯出的 Tab group (註: 不用擔心重複執行會產生重複資料)
  • 新進的項目會附加在頁面最下方
  • 若無錯誤的執行完成,則順便一併將該檔案作 git commit 儲存記錄。
  • 儲存完後,回 OneTab 將已匯出的內容清空
  • 安排流程進 OneTab 消化 queued 的group

實作

整合上述的研究內容跟設計想法,實作 script 工具如下:

onetab.inc.sh:

#!/usr/bin/env bash

function murmur {
	echo -e ">>>" $@ >&2
}

function die {
	murmur $@
	exit 1
}

function need_prog() {
	:
}

function cmd_onetab2md() { # Export OneTab data and output with markdown format
	need_prog "LevelDBDumper"
	need_prog "jq"
	need_prog "tac"
	need_prog "git"

	local profile_path="$1"
	local output_path="$2"
	local do_git_commit="$3"
	local ldb_dpath="$profile_path/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall"

	[ -d "$ldb_dpath" ] || die "OneTab database directory not found. Path: ${ldb_dpath}/"

	TMPD=$(mktemp -d)
	[ -d ${TMPD} ] && (
		LevelDBDumper -d "$ldb_dpath" -o $TMPD -b --outputType json

		[ -e $TMPD/LevelDBDumper.json ] || die "Output json file not found"
		[ -e "$output_path" ] || die "Output markdown file not found"

		{
			cat $TMPD/LevelDBDumper.json | jq -Mr .[0].data.state | sed -e 's/\\"/"/g' -e 's/\\\\/\\/g' -e 's/^"//g' -e 's/"$//g' |
				jq -cMr .tabGroups[] |
				tac |
				while read -r line; do
					var_id=$(echo "$line" | jq -Mr .id)
					var_time=$(echo "$line" | jq -Mr .createDate | sed -e 's/...$//g')
					var_time=$(date --date="@${var_time}" --iso-8601=s)

					(grep -e "^## $var_time $var_id" "${output_path}" >&/dev/null) && {
						murmur "[SKIP] $var_time $var_id section already exists."
						continue
					}

					murmur "Processing $var_time $var_id section..."
					(
						echo "## $var_time $var_id"
						echo ""

						echo "$line" | jq -Mcr .tabsMeta[] | while read -r line2; do
							var_url=$(echo "$line2" | jq -Mr .url)
							var_title=$(echo "$line2" | jq -Mr .title)
							echo "- [$var_title]($var_url)"
						done

						echo ""
					) >>"${output_path}"
				done
		}
	)
	rm -r ${TMPD}

	do_git_commit=$(echo $do_git_commit | tr 'A-Z' 'a-z' | sed -e 's/\s//g')
	if [ "$do_git_commit" = "yes" ]; then
		murmur "Check git changes and commit..."

		local output_dpath=$(dirname "$output_path")
		(
			cd "$output_dpath" || exit

			(git rev-parse --show-toplevel >&/dev/null) && {
				local var_filename=$(basename "$output_path")
				git diff -- "$var_filename"
				git add -- "$var_filename"
				git commit -s --message="Export and commit by onetab2md script" -- "$var_filename"
			}
		)
	fi
}

使用方式如下:

source onetab.inc.sh
cmd_onetab2md 《Chrome's profile directory path》 《Output markdown file path》 《git commit or not(yes|no)》

例:

source onetab.inc.sh
cmd_onetab2md $HOME/.config/google-chrome/Default $HOME/Notebooks/Notes/Mobile/OneTab.md yes

後記

2023-01-12

在這篇文章 https://amrf000.github.io/blogs/116893 中,作者 amrf000 分析 OneTab 丟失資料的可能原因。另外, 也在另一篇 https://amrf000.github.io/blogs/112515 有提到:

环境里C盘一旦满了之后,onetab保存标签出错后会导致所有onetab旧数据丢失

2023-01-15

我後來發現 GitHub 上也有專案找到 Local Extension Settings/ 的存放目錄:

專案的 README.md 的 Notes 寫道:

Took me a lot of time to figure out OneTab data is stored in the Local Extension Settings folder

可見資料存放位置並不是很多人知道,也不容易找。

另外,從另一個專案:

有看到 storage.js 的線索。storage.js 似乎是屬於 OneTab 在 Firefox 上存放資料的位置。另一處找到的片段:

也指向相同的結果。

2023-01-15

我以關鍵字: “OneTab”, “Local Extension Settings” 合併在 GitHub 搜尋時,意外發現有人已經做了 OneTab 的備份工具:

其使用了 Tauri, React + TypeScript, Elastic UI 來打造 GUI 應用程式, 相當值得參考。

2023-02-06

GitHub 跟 OneTab 相關的專案有: onetab · GitHub Topics