пятки мне целуйте нахуй
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:isolate';
|
||||
import 'package:dart_lz4/dart_lz4.dart';
|
||||
import 'package:msgpack_dart/msgpack_dart.dart' as msgpack;
|
||||
|
||||
@@ -69,71 +70,75 @@ Uint8List packPacket(int opcode, Map<dynamic, dynamic> payload, {int seq = 0}) {
|
||||
}
|
||||
|
||||
/// Распаковка пакета от сервера
|
||||
Packet unpackPacket(Uint8List packet) {
|
||||
// Для удобства расшифровки пакета переводим в ByteData
|
||||
ByteData packetData = ByteData.view(
|
||||
packet.buffer,
|
||||
packet.offsetInBytes,
|
||||
packet.lengthInBytes,
|
||||
);
|
||||
Future<Packet> unpackPacket(Uint8List packet) async {
|
||||
return Isolate.run(() {
|
||||
// Для удобства расшифровки пакета переводим в ByteData
|
||||
ByteData packetData = ByteData.view(
|
||||
packet.buffer,
|
||||
packet.offsetInBytes,
|
||||
packet.lengthInBytes,
|
||||
);
|
||||
|
||||
// Объяснение каждой переменной смотри в классе Packet
|
||||
// API версия и cmd представляют из себя 8 битные числа
|
||||
final apiVer = packetData.getUint8(0) & 0xFF;
|
||||
final cmd = packetData.getUint8(1) & 0xFF;
|
||||
|
||||
// API версия и cmd представляют из себя 8 битные числа
|
||||
final apiVer = packetData.getUint8(0) & 0xFF;
|
||||
final cmd = packetData.getUint8(1) & 0xFF;
|
||||
// Sequence и OPCode представляют из себя 16 битные числа
|
||||
final seq = packetData.getUint16(2) & 0xFFFF;
|
||||
final opcode = packetData.getUint16(4) & 0xFFFF;
|
||||
|
||||
// Sequence и OPCode представляют из себя 16 битные числа
|
||||
final seq = packetData.getUint16(2) & 0xFFFF;
|
||||
final opcode = packetData.getUint16(4) & 0xFFFF;
|
||||
// После базовых переменных идет длина пакета, является 32 битным числом
|
||||
final packedLen = packetData.getUint32(6);
|
||||
|
||||
// После базовых переменных идет длина пакета, является 32 битным числом
|
||||
final packedLen = packetData.getUint32(6);
|
||||
// Compression flag показывает, сжат ли payload
|
||||
final compFlag = packedLen >> 24;
|
||||
|
||||
// Compression flag показывает, сжат ли payload
|
||||
final compFlag = packedLen >> 24;
|
||||
|
||||
// Длина payload'а
|
||||
final payloadLength = packedLen & 0xFFFFFF;
|
||||
// Длина payload'а
|
||||
final payloadLength = packedLen & 0xFFFFFF;
|
||||
|
||||
// Байты payload'а, могут быть сжаты LZ4
|
||||
var payloadBytes = packet.buffer.asUint8List(10, payloadLength);
|
||||
// Байты payload'а, могут быть сжаты LZ4
|
||||
var payloadBytes = packet.buffer.asUint8List(10, payloadLength);
|
||||
|
||||
dynamic payload;
|
||||
|
||||
if (payloadBytes.isNotEmpty) {
|
||||
if (compFlag != 0) {
|
||||
try {
|
||||
payloadBytes = lz4Decompress(
|
||||
payloadBytes,
|
||||
decompressedSize: _maxDecompressedSize,
|
||||
);
|
||||
|
||||
} catch (_) {
|
||||
dynamic payload;
|
||||
|
||||
if (payloadBytes.isNotEmpty) {
|
||||
if (compFlag != 0) {
|
||||
try {
|
||||
payloadBytes = _lz4BlockDecompress(payloadBytes, _maxDecompressedSize);
|
||||
} catch (e) {
|
||||
logger.e("LZ4 decompression error: $e", error: e);
|
||||
payloadBytes = lz4Decompress(
|
||||
payloadBytes,
|
||||
decompressedSize: _maxDecompressedSize,
|
||||
);
|
||||
} catch (_) {
|
||||
try {
|
||||
payloadBytes = _lz4BlockDecompress(
|
||||
payloadBytes,
|
||||
_maxDecompressedSize,
|
||||
);
|
||||
} catch (e) {
|
||||
// В изоляте нельзя использовать логгер, который пишет в терминал через зависимости Flutter,
|
||||
// но простой print или throw сработает
|
||||
print("LZ4 decompression error: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
payload = msgpack.deserialize(payloadBytes);
|
||||
} catch (e) {
|
||||
if (payloadBytes.isNotEmpty) {
|
||||
print("MsgPack deserialization error: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
payload = msgpack.deserialize(payloadBytes);
|
||||
} catch (e) {
|
||||
if (payloadBytes.isNotEmpty) {
|
||||
logger.e("MsgPack deserialization error: $e", error: e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Packet(
|
||||
api: apiVer,
|
||||
cmd: cmd,
|
||||
seq: seq,
|
||||
opcode: opcode,
|
||||
payload: payload,
|
||||
);
|
||||
return Packet(
|
||||
api: apiVer,
|
||||
cmd: cmd,
|
||||
seq: seq,
|
||||
opcode: opcode,
|
||||
payload: payload,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// LZ4 block декомпрессия (без frame-заголовка).
|
||||
|
||||
@@ -120,7 +120,7 @@ class AppDatabase {
|
||||
final dbPath = await getDatabasesPath();
|
||||
return openDatabase(
|
||||
join(dbPath, 'komet.db'),
|
||||
version: 5,
|
||||
version: 6,
|
||||
onOpen: (db) => db.execute('PRAGMA foreign_keys = ON'),
|
||||
onCreate: (db, _) => _createTables(db),
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
@@ -141,6 +141,9 @@ class AppDatabase {
|
||||
await db.execute('DROP TABLE IF EXISTS chats_cache');
|
||||
await db.execute(_chatsCacheSchema);
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
await db.execute(_messagesSchema);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -164,6 +167,7 @@ class AppDatabase {
|
||||
await db.execute(_syncStateSchema);
|
||||
await db.execute(_chatsCacheSchema);
|
||||
await db.execute(_contactsSchema);
|
||||
await db.execute(_messagesSchema);
|
||||
}
|
||||
|
||||
static const _contactsSchema = '''
|
||||
@@ -211,6 +215,21 @@ class AppDatabase {
|
||||
)
|
||||
''';
|
||||
|
||||
static const _messagesSchema = '''
|
||||
CREATE TABLE messages (
|
||||
id TEXT NOT NULL,
|
||||
account_id INTEGER NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
chat_id INTEGER NOT NULL,
|
||||
sender_id INTEGER NOT NULL,
|
||||
text TEXT,
|
||||
time INTEGER NOT NULL,
|
||||
status TEXT,
|
||||
payload TEXT,
|
||||
PRIMARY KEY (id, account_id),
|
||||
FOREIGN KEY (chat_id, account_id) REFERENCES chats_cache (id, account_id) ON DELETE CASCADE
|
||||
)
|
||||
''';
|
||||
|
||||
static Future<void> saveProfile(ProfileData profile) async {
|
||||
final db = await _instance;
|
||||
await db.insert(
|
||||
@@ -360,4 +379,45 @@ class AppDatabase {
|
||||
whereArgs: [accountId],
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> saveMessages(List<Map<String, dynamic>> rows) async {
|
||||
final db = await _instance;
|
||||
await db.transaction((txn) async {
|
||||
final batch = txn.batch();
|
||||
for (final row in rows) {
|
||||
batch.insert(
|
||||
'messages',
|
||||
row,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> loadMessages(
|
||||
int accountId,
|
||||
int chatId, {
|
||||
int? limit,
|
||||
int? offset,
|
||||
}) async {
|
||||
final db = await _instance;
|
||||
return db.query(
|
||||
'messages',
|
||||
where: 'account_id = ? AND chat_id = ?',
|
||||
whereArgs: [accountId, chatId],
|
||||
orderBy: 'time DESC',
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> clearMessages(int accountId, int chatId) async {
|
||||
final db = await _instance;
|
||||
await db.delete(
|
||||
'messages',
|
||||
where: 'account_id = ? AND chat_id = ?',
|
||||
whereArgs: [accountId, chatId],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,22 @@ class PacketReceiver {
|
||||
|
||||
static const int _maxBufferSize = 2 * 1024 * 1024; // 2 мегабуйта
|
||||
|
||||
/// Добавляет байты в буфер, возвращает все собранные пакеты.
|
||||
/// Добавляет байты в буфер, возвращает поток собранных пакетов.
|
||||
/// Неполные данные остаются в буфере до следующего вызова.
|
||||
List<Packet> feed(Uint8List data) {
|
||||
Stream<Packet> feed(Uint8List data) async* {
|
||||
final newBuffer = Uint8List(_buffer.length + data.length);
|
||||
newBuffer.setAll(0, _buffer);
|
||||
newBuffer.setAll(_buffer.length, data);
|
||||
_buffer = newBuffer;
|
||||
|
||||
if (_buffer.length > _maxBufferSize) {
|
||||
logger.e('PacketReceiver: переполнение буфера (${_buffer.length} B), сброс');
|
||||
logger.e(
|
||||
'PacketReceiver: переполнение буфера (${_buffer.length} B), сброс',
|
||||
);
|
||||
reset();
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
|
||||
final packets = <Packet>[];
|
||||
|
||||
while (_buffer.length >= headerSize) {
|
||||
final bd = ByteData.view(
|
||||
_buffer.buffer,
|
||||
@@ -42,13 +42,11 @@ class PacketReceiver {
|
||||
_buffer = _buffer.sublist(totalLength);
|
||||
|
||||
try {
|
||||
packets.add(unpackPacket(packetBytes));
|
||||
yield await unpackPacket(packetBytes);
|
||||
} catch (e) {
|
||||
logger.e('PacketReceiver: ошибка распаковки: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return packets;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
|
||||
Reference in New Issue
Block a user