| 1 | """Reading a notebook cache (schema 15) from outside: its section images, stored as 16 KiB |
| 2 | chunk rows, and the state of its write-ahead log.""" |
| 3 | import struct |
| 4 | |
| 5 | |
| 6 | def image(connection, table='base'): |
| 7 | """The `base` image the queued edits apply to, or the observed `remote` image (None |
| 8 | unless a conflict or uncertain attempt holds one).""" |
| 9 | chunks = connection.execute(f'SELECT chunk, bytes FROM {table} ORDER BY chunk').fetchall() |
| 10 | assert [chunk for chunk, _ in chunks] == list(range(len(chunks))), 'Damaged cached image' |
| 11 | return b''.join(bytes(data) for _, data in chunks) or None |
| 12 | |
| 13 | |
| 14 | def wal_commits(path): |
| 15 | """Commits in `<cache>-wal` since its last reset: frames whose header records the |
| 16 | database size after them, among the frames carrying the log's current salts. Frames |
| 17 | after the last commit belong to a transaction that has not committed.""" |
| 18 | try: |
| 19 | data = path.with_name(path.name + '-wal').read_bytes() |
| 20 | except FileNotFoundError: |
| 21 | return 0, 0 |
| 22 | if len(data) < 32: |
| 23 | return 0, 0 |
| 24 | magic, _, page_size, _, salt1, salt2 = struct.unpack_from('>IIIIII', data) |
| 25 | assert magic in (0x377f0682, 0x377f0683), 'Not a SQLite write-ahead log' |
| 26 | commits = pending = 0 |
| 27 | at = 32 |
| 28 | while at + 24 + page_size <= len(data): |
| 29 | _, committed, frame1, frame2 = struct.unpack_from('>IIII', data, at) |
| 30 | if (frame1, frame2) != (salt1, salt2): |
| 31 | break |
| 32 | if committed: |
| 33 | commits, pending = commits + 1, 0 |
| 34 | else: |
| 35 | pending += 1 |
| 36 | at += 24 + page_size |
| 37 | return commits, pending |