You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
tg sql --account <id|@username|label> queries that account's cache;
without it tg sql reads the active account's. tg sql print-schema needs
no account and creates no file.
CACHE_UNAVAILABLE (exit 1), with details: { reason, path }, when an
account's cache can't be opened yet: an older tg still holds the shared
cache (shared_cache_in_use), the move failed (move_failed), or the file
belongs to another account (owner_mismatch, from the new cache_meta
stamp).
tg status has a storage section (legacy_caches: shared caches set
aside; migration: a move that waits), and every item in sync.jobs, progress.sample_chats and rate_limits.active_flood_waits has account_id. With several accounts, pretty output tags jobs with acct:<id> and the retry commands add --account <id>.
tg status gives each entry in accounts.accounts its own cache, connection, sync, progress and rate_limits, and pretty output
prints them on a line under each account. tg status --account <selector>
reports only that account and sets the new top-level account_id (null
for all); pretty output names that account under the title. tg daemon status adds accounts[] with each account's
connection, last error, messages synced and cached, and chat counts, and
the same storage as tg status, so a shared cache that an older daemon
still holds explains why every account reads empty.
Changed
Breaking: each account has its own cache, accounts/<id>/cache.db:
cached chats, users and messages, sync state, jobs, rate limits and api_activity. data.db keeps only the accounts. Stop the daemon before
upgrading. The first command that needs a cache moves the shared cache.db of 0.3 and older, and so do tg auth login, login-qr, logout and tg accounts remove before they change the accounts: with
one account it becomes that account's cache and everything carries over;
with several (or none) its rows can't be split by account, so it is kept
as shared-cache.db and each account syncs again from an empty cache; an
empty one is deleted. While an older tg still has the file open, a single
account's commands fail with CACHE_UNAVAILABLE until it exits. tg auth login, login-qr, logout and tg accounts remove fail the same way
with any number of accounts and change none, so a second account can't
force the first one's cache aside, and the account left after a removal
(or the first one added) is never handed rows that other accounts wrote.
A file that can't be moved yet (move_failed: a read-only file, a full
disk) waits the same way; only a file that is not a database is set aside
for a single account. A login after tg auth logout gives
the same Telegram user a new account ID and an empty cache; the old accounts/<id>/ stays, and renamed to the new ID it is taken back, as the
owner stamp matches the user. Going back to 0.3 after the move is not
supported. Scripts that open cache.db directly must open accounts/<id>/cache.db or use tg sql --account. See "Upgrading to
per-account caches" in docs/configuration.md.
Breaking: a command that reads a cache works on one account: --account, else the active account, else the only one. With no account
it fails with AUTH_REQUIRED (exit 2), so tg chats get --id <ID> without
an account is no longer the INVALID_ARGS cache miss of 0.3.0. With
several accounts and none active it fails with NO_ACTIVE_ACCOUNT
(exit 1) and lists them in details.accounts.
The daemon runs each account with its own cache, scheduler and sync
worker, taking turns between accounts; each account waits 3 seconds
between its own jobs. An account whose cache doesn't open is logged and
left out; one whose scheduler fails its startup pass, or whose cache fails
while its next job is picked, is logged and keeps syncing, and never stops
the accounts after it. daemon_status rows are per account; tg status and tg daemon status sum them and count only rows the live daemon wrote.
Fixed
tg chats list, chats search, contacts list, messages search and tg sql mixed every account's data, and message IDs and basic group IDs
that two accounts share collided in one cache (#17).
The daemon ran a sync job on the first connected account's client, with
another account's access hashes; a job now runs only on its own account's
client, and a disconnected account's jobs wait for it.
tg send resolved recipients with access hashes that another account
cached.
A delete without chat context could mark another account's message with
the same ID as deleted (the cross-account part of #7).
A FLOOD_WAIT that one account recorded (one longer than mtcute waits out:
60 seconds in the CLI, 5 minutes in the daemon) blocked API calls of every
account. A shorter one that mtcute sleeps through inside a daemon job
still holds the job loop for every account (#60).
tg chats search, messages search and chats sync ignored --account.
Processes that opened a new cache or data.db at the same moment, such as
the daemon and a command right after the first login, could fail with
"database is locked": SQLite fails all but one switch to WAL at once,
without waiting. They now try again for up to 5 seconds. The cache moved
from a shared one is also in this state on its first open.
Processes that opened a new cache, or one with an outdated search index,
at the same moment built the index together: one could drop the message_search table or its triggers while another was filling it, and
fail with "no such table", "vtable constructor failed" or "database is
locked". One process now builds it under the write lock while the others
wait.
After a reconnect, a complete dialog pass now marks the account's chats it
didn't list (left, deleted or migrated) caught up, as the startup pass
does. Before, such a chat whose newest message an earlier failed pass had
left unknown still cost a messages.getHistory call. A chat turned on with tg chats sync still gets its checking page.
The daemon could skip a delete in a private chat or basic group, or mark the
wrong message deleted. Telegram names no chat for such a delete, and the
daemon matched the ID across every cached chat of the account. When a
channel or supergroup had a message with the same ID, the deleted message
stayed unmarked; when the deleted message wasn't cached, that channel post
was marked deleted instead and dropped out of tg messages list and messages search. The daemon now looks only among the account's private
chats and basic groups, which share one ID sequence, through a new index on messages_cache.message_id instead of reading every cached message. An ID
that two of them still have is left as it is and logged as a warning (#7).
An account whose first connect failed (login, getMe or the updates loop)
kept its Telegram client, its connection and its session file open until the
daemon stopped. Stopping the daemon (tg daemon stop, SIGTERM or SIGINT)
also exited without closing any client's connection. The daemon now closes
each client once, right after a failed connect and on shutdown (#34).
History pages stored no sender for an incoming private message, a channel
post or a message in Saved Messages, because Telegram leaves the sender out
of those, while the same message cached live had one. fromId and sender.id were null for them in tg messages search, list and get, sender had no name, and --sender missed them. A daemon catch-up or a --fresh page also erased the sender that a live update had stored. They
now get the sender a live message gets: the other user in a private chat,
your own user in Saved Messages and the channel's marked ID (-100…) for a
channel post. The first run of the new version fills in the messages cached
before. Stop the daemon before upgrading and start it again after, so the
old daemon neither loses writes while that one-time fill holds the database
nor stores new messages without a sender; any it caches after the upgrade
stay without a sender until they are fetched again (tg messages list --fresh) (#44).
A Telegram community (a collection of linked chats) would have been cached
as a basic group without its access hash, so tg chats list --type group
would have listed it, and syncing it with tg chats sync would have reached
Telegram with access hash 0. The cache now stores a community as type community with its access hash: type in tg chats output, chat_type
in tg status, and chats_cache.type / chat_sync_state.chat_type in tg sql say community, tg chats list --type community filters on it, and
like a channel it is off by default. tg doesn't cache joined communities
yet, because mtcute's dialog list skips them (#58), so --type community
finds none so far (#45).
tg status didn't say when the running daemon was a different version from tg, so after an upgrade a daemon left running kept writing the cache in
the old format and nothing said to restart it. The daemon now records its
version when it starts. tg status reports it as daemon.version and sets daemon.version_mismatch when it differs from this tg (cli_version). --format pretty shows a Version row and how to restart the daemon, or,
when the daemon is the newer one, says to use the newer tg instead. A
daemon started by 0.3.0 or older records no version and counts as older
(#46).
tg send --to, tg messages list and get --chat took a positive number
the cache doesn't know as basic group N, so a typo or an uncached user ID
could send to that group or fail with PEER_ID_INVALID. Such a number is
now user N and never a group: basic group N is -N. Without a cached access
hash Telegram may still refuse the user; use its @username or +phone
once and the ID works after that. A number of 10 or more digits is still
read only as a phone number (#47).
A daemon stop closed the session files under running clients, so update
state and peers that arrived between the signal and the shutdown were lost,
the updates loop logged errors until the process exited, and "Received
SIGTERM" appeared twice. The daemon now keeps the session files open until
it closes its clients, so those writes are saved and the stop logs no
updates-loop error or failed save. A stop during the dialog pass ends it at
the next dialog; a FLOOD_WAIT the daemon is already waiting out still holds
the stop until it ends (#64) (#56).
tg accounts switch lasted one command when config.json set activeAccount: the next command made that account active again and read
its cache. The switch now writes the new ID to activeAccount when the key
is set; without the key config.json is not created (#61).
The startup handling of config.json had four more gaps: -q skipped
applying activeAccount and the file check that every other format runs, so
a quiet command could use another account; -f <format> before tg config
or tg skill ran the check, so tg config set couldn't repair an invalid
file; a file that couldn't be read printed a stack trace instead of a GENERAL_ERROR with the path in details.path; and an invalid file stopped tg daemon start, though the daemon reads none of its keys (#61).