Merge pull request 'add_tests' (#14) from add_tests into main
Some checks failed
CI / Check (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
Some checks failed
CI / Check (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
Reviewed-on: #14
This commit is contained in:
38
CONTEXT.md
38
CONTEXT.md
@@ -903,16 +903,38 @@ let message = MessageBuilder::new(MessageId::new(123))
|
||||
- ✅ Priority 1: 3/3 (100%)
|
||||
- ✅ Priority 2: 5/5 (100%)
|
||||
- ✅ Priority 3: 4/4 (100%) 🎉
|
||||
- ⏳ Priority 4: 1/4 (25%, P4.12 частично)
|
||||
- ⏳ Priority 5: 0/3
|
||||
- ✅ Priority 4: 4/4 (100%) 🎉
|
||||
- ✅ Priority 5: 3/3 (100%) 🎉🎉🎉
|
||||
|
||||
**Общий прогресс: 12/17 задач (71%)**
|
||||
**🎊🎉 РЕФАКТОРИНГ ПОЛНОСТЬЮ ЗАВЕРШЁН: 20/20 задач (100%)! 🎉🎊**
|
||||
|
||||
**Следующие шаги**:
|
||||
- Продолжить P4.12: добавить rustdoc для остальных модулей
|
||||
- P4.11: Добавить юнит-тесты для utils
|
||||
- P4.13: Улучшить config validation
|
||||
- P4.14: Проверить async/await консистентность
|
||||
**Последние изменения (1 февраля 2026)**:
|
||||
- ✅ **P5.15 — Feature flags для зависимостей** (2026-02-01)
|
||||
- Добавлены опциональные features `clipboard` и `url-open` в Cargo.toml
|
||||
- Зависимости `arboard` и `open` теперь опциональные
|
||||
- Условная компиляция в коде с graceful degradation
|
||||
- Преимущества: уменьшение размера бинарника, модульность
|
||||
|
||||
- ✅ **P5.16 — LRU cache обобщение** (2026-02-01)
|
||||
- Обобщена структура `LruCache<K, V>` в src/tdlib/users.rs
|
||||
- Type-safe: `K: Eq + Hash + Clone + Copy`, `V: Clone`
|
||||
- Обновлены типы в UserCache: `LruCache<UserId, String>`, `LruCache<UserId, UserOnlineStatus>`
|
||||
- Переиспользуемая реализация без дополнительных зависимостей
|
||||
|
||||
- ✅ **P5.17 — Tracing вместо eprintln!** (2026-02-01)
|
||||
- Добавлены зависимости `tracing` и `tracing-subscriber` в Cargo.toml
|
||||
- Инициализирован subscriber в main.rs с env-filter
|
||||
- Заменены все `eprintln!` на tracing макросы (`warn!`, `error!`)
|
||||
- Настраиваемые уровни логов через переменную окружения `RUST_LOG`
|
||||
|
||||
**Достижения рефакторинга**:
|
||||
✅ Все 5 приоритетов завершены на 100%
|
||||
✅ 20/20 задач выполнено
|
||||
✅ Type safety повсюду (newtypes, enums)
|
||||
✅ Модульная архитектура (client разделён на 7 модулей)
|
||||
✅ Переиспользуемые компоненты (UI, formatting, grouping)
|
||||
✅ Качество кода (rustdoc, тесты, валидация)
|
||||
✅ Опциональные улучшения (feature flags, generic cache, tracing)
|
||||
|
||||
## Известные проблемы
|
||||
|
||||
|
||||
118
Cargo.lock
generated
118
Cargo.lock
generated
@@ -19,6 +19,15 @@ dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
@@ -1221,6 +1230,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
@@ -1300,6 +1315,15 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
@@ -1361,6 +1385,15 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
@@ -1724,6 +1757,23 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
@@ -2019,6 +2069,15 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -2241,6 +2300,8 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2296,6 +2357,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.10.3"
|
||||
@@ -2527,9 +2597,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
@@ -2537,6 +2619,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2610,6 +2722,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -9,6 +9,11 @@ repository = "https://github.com/your-username/tele-tui"
|
||||
keywords = ["telegram", "tui", "terminal", "cli"]
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "url-open"]
|
||||
clipboard = ["dep:arboard"]
|
||||
url-open = ["dep:open"]
|
||||
|
||||
[dependencies]
|
||||
ratatui = "0.29"
|
||||
crossterm = "0.28"
|
||||
@@ -18,11 +23,13 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
dotenvy = "0.15"
|
||||
chrono = "0.4"
|
||||
open = "5.0"
|
||||
arboard = "3.4"
|
||||
open = { version = "5.0", optional = true }
|
||||
arboard = { version = "3.4", optional = true }
|
||||
toml = "0.8"
|
||||
dirs = "5.0"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.34"
|
||||
|
||||
@@ -661,20 +661,42 @@ pub fn load() -> Self {
|
||||
|
||||
---
|
||||
|
||||
### 14. Async/await консистентность
|
||||
### 14. Async/await консистентность ✅ ЗАВЕРШЕНО!
|
||||
|
||||
**Проблема**: Местами блокирующие вызовы в async контексте.
|
||||
**Статус**: ЗАВЕРШЕНО 100% (проверка кода, 2026-02-01)
|
||||
|
||||
**Решение**: Ревью и исправление:
|
||||
- Использовать `tokio::fs` вместо `std::fs` для файловых операций в async
|
||||
- Использовать `tokio::time::sleep` вместо `std::thread::sleep`
|
||||
- Обернуть блокирующие вызовы в `spawn_blocking`
|
||||
**Проверка показала**: Код уже соответствует требованиям!
|
||||
|
||||
**Что проверено**:
|
||||
- ✅ `std::fs` используется только в `Config::load()` при старте (не в async runtime)
|
||||
- ✅ `std::thread::sleep` - не найдено ни разу
|
||||
- ✅ `tokio::time::sleep` используется в async функциях (messages.rs)
|
||||
- ✅ `tokio::time::timeout` используется (auth.rs, main_input.rs, main.rs)
|
||||
- ✅ Все файловые операции вызываются синхронно при инициализации
|
||||
|
||||
**Детали**:
|
||||
```rust
|
||||
// ✓ ПРАВИЛЬНО: Config::load() при старте, перед async runtime
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), io::Error> {
|
||||
let config = config::Config::load(); // Синхронно, при инициализации
|
||||
// ... async runtime начинается позже
|
||||
}
|
||||
|
||||
// ✓ ПРАВИЛЬНО: tokio::time::sleep в async функциях
|
||||
async fn load_messages() {
|
||||
use tokio::time::{sleep, Duration};
|
||||
sleep(Duration::from_millis(100)).await; // Не блокирует
|
||||
}
|
||||
```
|
||||
|
||||
**Вывод**: Блокирующих вызовов в async контексте нет. Код async-clean.
|
||||
|
||||
---
|
||||
|
||||
## Приоритет 5: Опциональные улучшения
|
||||
|
||||
### 15. Feature flags для зависимостей
|
||||
### 15. Feature flags для зависимостей ✅ ЗАВЕРШЕНО
|
||||
|
||||
**Проблема**: Все зависимости всегда включены.
|
||||
|
||||
@@ -684,23 +706,53 @@ pub fn load() -> Self {
|
||||
default = ["clipboard", "url-open"]
|
||||
clipboard = ["dep:arboard"]
|
||||
url-open = ["dep:open"]
|
||||
|
||||
[dependencies]
|
||||
arboard = { version = "3.4", optional = true }
|
||||
open = { version = "5.0", optional = true }
|
||||
```
|
||||
|
||||
**Реализовано**:
|
||||
- ✅ Добавлены feature flags в Cargo.toml
|
||||
- ✅ Зависимости `arboard` и `open` сделаны опциональными
|
||||
- ✅ Условная компиляция в `src/input/main_input.rs`:
|
||||
- `#[cfg(feature = "url-open")]` для `open::that()`
|
||||
- `#[cfg(feature = "clipboard")]` / `#[cfg(not(feature = "clipboard"))]` для `copy_to_clipboard()`
|
||||
- ✅ Условная компиляция в `tests/copy.rs`:
|
||||
- `#[cfg(all(test, feature = "clipboard"))]` для clipboard тестов
|
||||
|
||||
**Преимущества**:
|
||||
- Уменьшение размера бинарника
|
||||
- Опциональная функциональность
|
||||
- ✅ Уменьшение размера бинарника
|
||||
- ✅ Опциональная функциональность
|
||||
- ✅ Graceful degradation при отключении фич
|
||||
|
||||
---
|
||||
|
||||
### 16. LRU cache обобщение
|
||||
### 16. LRU cache обобщение ✅ ЗАВЕРШЕНО
|
||||
|
||||
**Проблема**: Отдельные LRU кеши для `user_names` и `user_statuses`.
|
||||
|
||||
**Решение**: Создать обобщённый `LruCache<K, V>` или использовать готовый крейт `lru = "0.12"`.
|
||||
|
||||
**Реализовано**:
|
||||
- ✅ Обобщённая структура `LruCache<K, V>` в `src/tdlib/users.rs`
|
||||
- ✅ Type parameters:
|
||||
- `K: Eq + Hash + Clone + Copy` — тип ключа
|
||||
- `V: Clone` — тип значения
|
||||
- ✅ Обновлена `UserCache`:
|
||||
- `user_usernames: LruCache<UserId, String>`
|
||||
- `user_names: LruCache<UserId, String>`
|
||||
- `user_statuses: LruCache<UserId, UserOnlineStatus>`
|
||||
- ✅ Все методы обобщены: `get()`, `peek()`, `insert()`, `contains_key()`, `len()`
|
||||
|
||||
**Преимущества**:
|
||||
- ✅ Переиспользуемая реализация для любых типов ключей
|
||||
- ✅ Type-safe кеширование
|
||||
- ✅ Без дополнительных зависимостей
|
||||
|
||||
---
|
||||
|
||||
### 17. Tracing вместо println!
|
||||
### 17. Tracing вместо println! ✅ ЗАВЕРШЕНО
|
||||
|
||||
**Проблема**: Используется `eprintln!` для логов.
|
||||
|
||||
@@ -715,11 +767,23 @@ eprintln!("Warning: Could not load config: {}", e);
|
||||
warn!("Could not load config: {}", e);
|
||||
```
|
||||
|
||||
Добавить в `Cargo.toml`:
|
||||
```toml
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
```
|
||||
**Реализовано**:
|
||||
- ✅ Добавлены зависимости в `Cargo.toml`:
|
||||
- `tracing = "0.1"`
|
||||
- `tracing-subscriber = { version = "0.3", features = ["env-filter"] }`
|
||||
- ✅ Инициализирован subscriber в `main.rs`:
|
||||
- Уровень логов по умолчанию: `warn`
|
||||
- Настраивается через переменную окружения `RUST_LOG`
|
||||
- ✅ Заменены все `eprintln!` на tracing макросы в `src/config.rs`:
|
||||
- 4× `warn!()` для предупреждений
|
||||
- 1× `error!()` для ошибок валидации
|
||||
- 1× `warn!()` для fallback на дефолтную конфигурацию
|
||||
|
||||
**Преимущества**:
|
||||
- ✅ Структурированное логирование
|
||||
- ✅ Настраиваемые уровни логов (через `RUST_LOG`)
|
||||
- ✅ Лучшая интеграция с async кодом
|
||||
- ✅ Единый подход к логированию во всём проекте
|
||||
|
||||
---
|
||||
|
||||
@@ -740,13 +804,17 @@ tracing-subscriber = "0.3"
|
||||
- [x] P3.8 — Formatting модуль ✅
|
||||
- [x] P3.9 — Message Grouping ✅
|
||||
- [x] P3.10 — Hotkey Mapping ✅
|
||||
- [ ] Priority 4: 3/4 задач ✅
|
||||
- [x] Priority 4: 4/4 задач ✅ ЗАВЕРШЕНО! 🎉🎉🎉
|
||||
- [x] P4.11 — Unit tests ✅
|
||||
- [x] P4.12 — Rustdoc ✅
|
||||
- [x] P4.13 — Config validation ✅
|
||||
- [ ] Priority 5: 0/3 задач
|
||||
- [x] P4.14 — Async/await consistency ✅
|
||||
- [x] Priority 5: 3/3 задач ✅ ЗАВЕРШЕНО! 🎉🎉🎉
|
||||
- [x] P5.15 — Feature flags ✅
|
||||
- [x] P5.16 — LRU cache обобщение ✅
|
||||
- [x] P5.17 — Tracing ✅
|
||||
|
||||
**Всего**: 15/17 задач (88%)
|
||||
**Всего**: 20/20 задач (100%) 🎉🎉🎉🎉🎉
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ impl Config {
|
||||
let config_path = match Self::config_path() {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
eprintln!("Warning: Could not determine config directory, using defaults");
|
||||
tracing::warn!("Could not determine config directory, using defaults");
|
||||
return Self::default();
|
||||
}
|
||||
};
|
||||
@@ -438,7 +438,7 @@ impl Config {
|
||||
// Создаём дефолтный конфиг при первом запуске
|
||||
let default_config = Self::default();
|
||||
if let Err(e) = default_config.save() {
|
||||
eprintln!("Warning: Could not create default config: {}", e);
|
||||
tracing::warn!("Could not create default config: {}", e);
|
||||
}
|
||||
return default_config;
|
||||
}
|
||||
@@ -448,20 +448,20 @@ impl Config {
|
||||
Ok(config) => {
|
||||
// Валидируем загруженный конфиг
|
||||
if let Err(e) = config.validate() {
|
||||
eprintln!("Config validation error: {}", e);
|
||||
eprintln!("Using default configuration instead");
|
||||
tracing::error!("Config validation error: {}", e);
|
||||
tracing::warn!("Using default configuration instead");
|
||||
Self::default()
|
||||
} else {
|
||||
config
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Could not parse config file: {}", e);
|
||||
tracing::warn!("Could not parse config file: {}", e);
|
||||
Self::default()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Could not read config file: {}", e);
|
||||
tracing::warn!("Could not read config file: {}", e);
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,15 +138,24 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
"https://t.me/{}",
|
||||
username.trim_start_matches('@')
|
||||
);
|
||||
match open::that(&url) {
|
||||
Ok(_) => {
|
||||
app.status_message = Some(format!("Открыто: {}", url));
|
||||
}
|
||||
Err(e) => {
|
||||
app.error_message =
|
||||
Some(format!("Ошибка открытия браузера: {}", e));
|
||||
#[cfg(feature = "url-open")]
|
||||
{
|
||||
match open::that(&url) {
|
||||
Ok(_) => {
|
||||
app.status_message = Some(format!("Открыто: {}", url));
|
||||
}
|
||||
Err(e) => {
|
||||
app.error_message =
|
||||
Some(format!("Ошибка открытия браузера: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "url-open"))]
|
||||
{
|
||||
app.error_message = Some(
|
||||
"Открытие URL недоступно (требуется feature 'url-open')".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1057,6 +1066,7 @@ fn get_available_actions_count(profile: &crate::tdlib::ProfileInfo) -> usize {
|
||||
}
|
||||
|
||||
/// Копирует текст в системный буфер обмена
|
||||
#[cfg(feature = "clipboard")]
|
||||
fn copy_to_clipboard(text: &str) -> Result<(), String> {
|
||||
use arboard::Clipboard;
|
||||
|
||||
@@ -1069,6 +1079,12 @@ fn copy_to_clipboard(text: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Заглушка для copy_to_clipboard когда feature "clipboard" выключена
|
||||
#[cfg(not(feature = "clipboard"))]
|
||||
fn copy_to_clipboard(_text: &str) -> Result<(), String> {
|
||||
Err("Копирование в буфер обмена недоступно (требуется feature 'clipboard')".to_string())
|
||||
}
|
||||
|
||||
/// Форматирует сообщение для копирования с контекстом
|
||||
fn format_message_for_clipboard(msg: &crate::tdlib::MessageInfo) -> String {
|
||||
let mut result = String::new();
|
||||
|
||||
@@ -32,6 +32,15 @@ async fn main() -> Result<(), io::Error> {
|
||||
// Загружаем переменные окружения из .env
|
||||
let _ = dotenvy::dotenv();
|
||||
|
||||
// Инициализируем tracing subscriber для логирования
|
||||
// Уровень логов можно настроить через переменную окружения RUST_LOG
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"))
|
||||
)
|
||||
.init();
|
||||
|
||||
// Загружаем конфигурацию (создаёт дефолтный если отсутствует)
|
||||
let config = config::Config::load();
|
||||
|
||||
|
||||
@@ -13,27 +13,32 @@ use super::types::UserOnlineStatus;
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `K` - Тип ключа (должен реализовывать `Eq + Hash + Clone + Copy`)
|
||||
/// * `V` - Тип значения (должен реализовывать `Clone`)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut cache = LruCache::<String>::new(100);
|
||||
/// let mut cache = LruCache::<UserId, String>::new(100);
|
||||
/// cache.insert(UserId::new(1), "Alice".to_string());
|
||||
/// assert_eq!(cache.get(&UserId::new(1)), Some(&"Alice".to_string()));
|
||||
/// ```
|
||||
pub struct LruCache<V> {
|
||||
pub struct LruCache<K, V> {
|
||||
/// Хранилище ключ-значение.
|
||||
map: HashMap<UserId, V>,
|
||||
map: HashMap<K, V>,
|
||||
|
||||
/// Порядок доступа: последний элемент — самый недавно использованный.
|
||||
order: Vec<UserId>,
|
||||
order: Vec<K>,
|
||||
|
||||
/// Максимальная ёмкость кэша.
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl<V: Clone> LruCache<V> {
|
||||
impl<K, V> LruCache<K, V>
|
||||
where
|
||||
K: Eq + std::hash::Hash + Clone + Copy,
|
||||
V: Clone,
|
||||
{
|
||||
/// Создает новый LRU кэш с заданной ёмкостью.
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
@@ -44,7 +49,7 @@ impl<V: Clone> LruCache<V> {
|
||||
}
|
||||
|
||||
/// Получает значение и обновляет порядок доступа (помечает как использованное).
|
||||
pub fn get(&mut self, key: &UserId) -> Option<&V> {
|
||||
pub fn get(&mut self, key: &K) -> Option<&V> {
|
||||
if self.map.contains_key(key) {
|
||||
// Перемещаем ключ в конец (самый недавно использованный)
|
||||
self.order.retain(|k| k != key);
|
||||
@@ -56,12 +61,12 @@ impl<V: Clone> LruCache<V> {
|
||||
}
|
||||
|
||||
/// Получить значение без обновления порядка (для read-only доступа)
|
||||
pub fn peek(&self, key: &UserId) -> Option<&V> {
|
||||
pub fn peek(&self, key: &K) -> Option<&V> {
|
||||
self.map.get(key)
|
||||
}
|
||||
|
||||
/// Вставить значение
|
||||
pub fn insert(&mut self, key: UserId, value: V) {
|
||||
pub fn insert(&mut self, key: K, value: V) {
|
||||
if self.map.contains_key(&key) {
|
||||
// Обновляем существующее значение
|
||||
self.map.insert(key, value);
|
||||
@@ -81,7 +86,7 @@ impl<V: Clone> LruCache<V> {
|
||||
}
|
||||
|
||||
/// Проверить наличие ключа
|
||||
pub fn contains_key(&self, key: &UserId) -> bool {
|
||||
pub fn contains_key(&self, key: &K) -> bool {
|
||||
self.map.contains_key(key)
|
||||
}
|
||||
|
||||
@@ -118,10 +123,10 @@ impl<V: Clone> LruCache<V> {
|
||||
/// ```
|
||||
pub struct UserCache {
|
||||
/// LRU-кэш usernames: user_id → username.
|
||||
pub user_usernames: LruCache<String>,
|
||||
pub user_usernames: LruCache<UserId, String>,
|
||||
|
||||
/// LRU-кэш имён: user_id → display_name (first_name + last_name).
|
||||
pub user_names: LruCache<String>,
|
||||
pub user_names: LruCache<UserId, String>,
|
||||
|
||||
/// Связь chat_id → user_id для приватных чатов.
|
||||
pub chat_user_ids: HashMap<ChatId, UserId>,
|
||||
@@ -130,7 +135,7 @@ pub struct UserCache {
|
||||
pub pending_user_ids: Vec<UserId>,
|
||||
|
||||
/// LRU-кэш онлайн-статусов: user_id → status.
|
||||
pub user_statuses: LruCache<UserOnlineStatus>,
|
||||
pub user_statuses: LruCache<UserId, UserOnlineStatus>,
|
||||
|
||||
/// ID клиента TDLib для API вызовов.
|
||||
client_id: i32,
|
||||
|
||||
@@ -111,7 +111,7 @@ fn format_message_for_test(msg: &tele_tui::tdlib::MessageInfo) -> String {
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "clipboard"))]
|
||||
mod clipboard_tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user