More Codes

- Local Service Repository
This commit is contained in:
Linloir 2022-10-11 23:51:46 +08:00
parent 2bbad43739
commit d3a5a32fdb
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
9 changed files with 500 additions and 12 deletions

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() {
sqfliteFfiInit();
runApp(const MyApp());
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 10:30:05
* @LastEditTime : 2022-10-11 15:36:23
* @LastEditTime : 2022-10-11 23:35:45
* @Description :
*/
@ -32,6 +32,7 @@ class Message extends JSONEncodable {
final String _content;
final int _timestamp;
late final String _contentmd5;
late final String? _filemd5;
final LocalFile? _payload;
Message({
@ -54,6 +55,7 @@ class Message extends JSONEncodable {
..addAll(Uint8List(4)..buffer.asInt32List()[0] = targetid)
..addAll(Uint8List(4)..buffer.asInt32List()[0] = _timestamp)
).toString();
_filemd5 = _payload?.filemd5;
}
Message.fromJSONObject({
@ -66,6 +68,7 @@ class Message extends JSONEncodable {
_content = jsonObject['content'] as String,
_timestamp = jsonObject['timestamp'] as int,
_contentmd5 = jsonObject['md5encoded'] as String,
_filemd5 = jsonObject['filemd5'] as String,
_payload = payload;
int get senderID => _userid;
@ -75,6 +78,7 @@ class Message extends JSONEncodable {
String get contentEncoded => _content;
String get contentmd5 => _contentmd5;
int get timeStamp => _timestamp;
String? get filemd5 => _filemd5;
LocalFile? get payload => _payload;
@override

View File

@ -1,6 +1,250 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 10:56:02
* @LastEditTime : 2022-10-11 10:56:03
* @Description :
* @LastEditTime : 2022-10-11 23:49:16
* @Description : Local Service Repository
*/
import 'dart:async';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/repositories/common_models/message.dart';
import 'package:tcp_client/repositories/common_models/userinfo.dart';
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class LocalServiceRepository {
late final Database _database;
LocalServiceRepository._internal({
required Database database
}): _database = database;
static FutureOr<void> _onDatabaseCreate(Database db, int version) async {
await db.execute(
'''
create table users (
userid integer primary key,
username text not null,
avatar text
);
create table msgs (
userid integer not null,
targetid integer not null,
contenttype text not null,
content text not null,
timestamp int not null,
md5encoded text primary key,
filemd5 text not null
);
create table files (
filemd5 text primary key,
dir text not null
);
'''
);
}
static Future<LocalServiceRepository> create({
UserInfo? currentUser,
required String databaseFilePath
}) async {
var database = await databaseFactoryFfi.openDatabase(
databaseFilePath,
options: OpenDatabaseOptions(
version: 1,
onCreate: _onDatabaseCreate
)
);
return LocalServiceRepository._internal(database: database);
}
//Calls on the system to open the file
Future<LocalFile?> pickFile(FileType fileType) async {
var filePickResult = await FilePicker.platform.pickFiles(
type: fileType,
allowMultiple: false,
);
if (filePickResult == null) return null;
var file = File(filePickResult.files.single.path!);
return LocalFile(
file: file,
filemd5: md5.convert(await file.readAsBytes()).toString(),
ext: file.path.substring(file.path.lastIndexOf('.'))
);
}
Future<void> storeMessages(List<Message> messages) async {
for(var message in messages) {
try {
await _database.insert(
'msgs',
message.jsonObject,
conflictAlgorithm: ConflictAlgorithm.replace
);
} catch (err) {
//TODO: do something
}
}
}
Future<List<Message>> findMessages({required String pattern}) async {
// Obtain shared preferences.
final pref = await SharedPreferences.getInstance();
// Get user info from preferences
var currentUserID = pref.getInt('userid');
var alikeMessages = await _database.query(
'msgs',
where: 'userid = ? or targetid = ?',
whereArgs: [
currentUserID, currentUserID
],
orderBy: 'timestamp desc',
limit: 100
);
return alikeMessages.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
}
//Find the most recent message of given users
Future<List<List<Message>>> fetchMessageList({required List<int> users}) async {
var pref = await SharedPreferences.getInstance();
var currentUserID = pref.getInt('userid');
var messages = <List<Message>>[];
for(var user in users) {
var queryResult = await _database.query(
'msgs',
where: '(userid = ? and targetid = ?) and (userid = ? and targetid = ?)',
whereArgs: [
currentUserID, user, user, currentUserID
],
orderBy: 'timestamp desc',
limit: 1
);
if(queryResult.isEmpty) {
messages.add([]);
}
else {
messages.add([Message.fromJSONObject(jsonObject: queryResult[0])]);
}
}
return messages;
}
//Fetch chat history with another user, provided the user ID
Future<List<Message>> fetchMessageHistory({required int userID, required int position, int num = 20}) async {
//the histories with userID
var pref = await SharedPreferences.getInstance();
var currentUserID = pref.getInt('userid');
if(currentUserID == null) {
//TODO: do something
return [];
}
var queryResult = await _database.query(
'msgs',
where: '(userid = ? and targetid = ?) or (userid = ? and targetid = ?)',
whereArgs: [
currentUserID, userID, userID, currentUserID
],
orderBy: 'timestamp desc',
limit: num,
offset: position
);
return queryResult.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
}
Future<File?> findFile({required String filemd5}) async {
var directory = await _database.query(
'files',
where: 'filemd5 = ?',
whereArgs: [
filemd5
]
);
if(directory.isEmpty) {
return null;
}
else {
var filePath = directory[0]['dir'] as String;
//Try if the file exists
var file = File(filePath);
if(await file.exists()) {
return file;
}
else {
//Delete all linked files
await _database.delete(
'files',
where: 'filemd5 = ?',
whereArgs: [
filemd5
]
);
return null;
}
}
}
Future<void> storeFile({
required LocalFile tempFile
}) async {
//Write to file library
var documentPath = (await getApplicationDocumentsDirectory()).path;
var permanentFilePath = '$documentPath/files/${tempFile.filemd5}${tempFile.ext}';
await tempFile.file.copy(permanentFilePath);
await _database.insert(
'files',
{
'filemd5': tempFile.filemd5,
'dir': permanentFilePath
}
);
}
final StreamController<UserInfo> _userInfoChangeStreamController = StreamController();
Stream<UserInfo> get userInfoChangedStream => _userInfoChangeStreamController.stream;
Future<void> storeUserInfo({
required UserInfo userInfo
}) async {
//check if exist
var queryResult = await _database.query(
'users',
where: 'userid = ?',
whereArgs: [userInfo.userID]
);
if(queryResult.isEmpty) {
_database.insert(
'users',
userInfo.jsonObject
);
}
else {
_database.update(
'users',
userInfo.jsonObject,
where: 'userid = ?',
whereArgs: [userInfo.userID]
);
}
_userInfoChangeStreamController.add(userInfo);
}
Future<UserInfo?> fetchUserInfo({required userid}) async {
var targetUser = await _database.query(
'users',
where: 'userid = ?',
whereArgs: [userid]
);
if(targetUser.isEmpty) {
return null;
}
else {
return UserInfo.fromJSONObject(jsonObject: targetUser[0]);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 10:55:36
* @LastEditTime : 2022-10-11 17:44:06
* @LastEditTime : 2022-10-11 22:54:59
* @Description : Local File Model
*/
@ -12,8 +12,9 @@ import 'package:equatable/equatable.dart';
class LocalFile extends Equatable {
final File file;
final String filemd5;
final String ext;
const LocalFile({required this.file, required this.filemd5});
const LocalFile({required this.file, required this.filemd5, required this.ext});
@override
List<Object> get props => [filemd5];

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:02:19
* @LastEditTime : 2022-10-11 16:00:53
* @LastEditTime : 2022-10-11 22:55:48
* @Description :
*/
@ -195,12 +195,18 @@ class FindFileResponse extends TCPResponse {
}
class FetchFileResponse extends TCPResponse {
final LocalFile _payload;
late final LocalFile _payload;
FetchFileResponse({
required Map<String, Object?> jsonObject,
required LocalFile payload
}): _payload = payload, super(jsonObject: jsonObject);
}): super(jsonObject: jsonObject) {
_payload = LocalFile(
file: payload.file,
filemd5: payload.filemd5,
ext: (jsonObject['body'] as Map<String, Object?>)['ext'] as String
);
}
LocalFile get payload => _payload;
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 09:42:05
* @LastEditTime : 2022-10-11 17:41:11
* @LastEditTime : 2022-10-11 22:55:28
* @Description : TCP repository
*/
@ -11,12 +11,17 @@ import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
class TCPRepository {
TCPRepository(this._socket) {
TCPRepository({
required Socket socket,
required String remoteAddress,
required int remotePort
}): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort {
_socket.listen(_pullResponse);
//This future never ends, would that be bothersome?
Future(() async {
@ -33,6 +38,8 @@ class TCPRepository {
}
final Socket _socket;
final String _remoteAddress;
final int _remotePort;
//Stores the incoming bytes of the TCP connection temporarily
final List<int> buffer = [];
@ -50,7 +57,12 @@ class TCPRepository {
//Provide a response stream for blocs to listen on
final StreamController<TCPResponse> _responseStreamController = StreamController();
Stream<TCPResponse>? _responseStreamBroadcast;
Stream<TCPResponse> get responseStream => _responseStreamController.stream;
Stream<TCPResponse> get responseStreamBroadcast {
_responseStreamBroadcast ??= _responseStreamController.stream.asBroadcastStream();
return _responseStreamBroadcast!;
}
//Provide a request stream for widgets to push to
final StreamController<TCPRequest> _requestStreamController = StreamController();
@ -213,7 +225,8 @@ class TCPRepository {
jsonObject: responseObject,
payload: LocalFile(
file: tempFile,
filemd5: md5.convert(await tempFile.readAsBytes()).toString()
filemd5: md5.convert(await tempFile.readAsBytes()).toString(),
ext: ""
)
));
break;
@ -239,4 +252,37 @@ class TCPRepository {
}
}
}
Future<bool> checkFileExistence({
required LocalFile file
}) async {
//Duplicate current socket
Socket socket = await Socket.connect(_remoteAddress, _remotePort);
TCPRepository duplicatedRepository = TCPRepository(
socket: socket,
remoteAddress: _remoteAddress,
remotePort: _remotePort
);
var pref = await SharedPreferences.getInstance();
var request = FindFileRequest(file: file, token: pref.getInt('token')!);
duplicatedRepository.pushRequest(request);
var hasFile = false;
await for(var response in duplicatedRepository.responseStream) {
if(response.type == TCPResponseType.findFile) {
hasFile = response.status == TCPResponseStatus.ok;
break;
}
}
duplicatedRepository.dispose();
return hasFile;
}
void dispose() {
_responseRawStreamController.close();
_payloadPullStreamController.close();
_payloadRawStreamController.close();
_responseStreamController.close();
_requestStreamController.close();
_socket.close();
}
}

View File

@ -5,6 +5,10 @@
import FlutterMacOS
import Foundation
import path_provider_macos
import shared_preferences_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@ -85,6 +85,20 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
file:
dependency: transitive
description:
name: file
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.4"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.2.1"
flutter:
dependency: "direct main"
description: flutter
@ -104,11 +118,23 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
formz:
dependency: "direct main"
description:
@ -158,6 +184,15 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
open_file:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: fbf68e4bb5cb3e262d8f8ebe10b2f8449ff8e030
url: "https://github.com/crazecoder/open_file"
source: git
version: "3.2.2"
path:
dependency: transitive
description:
@ -165,6 +200,76 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.2"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.11"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.20"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.11"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.6"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.5"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
process:
dependency: transitive
description:
name: process
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.4"
provider:
dependency: transitive
description:
@ -172,6 +277,62 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.3"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.15"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.13"
shared_preferences_ios:
dependency: transitive
description:
name: shared_preferences_ios
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.4"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.4"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
sky_engine:
dependency: transitive
description: flutter
@ -261,6 +422,20 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0+2"
sdks:
dart: ">=2.18.2 <3.0.0"
flutter: ">=1.16.0"
flutter: ">=3.0.0"

View File

@ -32,6 +32,7 @@ dependencies:
flutter:
sdk: flutter
path_provider: ^2.0.11
sqflite_common_ffi: ^2.1.1+1
sqflite_common: ^2.3.0
crypto: ^3.0.2
@ -40,6 +41,11 @@ dependencies:
formz: ^0.4.1
bloc: ^8.1.0
flutter_bloc: ^8.1.1
file_picker: ^5.2.1
open_file:
git:
url: https://github.com/crazecoder/open_file
shared_preferences: ^2.0.15
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.