mirror of https://github.com/CympleTech/ESSE.git
22 changed files with 1058 additions and 182 deletions
After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1,391 @@
@@ -0,0 +1,391 @@
|
||||
import 'dart:io'; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:image_gallery_saver/image_gallery_saver.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:permission_handler/permission_handler.dart'; |
||||
import 'package:open_file/open_file.dart'; |
||||
|
||||
import 'package:esse/l10n/localizations.dart'; |
||||
import 'package:esse/utils/adaptive.dart'; |
||||
import 'package:esse/utils/file_image.dart'; |
||||
import 'package:esse/utils/better_print.dart'; |
||||
import 'package:esse/widgets/avatar.dart'; |
||||
import 'package:esse/widgets/audio_player.dart'; |
||||
import 'package:esse/widgets/shadow_dialog.dart'; |
||||
import 'package:esse/global.dart'; |
||||
|
||||
import 'package:esse/apps/assistant/models.dart'; |
||||
|
||||
class AssistantMessage extends StatelessWidget { |
||||
final String name; |
||||
final Message message; |
||||
|
||||
const AssistantMessage({Key key, this.name, this.message}): super(key: key); |
||||
|
||||
Widget _showText(context, color, isDesktop, content, isMe) { |
||||
final width = MediaQuery.of(context).size.width * 0.6; |
||||
// text |
||||
return Container( |
||||
constraints: BoxConstraints(minWidth: 50, maxWidth: isDesktop ? width - 300.0 : width), |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 14.0), |
||||
decoration: BoxDecoration( |
||||
color: isMe ? color.primary : color.primaryVariant, |
||||
borderRadius: BorderRadius.circular(15.0), |
||||
), |
||||
child: Text(content, |
||||
style: TextStyle( |
||||
color: isMe ? Colors.white : Color(0xFF1C1939), |
||||
fontSize: 14.0))); |
||||
} |
||||
|
||||
Widget _showImage(context, lang, color, content) { |
||||
// image |
||||
bool imageExsit = true; |
||||
var thumImage; |
||||
final imagePath = Global.imagePath + content; |
||||
final thumPath = Global.thumbPath + content; |
||||
if (FileSystemEntity.typeSync(thumPath) == |
||||
FileSystemEntityType.notFound) { |
||||
imageExsit = false; |
||||
thumImage = AssetImage('assets/images/image_missing.png'); |
||||
} else { |
||||
thumImage = FileImage(File(thumPath)); |
||||
} |
||||
return GestureDetector( |
||||
onTap: imageExsit |
||||
? () => showShadowDialog( |
||||
context, |
||||
Icons.image_rounded, |
||||
lang.album, |
||||
Column(children: [ |
||||
Image(image: FileImage(File(imagePath)), fit: BoxFit.cover), |
||||
SizedBox(height: 15.0), |
||||
if (Platform.isAndroid || Platform.isIOS) |
||||
InkWell( |
||||
onTap: () async { |
||||
Map<Permission, PermissionStatus> statuses = await [ |
||||
Permission.storage, |
||||
].request(); |
||||
|
||||
if (statuses[Permission.storage] == PermissionStatus.granted) { |
||||
final result = await ImageGallerySaver.saveFile(imagePath); |
||||
print(result); |
||||
Navigator.pop(context); |
||||
} |
||||
}, |
||||
hoverColor: Colors.transparent, |
||||
child: Container( |
||||
width: 200.0, |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0), |
||||
decoration: BoxDecoration( |
||||
border: Border.all(color: color.primary), |
||||
borderRadius: BorderRadius.circular(10.0)), |
||||
child: Center(child: Text(lang.download, |
||||
style: TextStyle(fontSize: 14.0, color: color.primary))), |
||||
) |
||||
), |
||||
])) |
||||
: () => {}, |
||||
child: Container( |
||||
width: imageExsit ? 120.0 : 60.0, |
||||
child: Image(image: thumImage, fit: BoxFit.cover), |
||||
)); |
||||
} |
||||
|
||||
Widget _showFile(context, lang, color, content) { |
||||
// file |
||||
bool fileExsit = true; |
||||
Widget fileImage; |
||||
final filePath = Global.filePath + content; |
||||
if (FileSystemEntity.typeSync(filePath) == |
||||
FileSystemEntityType.notFound) { |
||||
fileExsit = false; |
||||
fileImage = Image(image: AssetImage('assets/images/image_missing.png'), fit: BoxFit.cover); |
||||
} else { |
||||
fileImage = fileIcon(content, 36.0); |
||||
} |
||||
return GestureDetector( |
||||
onTap: fileExsit |
||||
? () => showShadowDialog( |
||||
context, |
||||
Icons.folder_rounded, |
||||
lang.files, |
||||
Column(children: [ |
||||
Text(content), |
||||
SizedBox(height: 15.0), |
||||
Container( |
||||
height: 100.0, |
||||
child: fileImage, |
||||
), |
||||
SizedBox(height: 15.0), |
||||
InkWell( |
||||
onTap: () => OpenFile.open(filePath), |
||||
hoverColor: Colors.transparent, |
||||
child: Container( |
||||
width: 200.0, |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0), |
||||
decoration: BoxDecoration( |
||||
border: Border.all(color: color.primary), |
||||
borderRadius: BorderRadius.circular(10.0)), |
||||
child: Center(child: Text(lang.open, |
||||
style: TextStyle(fontSize: 14.0, color: color.primary))), |
||||
) |
||||
), |
||||
])) |
||||
: () => {}, |
||||
child: Container( |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), |
||||
decoration: BoxDecoration( |
||||
color: const Color(0x40ADB0BB), |
||||
borderRadius: BorderRadius.circular(15.0), |
||||
), |
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [ |
||||
Container( |
||||
height: 36.0, |
||||
child: fileImage, |
||||
), |
||||
Container( |
||||
padding: const EdgeInsets.only(left: 5.0), |
||||
width: 120.0, |
||||
child: Text(content, |
||||
maxLines: 1, |
||||
overflow: TextOverflow.ellipsis, |
||||
style: fileExsit |
||||
? TextStyle( |
||||
color: color.onPrimary, |
||||
fontSize: 14.0, |
||||
) |
||||
: TextStyle( |
||||
color: color.onPrimary.withOpacity(0.5), |
||||
decoration: TextDecoration.lineThrough, |
||||
fontSize: 14.0, |
||||
)), |
||||
), |
||||
]))); |
||||
} |
||||
|
||||
Widget _showRecord(content) { |
||||
final raws = Message.showRecordTime(content); |
||||
// text |
||||
return Container( |
||||
width: 120.0, |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), |
||||
decoration: BoxDecoration( |
||||
color: const Color(0x40ADB0BB), |
||||
borderRadius: BorderRadius.circular(15.0), |
||||
), |
||||
child: RecordPlayer(path: Global.recordPath + raws[1], time: raws[0]), |
||||
); |
||||
} |
||||
|
||||
Widget _showContact(context, lang, color, content) { |
||||
// contact [name, gid, addr, avatar] |
||||
final infos = Message.showContact(content); |
||||
final gid = 'EH' + infos[1].toUpperCase(); |
||||
|
||||
if (infos != null) { |
||||
return GestureDetector( |
||||
onTap: () => showShadowDialog( |
||||
context, |
||||
Icons.person_rounded, |
||||
lang.contact, |
||||
Column(children: [ |
||||
Avatar( |
||||
width: 100.0, |
||||
name: infos[0], |
||||
avatarPath: infos[3], |
||||
needOnline: false |
||||
), |
||||
const SizedBox(height: 10.0), |
||||
Text(infos[0]), |
||||
const SizedBox(height: 10.0), |
||||
const Divider(height: 1.0, color: Color(0x40ADB0BB)), |
||||
const SizedBox(height: 10.0), |
||||
_infoListTooltip(Icons.person, color.primary, gid), |
||||
_infoListTooltip(Icons.location_on, color.primary, "0x" + infos[2]), |
||||
Container( |
||||
width: 300.0, |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0), |
||||
child: Row( |
||||
children: [ |
||||
Icon(Icons.turned_in, size: 20.0, color: color.primary), |
||||
const SizedBox(width: 20.0), |
||||
Expanded(child: Text(lang.fromContactCard(name))), |
||||
] |
||||
), |
||||
), |
||||
const SizedBox(height: 20.0), |
||||
InkWell( |
||||
onTap: () => Navigator.pop(context), |
||||
hoverColor: Colors.transparent, |
||||
child: Container( |
||||
width: 200.0, |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0), |
||||
decoration: BoxDecoration( |
||||
border: Border.all(color: color.primary), |
||||
borderRadius: BorderRadius.circular(10.0)), |
||||
child: Center(child: Text(lang.ok, |
||||
style: TextStyle(fontSize: 14.0, color: color.primary))), |
||||
) |
||||
), |
||||
] |
||||
) |
||||
), |
||||
child: Container( |
||||
padding: const EdgeInsets.symmetric( |
||||
vertical: 10.0, horizontal: 10.0), |
||||
width: 200.0, |
||||
decoration: BoxDecoration( |
||||
color: const Color(0x40ADB0BB), |
||||
borderRadius: BorderRadius.circular(15.0), |
||||
), |
||||
child: Column( |
||||
crossAxisAlignment: CrossAxisAlignment.start, |
||||
children: [ |
||||
Row(children: [ |
||||
Avatar( |
||||
width: 40.0, |
||||
name: infos[0], |
||||
avatarPath: infos[3], |
||||
needOnline: false), |
||||
Container( |
||||
width: 135.0, |
||||
padding: const EdgeInsets.only(left: 10.0), |
||||
child: Column(children: [ |
||||
Text(infos[0], |
||||
maxLines: 1, |
||||
overflow: TextOverflow.ellipsis, |
||||
style: TextStyle( |
||||
color: color.onPrimary, fontSize: 16.0)), |
||||
SizedBox(height: 5.0), |
||||
Text(betterPrint(gid), |
||||
style: TextStyle( |
||||
color: Colors.grey, fontSize: 12.0)), |
||||
])), |
||||
]), |
||||
SizedBox(height: 5.0), |
||||
const Divider(height: 1.0, color: Color(0x40ADB0BB)), |
||||
SizedBox(height: 3.0), |
||||
Text(lang.contactCard, |
||||
style: TextStyle(color: Colors.grey, fontSize: 10.0)), |
||||
]))); |
||||
} else { |
||||
return Container( |
||||
padding: |
||||
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), |
||||
width: 200.0, |
||||
decoration: BoxDecoration( |
||||
color: const Color(0x40ADB0BB), |
||||
borderRadius: BorderRadius.circular(15.0), |
||||
), |
||||
child: |
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ |
||||
Row(children: [ |
||||
Container( |
||||
height: 35.0, |
||||
child: Image( |
||||
image: AssetImage('assets/images/image_missing.png'), |
||||
fit: BoxFit.cover), |
||||
), |
||||
Container( |
||||
width: 130.0, |
||||
padding: const EdgeInsets.only(left: 10.0), |
||||
child: Text(content, |
||||
maxLines: 1, |
||||
overflow: TextOverflow.ellipsis, |
||||
style: TextStyle( |
||||
color: color.onPrimary.withOpacity(0.5), |
||||
decoration: TextDecoration.lineThrough, |
||||
fontSize: 16.0)), |
||||
), |
||||
]), |
||||
SizedBox(height: 5.0), |
||||
const Divider(height: 1.0, color: Color(0x40ADB0BB)), |
||||
SizedBox(height: 3.0), |
||||
Text(lang.contactCard, |
||||
style: TextStyle(color: Colors.grey, fontSize: 10.0)), |
||||
])); |
||||
} |
||||
} |
||||
|
||||
Widget _infoListTooltip(icon, color, text) { |
||||
return Container( |
||||
width: 300.0, |
||||
padding: const EdgeInsets.symmetric(vertical: 10.0), |
||||
child: Row( |
||||
children: [ |
||||
Icon(icon, size: 20.0, color: color), |
||||
const SizedBox(width: 20.0), |
||||
Expanded( |
||||
child: Tooltip( |
||||
message: text, |
||||
child: Text(betterPrint(text)), |
||||
) |
||||
) |
||||
] |
||||
), |
||||
); |
||||
} |
||||
|
||||
Widget _show(context, color, lang, isDesktop, type, content, isMe) { |
||||
if (type == MessageType.String) { |
||||
return _showText(context, color, isDesktop, content, isMe); |
||||
} else if (type == MessageType.Image) { |
||||
return _showImage(context, lang, color, content); |
||||
} else if (type == MessageType.File) { |
||||
return _showFile(context, lang, color, content); |
||||
} else if (type == MessageType.Contact) { |
||||
return _showContact(context, lang, color, content); |
||||
} else if (type == MessageType.Record) { |
||||
return _showRecord(content); |
||||
} |
||||
return _showText(context, color, isDesktop, content, isMe); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final color = Theme.of(context).colorScheme; |
||||
final lang = AppLocalizations.of(context); |
||||
final isDesktop = isDisplayDesktop(context); |
||||
final qShow = _show(context, color, lang, isDesktop, message.q_type, message.q_content, true); |
||||
final aShow = _show(context, color, lang, isDesktop, message.a_type, message.a_content, false); |
||||
|
||||
return Padding( |
||||
padding: const EdgeInsets.symmetric(vertical: 5.0), |
||||
child: Column(children: [ |
||||
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ |
||||
Expanded( |
||||
child: Align( |
||||
alignment: Alignment.topRight, |
||||
child: qShow, |
||||
), |
||||
), |
||||
]), |
||||
Container( |
||||
padding: EdgeInsets.only(top: 10.0), |
||||
child: Row(children: [ |
||||
Spacer(), |
||||
Text(message.time.toString(), |
||||
style: TextStyle( |
||||
color: color.onPrimary.withOpacity(0.5), |
||||
fontSize: 12.0)), |
||||
SizedBox(width: 4.0), |
||||
Icon( |
||||
message.a_content.length == 0 ? Icons.hourglass_top : Icons.done, |
||||
size: 12.0, |
||||
color: color.primary |
||||
), |
||||
])), |
||||
const SizedBox(height: 4.0), |
||||
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ |
||||
Expanded( |
||||
child: Align( |
||||
alignment: Alignment.topLeft, |
||||
child: aShow, |
||||
), |
||||
), |
||||
]), |
||||
])); |
||||
} |
||||
} |
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
import 'package:esse/utils/relative_time.dart'; |
||||
import 'package:esse/global.dart'; |
||||
|
||||
enum MessageType { |
||||
String, |
||||
Image, |
||||
File, |
||||
Contact, |
||||
Emoji, |
||||
Record, |
||||
} |
||||
|
||||
// use 00-99 |
||||
extension MessageTypeExtension on MessageType { |
||||
int toInt() { |
||||
switch (this) { |
||||
case MessageType.String: |
||||
return 0; |
||||
case MessageType.Image: |
||||
return 1; |
||||
case MessageType.File: |
||||
return 2; |
||||
case MessageType.Contact: |
||||
return 3; |
||||
case MessageType.Emoji: |
||||
return 4; |
||||
case MessageType.Record: |
||||
return 5; |
||||
default: |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
static MessageType fromInt(int s) { |
||||
switch (s) { |
||||
case 0: |
||||
return MessageType.String; |
||||
case 1: |
||||
return MessageType.Image; |
||||
case 2: |
||||
return MessageType.File; |
||||
case 3: |
||||
return MessageType.Contact; |
||||
case 4: |
||||
return MessageType.Emoji; |
||||
case 5: |
||||
return MessageType.Record; |
||||
default: |
||||
return MessageType.String; |
||||
} |
||||
} |
||||
} |
||||
|
||||
class Message { |
||||
int id; |
||||
MessageType q_type; |
||||
String q_content; |
||||
MessageType a_type; |
||||
String a_content; |
||||
RelativeTime time = RelativeTime(); |
||||
|
||||
Message(this.q_type, this.q_content); |
||||
|
||||
static List showContact(String content) { |
||||
var name = ''; |
||||
var did = ''; |
||||
var addr = ''; |
||||
|
||||
var i_name = content.indexOf(';;'); |
||||
if (i_name > 0) { |
||||
name = content.substring(0, i_name).replaceAll('-;', ';'); |
||||
} |
||||
var raw = content.substring(i_name + 2); |
||||
var i_did = raw.indexOf(';;'); |
||||
if (i_did > 0) { |
||||
did = raw.substring(0, i_did); |
||||
} |
||||
addr = raw.substring(i_did + 2); |
||||
|
||||
return [name, did, addr, Global.avatarPath + did + '.png']; |
||||
} |
||||
|
||||
static String rawRecordName(int time, String name) { |
||||
return time.toString() + '-' + name; |
||||
} |
||||
|
||||
static List showRecordTime(String content) { |
||||
final len = content.indexOf('-'); |
||||
if (len > 0) { |
||||
final time = int.parse(content.substring(0, len)); |
||||
final path = content.substring(len + 1); |
||||
return [time, path]; |
||||
} else { |
||||
return [0, content]; |
||||
} |
||||
} |
||||
|
||||
String shortShow() { |
||||
switch (this.q_type) { |
||||
case MessageType.Image: |
||||
return '[IMAGE]'; |
||||
case MessageType.Record: |
||||
return '[RECORD]'; |
||||
case MessageType.Contact: |
||||
return '[CONTACT CARD]'; |
||||
default: |
||||
return this.q_content; |
||||
} |
||||
} |
||||
|
||||
Message.fromList(List params) { |
||||
this.id = params[0]; |
||||
this.q_type = MessageTypeExtension.fromInt(params[1]); |
||||
this.q_content = params[2]; |
||||
this.a_type = MessageTypeExtension.fromInt(params[3]); |
||||
this.a_content = params[4]; |
||||
this.time = RelativeTime.fromInt(params[5]); |
||||
} |
||||
|
||||
update(List params) { |
||||
// params[0] is id. |
||||
this.a_type = MessageTypeExtension.fromInt(params[1]); |
||||
this.a_content = params[2]; |
||||
} |
||||
} |
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
import "dart:collection"; |
||||
import 'package:flutter/material.dart'; |
||||
|
||||
import 'package:esse/rpc.dart'; |
||||
import 'package:esse/apps/assistant/models.dart'; |
||||
|
||||
class AssistantProvider extends ChangeNotifier { |
||||
bool isActived = false; |
||||
SplayTreeMap<int, Message> messages = SplayTreeMap(); |
||||
|
||||
AssistantProvider() { |
||||
// rpc. |
||||
rpc.addListener('assistant-list', _list, false); |
||||
rpc.addListener('assistant-create', _create, false); |
||||
rpc.addListener('assistant-update', _update, false); |
||||
rpc.addListener('assistant-delete', _delete, false); |
||||
} |
||||
|
||||
actived() { |
||||
this.isActived = true; |
||||
rpc.send('assistant-list', []); |
||||
} |
||||
|
||||
inactived() { |
||||
this.messages.clear(); |
||||
this.isActived = false; |
||||
print("============"); |
||||
} |
||||
|
||||
create(MessageType q_type, String q_content) { |
||||
rpc.send('assistant-create', [q_type.toInt(), q_content]); |
||||
notifyListeners(); |
||||
} |
||||
|
||||
/// delete a message. |
||||
delete(int id) { |
||||
this.messages.remove(id); |
||||
rpc.send('assistant-delete', [id]); |
||||
notifyListeners(); |
||||
} |
||||
|
||||
/// list message with friend. |
||||
_list(List params) { |
||||
if (this.isActived) { |
||||
params.forEach((param) { |
||||
this.messages[param[0]] = Message.fromList(param); |
||||
}); |
||||
notifyListeners(); |
||||
} |
||||
} |
||||
|
||||
/// friend send message to me. |
||||
_create(List params) { |
||||
if (this.isActived) { |
||||
final msg = Message.fromList(params); |
||||
this.messages[msg.id] = msg; |
||||
notifyListeners(); |
||||
} |
||||
} |
||||
|
||||
_update(List params) { |
||||
if (this.isActived) { |
||||
final id = params[0]; |
||||
if (this.messages.containsKey(id)) { |
||||
this.messages[id].update(params); |
||||
notifyListeners(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
_delete(List params) { |
||||
if (this.isActived) { |
||||
this.messages.remove(params[0]); |
||||
notifyListeners(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
#[rustfmt::skip] |
||||
pub(crate) const ASSISTANT_VERSIONS: [&str; 1] = [ |
||||
"CREATE TABLE IF NOT EXISTS messages( |
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, |
||||
q_type INTEGER NOT NULL, |
||||
q_content TEXT NOT NULL, |
||||
a_type INTEGER NOT NULL, |
||||
a_content TEXT NOT NULL, |
||||
datetime INTEGER NOT NULL, |
||||
is_deleted INTEGER NOT NULL);", |
||||
]; |
@ -1,12 +1,7 @@
@@ -1,12 +1,7 @@
|
||||
use tdn::types::{ |
||||
primitive::HandleResult, |
||||
rpc::{json, RpcHandler}, |
||||
}; |
||||
mod migrate; |
||||
mod models; |
||||
pub(crate) mod rpc; |
||||
|
||||
use crate::rpc::RpcState; |
||||
|
||||
pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { |
||||
handler.add_method("assistant-echo", |_, params, _| async move { |
||||
Ok(HandleResult::rpc(json!(params))) |
||||
}); |
||||
} |
||||
pub(crate) use migrate::ASSISTANT_VERSIONS; |
||||
pub(crate) use models::{Message, MessageType}; |
||||
pub(crate) use rpc::new_rpc_handler; |
||||
|
@ -0,0 +1,183 @@
@@ -0,0 +1,183 @@
|
||||
use std::path::PathBuf; |
||||
use std::time::{SystemTime, UNIX_EPOCH}; |
||||
use tdn::types::{ |
||||
group::GroupId, |
||||
primitive::{new_io_error, Result}, |
||||
rpc::{json, RpcParam}, |
||||
}; |
||||
use tdn_storage::local::{DStorage, DsValue}; |
||||
|
||||
use crate::apps::chat::Friend; |
||||
use crate::storage::{read_file, session_db, write_file, write_image}; |
||||
|
||||
#[derive(Eq, PartialEq, Clone)] |
||||
pub(crate) enum MessageType { |
||||
String, |
||||
Image, |
||||
File, |
||||
Contact, |
||||
Emoji, |
||||
Record, |
||||
} |
||||
|
||||
impl MessageType { |
||||
pub fn to_int(&self) -> i64 { |
||||
match self { |
||||
MessageType::String => 0, |
||||
MessageType::Image => 1, |
||||
MessageType::File => 2, |
||||
MessageType::Contact => 3, |
||||
MessageType::Emoji => 4, |
||||
MessageType::Record => 5, |
||||
} |
||||
} |
||||
|
||||
pub fn from_int(i: i64) -> MessageType { |
||||
match i { |
||||
0 => MessageType::String, |
||||
1 => MessageType::Image, |
||||
2 => MessageType::File, |
||||
3 => MessageType::Contact, |
||||
4 => MessageType::Emoji, |
||||
5 => MessageType::Record, |
||||
_ => MessageType::String, |
||||
} |
||||
} |
||||
|
||||
pub async fn handle( |
||||
&self, |
||||
base: &PathBuf, |
||||
mgid: &GroupId, |
||||
content: String, |
||||
) -> std::result::Result<String, tdn::types::rpc::RpcError> { |
||||
match self { |
||||
MessageType::String => Ok(content), |
||||
MessageType::Image => { |
||||
let bytes = read_file(&PathBuf::from(content)).await?; |
||||
let image_name = write_image(base, &mgid, &bytes).await?; |
||||
Ok(image_name) |
||||
} |
||||
MessageType::File => { |
||||
let file_path = PathBuf::from(content); |
||||
let bytes = read_file(&file_path).await?; |
||||
let old_name = file_path.file_name()?.to_str()?; |
||||
let filename = write_file(base, mgid, old_name, &bytes).await?; |
||||
Ok(filename) |
||||
} |
||||
MessageType::Contact => { |
||||
let cid: i64 = content.parse().map_err(|_e| new_io_error("id error"))?; |
||||
let db = session_db(base, mgid)?; |
||||
let contact = Friend::get_id(&db, cid)??; |
||||
db.close()?; |
||||
let tmp_name = contact.name.replace(";", "-;"); |
||||
Ok(format!( |
||||
"{};;{};;{}", |
||||
tmp_name, |
||||
contact.gid.to_hex(), |
||||
contact.addr.to_hex() |
||||
)) |
||||
} |
||||
MessageType::Record => Ok(content), |
||||
MessageType::Emoji => Ok(content), |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub(crate) struct Message { |
||||
pub id: i64, |
||||
pub q_type: MessageType, |
||||
pub q_content: String, |
||||
pub a_type: MessageType, |
||||
pub a_content: String, |
||||
pub datetime: i64, |
||||
} |
||||
|
||||
impl Message { |
||||
pub fn new( |
||||
q_type: MessageType, |
||||
q_content: String, |
||||
a_type: MessageType, |
||||
a_content: String, |
||||
) -> Message { |
||||
let start = SystemTime::now(); |
||||
let datetime = start |
||||
.duration_since(UNIX_EPOCH) |
||||
.map(|s| s.as_secs()) |
||||
.unwrap_or(0) as i64; // safe for all life.
|
||||
|
||||
Message { |
||||
id: 0, |
||||
q_type, |
||||
q_content, |
||||
a_type, |
||||
a_content, |
||||
datetime, |
||||
} |
||||
} |
||||
|
||||
/// here is zero-copy and unwrap is safe. checked.
|
||||
fn from_values(mut v: Vec<DsValue>) -> Message { |
||||
Message { |
||||
datetime: v.pop().unwrap().as_i64(), |
||||
a_content: v.pop().unwrap().as_string(), |
||||
a_type: MessageType::from_int(v.pop().unwrap().as_i64()), |
||||
q_content: v.pop().unwrap().as_string(), |
||||
q_type: MessageType::from_int(v.pop().unwrap().as_i64()), |
||||
id: v.pop().unwrap().as_i64(), |
||||
} |
||||
} |
||||
|
||||
pub fn to_rpc(&self) -> RpcParam { |
||||
json!([ |
||||
self.id, |
||||
self.q_type.to_int(), |
||||
self.q_content, |
||||
self.a_type.to_int(), |
||||
self.a_content, |
||||
self.datetime, |
||||
]) |
||||
} |
||||
|
||||
pub fn all(db: &DStorage) -> Result<Vec<Message>> { |
||||
let sql = |
||||
format!("SELECT id, q_type, q_content, a_type, a_content, datetime FROM messages where is_deleted = false"); |
||||
let matrix = db.query(&sql)?; |
||||
let mut messages = vec![]; |
||||
for values in matrix { |
||||
messages.push(Message::from_values(values)); |
||||
} |
||||
|
||||
Ok(messages) |
||||
} |
||||
|
||||
pub fn _get(db: &DStorage, id: &i64) -> Result<Option<Message>> { |
||||
let sql = format!( |
||||
"SELECT id, q_type, q_content, a_type, a_content, datetime FROM messages WHERE id = {}", |
||||
id |
||||
); |
||||
let mut matrix = db.query(&sql)?; |
||||
if matrix.len() > 0 { |
||||
Ok(Some(Message::from_values(matrix.pop().unwrap()))) |
||||
} else { |
||||
Ok(None) |
||||
} |
||||
} |
||||
|
||||
pub fn insert(&mut self, db: &DStorage) -> Result<()> { |
||||
let sql = format!( |
||||
"INSERT INTO messages (q_type, q_content, a_type, a_content, datetime, is_deleted) VALUES ({},'{}',{},'{}',{},false)", |
||||
self.q_type.to_int(), |
||||
self.q_content, |
||||
self.a_type.to_int(), |
||||
self.a_content, |
||||
self.datetime, |
||||
); |
||||
self.id = db.insert(&sql)?; |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn delete(db: &DStorage, id: i64) -> Result<usize> { |
||||
let sql = format!("UPDATE messages SET is_deleted = true WHERE id = {}", id); |
||||
db.delete(&sql) |
||||
} |
||||
} |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
use std::sync::Arc; |
||||
use tdn::types::{ |
||||
group::GroupId, |
||||
primitive::HandleResult, |
||||
rpc::{json, rpc_response, RpcHandler, RpcParam}, |
||||
}; |
||||
|
||||
use crate::rpc::RpcState; |
||||
use crate::storage::assistant_db; |
||||
|
||||
use super::{Message, MessageType}; |
||||
|
||||
#[inline] |
||||
pub(crate) fn _assistant_create(mgid: GroupId, device: &Message) -> RpcParam { |
||||
rpc_response(0, "assistant-create", json!(device.to_rpc()), mgid) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn _assistant_delete(mgid: GroupId, id: i64) -> RpcParam { |
||||
rpc_response(0, "assistant-delete", json!([id]), mgid) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn _assistant_update(mgid: GroupId, id: i64, message: &Message) -> RpcParam { |
||||
rpc_response( |
||||
0, |
||||
"assistant-update", |
||||
json!([id, message.a_type.to_int(), message.a_content]), |
||||
mgid, |
||||
) |
||||
} |
||||
|
||||
pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { |
||||
handler.add_method("assistant-echo", |_, params, _| async move { |
||||
Ok(HandleResult::rpc(json!(params))) |
||||
}); |
||||
|
||||
handler.add_method( |
||||
"assistant-list", |
||||
|gid: GroupId, _params: Vec<RpcParam>, state: Arc<RpcState>| async move { |
||||
let db = assistant_db(state.layer.read().await.base(), &gid)?; |
||||
let devices = Message::all(&db)?; |
||||
db.close()?; |
||||
let mut results = vec![]; |
||||
for device in devices { |
||||
results.push(device.to_rpc()); |
||||
} |
||||
Ok(HandleResult::rpc(json!(results))) |
||||
}, |
||||
); |
||||
|
||||
handler.add_method( |
||||
"assistant-create", |
||||
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move { |
||||
let q_type = MessageType::from_int(params[0].as_i64()?); |
||||
let q_content = params[1].as_str()?.to_string(); |
||||
|
||||
let base = state.layer.read().await.base().clone(); |
||||
let q_raw = q_type.handle(&base, &gid, q_content).await?; |
||||
|
||||
// echo
|
||||
let a_type = q_type.clone(); |
||||
let a_content = q_raw.clone(); |
||||
|
||||
let mut msg = Message::new(q_type, q_raw, a_type, a_content); |
||||
let db = assistant_db(state.layer.read().await.base(), &gid)?; |
||||
msg.insert(&db)?; |
||||
db.close()?; |
||||
|
||||
let results = HandleResult::rpc(json!(msg.to_rpc())); |
||||
Ok(results) |
||||
}, |
||||
); |
||||
|
||||
handler.add_method( |
||||
"assistant-delete", |
||||
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move { |
||||
let id = params[0].as_i64()?; |
||||
let db = assistant_db(state.layer.read().await.base(), &gid)?; |
||||
Message::delete(&db, id)?; |
||||
db.close()?; |
||||
Ok(HandleResult::new()) |
||||
}, |
||||
); |
||||
} |
@ -1,4 +1,3 @@
@@ -1,4 +1,3 @@
|
||||
#[rustfmt::skip] |
||||
pub(super) const SERVICE_VERSIONS: [&str; 0] = [ |
||||
|
||||
]; |
||||
|
Loading…
Reference in new issue