gdrive.koplugin

KOReader plugin to browse and read books from Google Drive, offline-first

View the Project on GitHub tamvcspk/gdrive.koplugin

Kế hoạch: Plugin KOReader đọc sách từ Google Drive (offline-first)

Cập nhật 2026-10-25 theo phản hồi: thiết bị Kobo; đã có OAuth client id/secret; thư viện = nhiều folder nguồn do user thêm (gồm cả folder được chia sẻ); hạn mức dung lượng do user cấu hình; không đồng bộ ngược tiến độ đọc (để dành cho kế hoạch sau).

Cập nhật lần 2: dùng chung OAuth client với rclone (Desktop); nguồn lớn nhất ~200–300 GB, nhiều loại file, cả truyện tranh lẫn truyện chữ; thông số đọc trực tiếp từ Kobo qua USB (mục 5.6).

1. Bối cảnh & mục tiêu

Mục tiêu: KOReader trên máy đọc sách duyệt được các thư mục sách trên Google Drive, tải từng cuốn về khi cần đọc, và vẫn duyệt/đọc được khi Wi-Fi tắt.

Ràng buộc:

Ràng buộc Hệ quả thiết kế
Bộ nhớ thiết bị nhỏ Chỉ tải sách khi cần, hạn mức dung lượng do user đặt, tự xoá bớt (LRU)
CPU/RAM yếu, không duyệt Drive trực tiếp được Chỉ gọi REST API tối giản, gộp request, UI dạng danh sách văn bản, phân trang
Wi-Fi thường tắt Offline-first: mọi thao tác duyệt chạy trên catalog cache cục bộ; mạng chỉ dùng khi user chủ động “Sync”
PDF chỉ đọc tốt bằng KOReader Giải pháp nằm bên trong KOReader (plugin Lua), mở sách bằng reader của KOReader

Luồng sử dụng:

  1. User bật Wi-Fi → Sync: cập nhật catalog của mọi folder nguồn + tải các sách trong hàng đợi.
  2. User tắt Wi-Fi → vẫn duyệt toàn bộ cây thư mục từ cache, thấy cuốn nào đã có trên máy, cuốn nào chưa.
  3. Chọn cuốn đã tải → mở ngay. Chọn cuốn chưa tải → có mạng thì tải rồi mở; không có mạng thì đưa vào hàng đợi, lần sync sau sẽ tải.

Ngoài phạm vi (để sau): đồng bộ tiến độ đọc/highlight giữa nhiều thiết bị, backup lên Drive. Thiết kế hiện tại chỉ cần scope chỉ đọc; khi làm phần này sẽ cần xin thêm scope ghi.


2. Các phương án kiến trúc

Phương án A: plugin KOReader gọi thẳng Google Drive API v3 (đã chọn)

Phương án B: rclone làm cầu nối + WebDAV/OPDS có sẵn của KOReader


3. Xác thực Google

3.1 Ràng buộc từ phía Google

3.2 Các cách lấy refresh token (gợi ý)

Cách Mô tả Đánh giá
A. Script trên PC tools/gdrive_login.py (Python thuần, không cần thư viện ngoài): mở trình duyệt → đăng nhập → nhận code qua loopback → đổi lấy refresh token → ghi file gdrive_auth.lua. Chép vào Kobo qua USB. Chắc chắn chạy, làm được ngay. Dùng cho MVP.
B. Ghép đôi qua điện thoại trong mạng LAN (không cần PC) Plugin mở một HTTP server nhỏ trên Kobo và hiện QR code trỏ tới http://<ip-kobo>:8765/. Điện thoại quét QR → trang trên Kobo có nút “Đăng nhập Google”. Sau khi đồng ý, Google redirect điện thoại tới http://127.0.0.1:8765/?code=…, trang này lỗi không tải được (đó là chuyện bình thường). User copy URL trên thanh địa chỉ, quay lại tab trang Kobo, dán vào ô và bấm gửi. Kobo tách lấy code và tự đổi lấy refresh token. Không cần PC, không phải gõ trên bàn phím e-ink; hơi nhiều bước nhưng chỉ làm một lần. Làm ở Phase 5.
C. Service account Chia sẻ folder cho email của service account; Kobo tự ký JWT RS256. Phải ký RSA trong Lua qua FFI OpenSSL → phức tạp; folder phải share riêng cho nó. Chỉ là dự phòng.
D. API key + folder “Anyone with link” Không cần OAuth. Sách bị public theo link. Chỉ dùng nếu chấp nhận điều đó.

Đề xuất: A cho MVP, sau đó B để thiết bị tự đăng nhập lại khi token bị thu hồi.

3.2b Dùng chung client với rclone (đã chốt)

Client hiện có là loại Desktop, đang dùng cho rclone → dùng lại được, và rclone thay luôn được script đăng nhập ở cách A:

# Tạo một remote riêng chỉ đọc cho Kobo. rclone tự mở trình duyệt để đăng nhập.
rclone config create kobo-gdrive drive client_id=<ID> client_secret=<SECRET> scope=drive.readonly
# Xem token vừa tạo (trong đó có refresh_token)
rclone config dump

Sau đó tools/rclone_to_kobo.py đọc rclone config dump và ghi ra gdrive_auth.lua (client_id, client_secret, refresh_token), có thể ghi thẳng vào E:\.adds\koreader\settings\ khi Kobo đang cắm USB.

Lưu ý:

3.3 Trên thiết bị


4. Thư viện: folder nguồn & cách duyệt cây thư mục

4.1 Folder nguồn (source)

User quản lý danh sách nguồn trong Google Drive → Nguồn sách. Mỗi nguồn có:

Cách thêm nguồn (có nhiều cách vì gõ trên Kobo rất cực):

  1. Folder picker online ngay trên Kobo (cách chính): duyệt lười từng cấp, mỗi lần chỉ một request:
    • My Drive → 'root' in parents
    • Shared with me → sharedWithMe = true and mimeType = 'application/vnd.google-apps.folder'
    • Shared drives → drives.list
    • Starred → starred = true (lối tắt tiện cho folder hay dùng)

    Giữ lâu một folder → “Thêm làm nguồn”.

  2. Dán link/ID qua trang web LAN (cùng HTTP server với cách đăng nhập B): mở trang trên điện thoại/PC, dán link drive.google.com/drive/folders/<id>.
  3. Sửa file cấu hình gdrive_sources.lua qua USB (cho người thích tự làm).

4.2 Các cách đồng bộ cây thư mục (brainstorm)

Chi phí chính trên Kobo là số lượng request HTTPS (mỗi request ~0,3–1 giây kể cả bắt tay TLS trên Wi-Fi yếu), không phải dung lượng dữ liệu. Các cách:

# Cách Số request Nhận xét
1 BFS ngây thơ: mỗi folder một lệnh files.list = số folder 500 folder ≈ 5–8 phút. Quá chậm.
2 BFS gộp cha: q = ('id1' in parents or 'id2' in parents or … ) and trashed = false, gộp 20–40 folder mỗi request (giới hạn bởi độ dài URL), pageSize=1000 ≈ số folder / 30 + số trang Giảm 20–40 lần. Làm được cho mọi loại nguồn. Mặc định.
3 Quét cả shared drive một lần: corpora=drive&driveId=…&includeItemsFromAllDrives=true&supportsAllDrives=true, liệt kê mọi item rồi dựng cây cục bộ theo parents ≈ số item / 1000 Nhanh nhất với shared drive chủ yếu chứa sách. Nếu drive có nhiều file rác (ảnh…) thì lọc theo mimeType/đuôi file trong q.
4 Lấy hết folder trước, file sau: một truy vấn mimeType = folder cho toàn bộ My Drive → dựng cây → chỉ lấy file của các folder nằm trong nguồn (dùng cách 2) ít Tốt khi My Drive có nhiều folder nhưng nguồn chỉ là một nhánh nhỏ. Tối ưu thêm, không bắt buộc.
5 Tăng dần bằng Changes API: changes.getStartPageToken sau lần quét đầy đủ, các lần sau changes.list (riêng cho My Drive/Shared-with-me và cho từng shared drive) thường 1–2 Sync hằng ngày gần như tức thì. Mỗi thay đổi được đối chiếu với cây đã biết: item có cha nằm trong nguồn thì cập nhật, item bị chuyển ra ngoài hoặc xoá thì gỡ.
6 Lười theo folder (on_demand): mở folder khi online thì gọi files.list cho đúng folder đó và cache lại 1 mỗi lần mở Cho folder khổng lồ; offline chỉ thấy những gì đã từng mở.

Lọc ngay trong truy vấn (quan trọng với nguồn 200–300 GB nhiều loại file): thêm vào q các điều kiện not mimeType contains 'video/' and not mimeType contains 'audio/'; ảnh (image/) cũng bị loại, trừ khi nguồn bật chế độ truyện tranh dạng folder ảnh (4.4). Drive không lọc được theo đuôi file trong q, nên phần còn lại lọc theo đuôi ngay trên thiết bị, và chỉ lưu vào SQLite những item là sách hoặc folder chứa sách. Nhờ vậy catalog nhỏ và lượng dữ liệu tải về giảm mạnh.

Ước lượng cho nguồn lớn nhất: giả sử khoảng 30.000 file sách và 3.000 folder:

Nếu thực tế lớn hơn nhiều thì đặt nguồn này ở chế độ depth 2 hoặc on_demand.

Chiến lược đề xuất: lần đầu quét đầy đủ bằng cách 2 (hoặc cách 3 cho shared drive); các lần sau dùng cách 5; folder nguồn đặt on_demand thì dùng cách 6. Có thêm nút “Quét lại toàn bộ” để sửa khi cache lệch.

Lưu ý: không dựa vào modifiedTime của folder để phát hiện thay đổi, vì Drive không cập nhật trường này một cách tin cậy khi file con thay đổi. Changes API mới là nguồn đáng tin.

Các trường hợp đặc biệt của Drive:

Trường dữ liệu yêu cầu từ API (giữ response nhỏ): nextPageToken, files(id, name, mimeType, size, modifiedTime, md5Checksum, parents, shortcutDetails, trashed)

4.3 Cách duyệt offline (UI)

Màn hình gốc của plugin:

Google Drive                      Sync: 2 ngày trước
───────────────────────────────────────────────
🔍 Tìm kiếm
🕘 Đọc gần đây
🆕 Mới trên Drive (từ lần sync trước)   (7)
💾 Trên máy                       1,2/2 GB
⏳ Hàng đợi tải                         (3)
───────────────────────────────────────────────
📚 Sách giáo khoa          (124 · 18 đã tải)
📚 Truyện (shared)          (56 · 4 đã tải)
📚 Papers [on-demand]

4.4 Truyện tranh & nhiều loại file

Nguồn có cả truyện chữ lẫn truyện tranh, nên phải xử lý các dạng sau:

Dạng trên Drive Cách xử lý
pdf, epub, mobi, azw3, fb2, djvu, txt… Tải nguyên file, mở bằng KOReader
cbz / zip Tải nguyên file; KOReader mở trực tiếp
cbr / rar, cb7 / 7z Tải nguyên file. KOReader v2025.04 trên máy có libarchive (plugin Archive viewer), nhưng phải thử mở một file .cbr/.cb7 thật ở Phase 1. Nếu không đọc được như một cuốn truyện thì cho hiện kèm cảnh báo, hoặc (tuỳ chọn) chuyển thành .cbz ngay trên máy sau khi tải bằng ffi/archiver (giải nén) + ffi/zipwriter (đóng gói)
Folder chứa toàn ảnh (mỗi chapter là một folder jpg/png/webp) Bật chế độ “Folder ảnh = truyện” cho từng nguồn (mặc định tắt). Khi đó folder có ≥ N ảnh và không có folder con hiện như một cuốn sách (📖 Chap 12 · 45 trang · 18 MB). Lúc tải: lấy lần lượt từng ảnh rồi đóng gói thành .cbz trên máy bằng ffi/zipwriter (chế độ store, không nén lại). Cần nhiều request nên chậm hơn, nhưng chạy trong hàng đợi và resume được theo từng ảnh
Ảnh lẻ, video, audio, Google Docs native, file khác Bỏ qua

Phần duyệt bổ sung cho truyện tranh:

Cách đã xem xét và loại bỏ: tạo file giả 0 byte trong File Manager của KOReader để duyệt bằng giao diện có sẵn. Loại vì KOReader sẽ cố mở hoặc đọc metadata các file đó, làm rối History/Collections, và không có chỗ hiển thị trạng thái. Thay vào đó, sách đã tải vẫn nằm trong thư mục thật nên File Manager, History, Collections của KOReader dùng bình thường.


5. Thiết kế plugin

5.1 Cấu trúc thư mục

gdrive.koplugin/
├── _meta.lua          -- tên, mô tả plugin
├── main.lua           -- đăng ký menu, Dispatcher actions, điều phối
├── auth.lua           -- đọc gdrive_auth.lua, refresh access token
├── api.lua            -- Drive API: files.list (gộp cha), drives.list, changes, download
├── catalog.lua        -- SQLite: sources, items, edges, queue
├── sources.lua        -- quản lý nguồn + folder picker online
├── sync.lua           -- quét đầy đủ / tăng dần / on-demand, xử lý hàng đợi
├── downloader.lua     -- tải ra .part, Range resume, kiểm tra size, tiến độ
├── storage.lua        -- hạn mức, LRU eviction, ghim
├── browser.lua        -- UI duyệt offline (Menu widget), tìm kiếm, danh sách phẳng
└── pairing.lua        -- (Phase 5) HTTP server LAN: đăng nhập qua điện thoại, thêm nguồn
tools/
└── gdrive_login.py    -- script trên PC sinh gdrive_auth.lua

5.2 Lược đồ SQLite

File: koreader/settings/gdrive/catalog.sqlite3 (KOReader có sẵn lua-ljsqlite3).

sources(id PK, folder_id, name, kind, drive_id, sync_mode, depth,
        enabled, status, changes_token, last_sync)
items(id PK,            -- Drive file id
      name, mime_type, is_folder, size, modified_time, md5,
      shortcut_target,
      book_count, local_count,           -- chỉ cho folder, tính sẵn khi sync
      synced,                            -- folder on_demand/depth: đã liệt kê con chưa
      local_path, local_modified_time,
      state,            -- remote|queued|downloading|local|outdated|error
      pinned, last_opened, first_seen)
edges(parent_id, child_id, PRIMARY KEY(parent_id, child_id))  -- nhiều-nhiều
meta(key PK, value)     -- last_sync, schema_version, ...

5.3 Tải sách

5.4 Quản lý dung lượng (hạn mức do user đặt)

5.5 Wi-Fi

5.6 Lưu ý riêng cho Kobo

Thông số đọc từ máy (ổ E:, 2026-10-25):

Mục Giá trị Hệ quả
KOReader v2025.04 (.adds/koreader/git-rev) Phiên bản mục tiêu để phát triển và test
Firmware Kobo 4.38.23429  
Bộ nhớ 6,9 GB, chỉ còn trống 39 MB Phải dọn chỗ trước khi test (Phase 0.5)
Sách hiện có /mnt/onboard/Home = 6,3 GB (home_dir của KOReader) Ứng viên chuyển lên Drive
Dữ liệu KOReader ota/ 91 MB (gói cập nhật cũ), cache/ 47 MB Xoá được, lấy lại ~140 MB
Nickel KoboReader.sqlite 139 MB; ExcludeSyncFolders đã bỏ qua thư mục bắt đầu bằng dấu chấm Đặt thư mục tải là .gdrive thì Nickel không quét
Metadata sách document_metadata_folder = "doc" Đổi sang “hash” (5.4)
Thư viện có sẵn libsqlite3, libarchive, libssl/libcrypto, ffi/zipwriter.lua, ffi/archiver.lua Đủ cho catalog, đóng gói .cbz, HTTPS
Plugin có sẵn SSH.koplugin, httpinspector, qrclipboard SSH dùng để phát triển; QR dùng cho ghép đôi qua điện thoại

Các lưu ý khác:

5.7 Điểm truy cập


6. Lộ trình triển khai

Phase Nội dung Kết quả
0 (tuỳ chọn) rclone serve webdav + Cloud storage WebDAV của KOReader Đọc được ngay trong lúc chờ plugin
0.5. Dọn chỗ Xoá ota/ và cache/ của KOReader; đổi metadata sang “hash”; sao lưu Home lên Drive rồi xoá bớt sách ít đọc Còn trống tối thiểu ~500 MB để test
1. Spike Tạo token bằng rclone + rclone_to_kobo.py; trên Kobo: refresh token, list 1 folder, tải 1 file, kiểm tra TLS và đồng hồ; mở thử .cbz/.cbr/.cb7 Chứng minh OAuth và HTTPS chạy trên Kobo thật; biết định dạng truyện nào đọc được
2. Nguồn + catalog SQLite schema, folder picker online (My Drive / Shared with me / Shared drives / Starred), quét đầy đủ kiểu BFS gộp cha, shortcut, lọc định dạng Thêm được nhiều nguồn, có cache cây thư mục
3. Duyệt offline + tải browser.lua (cây + Tìm kiếm + Trên máy + Hàng đợi), downloader (.part, resume), hàng đợi offline MVP: luồng sử dụng chính chạy được
4. Dung lượng Hạn mức, LRU, ghim, cảnh báo docsettings, màn hình Trên máy An toàn với bộ nhớ nhỏ
5. Hoàn thiện Changes API (sync tăng dần), chế độ on_demand/depth, Mới trên Drive, ghép đôi qua điện thoại (đăng nhập + thêm nguồn qua LAN), tự tắt Wi-Fi, Dispatcher, đổi tên/chuyển file theo Drive Dùng hằng ngày thoải mái
6. Truyện tranh Chế độ “Folder ảnh = truyện” + đóng gói .cbz, “Tải N chương tiếp theo”, chuyển cbr/cb7 sang cbz nếu cần, xoá chương đã đọc Đọc truyện tranh dạng folder ảnh
Sau này Đồng bộ tiến độ đọc/highlight nhiều thiết bị, backup lên Drive (cần scope ghi) Kế hoạch riêng

7. Môi trường phát triển & kiểm thử


8. Rủi ro & cách giảm thiểu

Rủi ro Giảm thiểu
Refresh token hết hạn sau 7 ngày Consent screen để In production; báo lỗi rõ và đăng nhập lại được ngay trên Kobo (Phase 5)
Client id hiện có là loại Web, không redirect được về loopback Thêm redirect URI loopback vào client, hoặc tạo client Desktop mới
Google siết scope drive.readonly với app chưa verify Dự phòng: service account hoặc API key + folder chia sẻ theo link
Sync chậm với cây lớn Gộp cha, quét cả shared drive một lần, Changes API, chế độ on_demand
Thiết bị treo khi sync Transaction SQLite, yield UI giữa các trang, sync bị ngắt thì chạy tiếp được
Wi-Fi rớt giữa chừng .part + Range resume, retry/backoff
Mất tiến độ đọc khi sách bị xoá/đổi tên Docsettings theo hash (5.4)
Nickel quét trùng sách Thư mục .gdrive (5.6)
API KOReader thay đổi giữa các phiên bản Ghi rõ phiên bản KOReader tối thiểu đã test

9. Quyết định đã chốt & còn mở

Đã chốt

Còn mở

  1. Số file/folder thực tế của nguồn lớn nhất: sẽ đo được sau lần quét thử đầu tiên ở Phase 2 (hoặc chạy rclone size gdrive:<folder> trên PC).
  2. Truyện tranh chủ yếu ở dạng nào (cbz, cbr, folder ảnh)? Câu trả lời quyết định Phase 6 có cần làm sớm không.
  3. Sách nào trong Home (6,3 GB) có thể chuyển lên Drive để giải phóng chỗ?