2022-03-16 15:33:00 +08:00
import ' dart:async ' ;
2022-03-07 22:54:34 +08:00
import ' dart:convert ' ;
2022-12-04 23:44:03 +09:00
import ' dart:io ' ;
2022-06-13 21:07:26 +08:00
2022-03-07 22:54:34 +08:00
import ' package:flutter/material.dart ' ;
2022-06-13 21:07:26 +08:00
import ' package:flutter_hbb/common.dart ' ;
2023-03-15 10:43:27 +08:00
import ' package:flutter_hbb/utils/event_loop.dart ' ;
2022-07-09 19:14:40 +08:00
import ' package:get/get.dart ' ;
2022-10-19 23:29:45 +09:00
import ' package:path/path.dart ' as path ;
2022-03-12 21:42:05 +08:00
2023-03-16 11:23:15 +09:00
import ' ../consts.dart ' ;
2022-03-07 22:54:34 +08:00
import ' model.dart ' ;
2022-08-03 22:03:31 +08:00
import ' platform_model.dart ' ;
2022-03-07 22:54:34 +08:00
2022-10-20 10:31:31 +09:00
enum SortBy {
name ,
type ,
modified ,
size ;
@ override
String toString ( ) {
final str = this . name . toString ( ) ;
return " ${ str [ 0 ] . toUpperCase ( ) } ${ str . substring ( 1 ) } " ;
}
}
2022-03-07 22:54:34 +08:00
2023-03-08 00:49:14 +09:00
class JobID {
int _count = 0 ;
int next ( ) {
_count + + ;
return _count ;
}
}
2023-06-06 07:39:44 +08:00
typedef GetSessionID = SessionID Function ( ) ;
2023-03-08 00:49:14 +09:00
2023-03-08 21:05:55 +09:00
class FileModel {
final WeakReference < FFI > parent ;
2023-06-06 07:39:44 +08:00
// late final String sessionId;
2023-03-08 22:32:55 +09:00
late final FileFetcher fileFetcher ;
late final JobController jobController ;
2022-07-09 19:14:40 +08:00
2023-03-08 22:32:55 +09:00
late final FileController localController ;
late final FileController remoteController ;
2022-06-13 21:07:26 +08:00
2023-03-08 22:32:55 +09:00
late final GetSessionID getSessionID ;
2023-06-06 07:39:44 +08:00
SessionID get sessionId = > getSessionID ( ) ;
2023-03-15 15:13:23 +08:00
late final FileDialogEventLoop evtLoop ;
2022-06-13 21:07:26 +08:00
2023-03-08 22:32:55 +09:00
FileModel ( this . parent ) {
2023-06-06 07:39:44 +08:00
getSessionID = ( ) = > parent . target ! . sessionId ;
2023-03-08 22:32:55 +09:00
fileFetcher = FileFetcher ( getSessionID ) ;
jobController = JobController ( getSessionID ) ;
localController = FileController (
isLocal: true ,
getSessionID: getSessionID ,
2023-03-16 11:23:15 +09:00
rootState: parent ,
2023-03-08 22:32:55 +09:00
jobController: jobController ,
2023-03-08 23:06:34 +09:00
fileFetcher: fileFetcher ,
getOtherSideDirectoryData: ( ) = > remoteController . directoryData ( ) ) ;
2023-03-08 22:32:55 +09:00
remoteController = FileController (
isLocal: false ,
getSessionID: getSessionID ,
2023-03-16 11:23:15 +09:00
rootState: parent ,
2023-03-08 22:32:55 +09:00
jobController: jobController ,
2023-03-08 23:06:34 +09:00
fileFetcher: fileFetcher ,
getOtherSideDirectoryData: ( ) = > localController . directoryData ( ) ) ;
2023-03-15 15:13:23 +08:00
evtLoop = FileDialogEventLoop ( ) ;
2023-03-08 22:32:55 +09:00
}
2022-03-11 01:28:13 +08:00
2023-03-08 21:05:55 +09:00
Future < void > onReady ( ) async {
2023-03-15 10:43:27 +08:00
await evtLoop . onReady ( ) ;
2023-03-08 21:05:55 +09:00
await localController . onReady ( ) ;
await remoteController . onReady ( ) ;
2022-03-11 01:28:13 +08:00
}
2023-03-08 21:05:55 +09:00
Future < void > close ( ) async {
2023-03-15 10:43:27 +08:00
await evtLoop . close ( ) ;
2023-03-08 21:05:55 +09:00
parent . target ? . dialogManager . dismissAll ( ) ;
await localController . close ( ) ;
await remoteController . close ( ) ;
2022-03-17 21:03:52 +08:00
}
2023-03-08 21:05:55 +09:00
Future < void > refreshAll ( ) async {
await localController . refresh ( ) ;
await remoteController . refresh ( ) ;
2022-03-11 01:28:13 +08:00
}
2023-03-08 21:05:55 +09:00
void receiveFileDir ( Map < String , dynamic > evt ) {
2022-07-11 10:30:45 +08:00
if ( evt [ ' is_local ' ] = = " false " ) {
2023-03-08 21:05:55 +09:00
// init remote home, the remote connection will send one dir event when established. TODO opt
remoteController . initDirAndHome ( evt ) ;
2022-04-07 16:20:32 +08:00
}
2023-03-08 21:05:55 +09:00
fileFetcher . tryCompleteTask ( evt [ ' value ' ] , evt [ ' is_local ' ] ) ;
2022-03-16 15:33:00 +08:00
}
2023-03-15 15:13:23 +08:00
Future < void > postOverrideFileConfirm ( Map < String , dynamic > evt ) async {
2023-03-15 15:20:50 +08:00
evtLoop . pushEvent (
2023-03-15 15:13:23 +08:00
_FileDialogEvent ( WeakReference ( this ) , FileDialogType . overwrite , evt ) ) ;
}
2023-03-15 10:43:27 +08:00
Future < void > overrideFileConfirm ( Map < String , dynamic > evt ,
2023-03-15 15:13:23 +08:00
{ bool ? overrideConfirm , bool skip = false } ) async {
// If `skip == true`, it means to skip this file without showing dialog.
// Because `resp` may be null after the user operation or the last remembered operation,
// and we should distinguish them.
2023-03-15 17:18:59 +08:00
final resp = overrideConfirm ? ?
( ! skip
? await showFileConfirmDialog ( translate ( " Overwrite " ) ,
" ${ evt [ ' read_path ' ] } " , true , evt [ ' is_identical ' ] = = " true " )
: null ) ;
2022-07-11 10:30:45 +08:00
final id = int . tryParse ( evt [ ' id ' ] ) ? ? 0 ;
2022-05-17 20:56:36 +08:00
if ( false = = resp ) {
2023-03-08 21:05:55 +09:00
final jobIndex = jobController . getJob ( id ) ;
2022-08-03 22:03:31 +08:00
if ( jobIndex ! = - 1 ) {
2023-03-15 10:43:27 +08:00
await jobController . cancelJob ( id ) ;
2023-03-08 21:05:55 +09:00
final job = jobController . jobTable [ jobIndex ] ;
2022-07-11 10:30:45 +08:00
job . state = JobState . done ;
2023-03-09 11:40:06 +09:00
jobController . jobTable . refresh ( ) ;
2022-07-11 10:30:45 +08:00
}
2022-05-17 20:56:36 +08:00
} else {
2022-07-01 11:26:32 +08:00
var need_override = false ;
2022-05-17 20:56:36 +08:00
if ( resp = = null ) {
// skip
2022-07-01 11:26:32 +08:00
need_override = false ;
2022-05-17 20:56:36 +08:00
} else {
// overwrite
2022-07-01 11:26:32 +08:00
need_override = true ;
2022-05-17 20:56:36 +08:00
}
2023-03-15 15:13:23 +08:00
// Update the loop config.
if ( fileConfirmCheckboxRemember ) {
evtLoop . setSkip ( ! need_override ) ;
}
2023-03-15 10:43:27 +08:00
await bind . sessionSetConfirmOverrideFile (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2022-08-03 22:03:31 +08:00
actId: id ,
fileNum: int . parse ( evt [ ' file_num ' ] ) ,
needOverride: need_override ,
remember: fileConfirmCheckboxRemember ,
2022-07-11 10:30:45 +08:00
isUpload: evt [ ' is_upload ' ] = = " true " ) ;
2022-05-17 20:56:36 +08:00
}
2023-03-15 15:13:23 +08:00
// Update the loop config.
if ( fileConfirmCheckboxRemember ) {
evtLoop . setOverrideConfirm ( resp ) ;
}
2022-05-17 20:56:36 +08:00
}
2023-03-08 21:05:55 +09:00
bool fileConfirmCheckboxRemember = false ;
Future < bool ? > showFileConfirmDialog (
2023-03-15 17:18:59 +08:00
String title , String content , bool showCheckbox , bool isIdentical ) async {
2023-03-08 21:05:55 +09:00
fileConfirmCheckboxRemember = false ;
return await parent . target ? . dialogManager . show < bool ? > (
2023-05-08 12:34:19 +08:00
( setState , Function ( bool ? v ) close , context ) {
2023-03-08 21:05:55 +09:00
cancel ( ) = > close ( false ) ;
submit ( ) = > close ( true ) ;
return CustomAlertDialog (
title: Row (
children: [
const Icon ( Icons . warning_rounded , color: Colors . red ) ,
Text ( title ) . paddingOnly (
left: 10 ,
) ,
] ,
) ,
contentBoxConstraints:
BoxConstraints ( minHeight: 100 , minWidth: 400 , maxWidth: 400 ) ,
content: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisSize: MainAxisSize . min ,
children: [
Text ( translate ( " This file exists, skip or overwrite this file? " ) ,
style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
const SizedBox ( height: 5 ) ,
Text ( content ) ,
2023-03-15 17:18:59 +08:00
Offstage (
offstage: ! isIdentical ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
const SizedBox ( height: 12 ) ,
2023-03-15 17:26:33 +08:00
Text ( translate ( " identical_file_tip " ) ,
2023-03-15 17:18:59 +08:00
style: const TextStyle ( fontWeight: FontWeight . w500 ) )
] ,
) ,
) ,
2023-03-08 21:05:55 +09:00
showCheckbox
? CheckboxListTile (
contentPadding: const EdgeInsets . all ( 0 ) ,
dense: true ,
controlAffinity: ListTileControlAffinity . leading ,
title: Text (
translate ( " Do this for all conflicts " ) ,
) ,
value: fileConfirmCheckboxRemember ,
onChanged: ( v ) {
if ( v = = null ) return ;
setState ( ( ) = > fileConfirmCheckboxRemember = v ) ;
} ,
)
: const SizedBox . shrink ( )
] ) ,
actions: [
dialogButton (
" Cancel " ,
icon: Icon ( Icons . close_rounded ) ,
onPressed: cancel ,
isOutline: true ,
) ,
dialogButton (
" Skip " ,
icon: Icon ( Icons . navigate_next_rounded ) ,
onPressed: ( ) = > close ( null ) ,
isOutline: true ,
) ,
dialogButton (
" OK " ,
icon: Icon ( Icons . done_rounded ) ,
onPressed: submit ,
) ,
] ,
onSubmit: submit ,
onCancel: cancel ,
) ;
} , useAnimation: false ) ;
2022-03-12 21:42:05 +08:00
}
2023-03-08 21:05:55 +09:00
}
2022-03-12 21:42:05 +08:00
2023-03-08 23:06:34 +09:00
class DirectoryData {
final DirectoryOptions options ;
final FileDirectory directory ;
DirectoryData ( this . directory , this . options ) ;
}
2023-03-08 21:05:55 +09:00
class FileController {
final bool isLocal ;
2023-03-08 22:32:55 +09:00
final GetSessionID getSessionID ;
2023-06-06 07:39:44 +08:00
SessionID get sessionId = > getSessionID ( ) ;
2022-08-03 22:03:31 +08:00
2023-03-08 21:05:55 +09:00
final FileFetcher fileFetcher ;
final options = DirectoryOptions ( ) . obs ;
final directory = FileDirectory ( ) . obs ;
final history = RxList < String > . empty ( growable: true ) ;
2023-03-08 22:32:55 +09:00
final sortBy = SortBy . name . obs ;
2023-03-09 00:06:24 +09:00
var sortAscending = true ;
2023-03-08 21:05:55 +09:00
final JobController jobController ;
2023-03-16 11:23:15 +09:00
final WeakReference < FFI > rootState ;
2023-03-08 21:05:55 +09:00
2023-03-08 23:06:34 +09:00
final DirectoryData Function ( ) getOtherSideDirectoryData ;
2023-03-09 18:05:09 +09:00
late final SelectedItems selectedItems = SelectedItems ( isLocal: isLocal ) ;
2023-03-08 23:06:34 +09:00
2023-03-08 21:05:55 +09:00
FileController (
{ required this . isLocal ,
2023-03-08 22:32:55 +09:00
required this . getSessionID ,
2023-03-16 11:23:15 +09:00
required this . rootState ,
2023-03-08 21:05:55 +09:00
required this . jobController ,
2023-03-08 23:06:34 +09:00
required this . fileFetcher ,
required this . getOtherSideDirectoryData } ) ;
2023-03-08 21:05:55 +09:00
String get homePath = > options . value . home ;
2023-03-16 11:23:15 +09:00
OverlayDialogManager ? get dialogManager = > rootState . target ? . dialogManager ;
2023-03-08 21:05:55 +09:00
String get shortPath {
final dirPath = directory . value . path ;
if ( dirPath . startsWith ( homePath ) ) {
var path = dirPath . replaceFirst ( homePath , " " ) ;
if ( path . isEmpty ) return " " ;
if ( path [ 0 ] = = " / " | | path [ 0 ] = = " \\ " ) {
// remove more '/' or '\'
path = path . replaceFirst ( path [ 0 ] , " " ) ;
}
return path ;
} else {
return dirPath . replaceFirst ( homePath , " " ) ;
}
}
2023-03-08 23:06:34 +09:00
DirectoryData directoryData ( ) {
return DirectoryData ( directory . value , options . value ) ;
}
2023-03-08 21:05:55 +09:00
Future < void > onReady ( ) async {
2023-03-16 19:39:37 +09:00
if ( isLocal ) {
options . value . home = await bind . mainGetHomeDir ( ) ;
}
2023-03-08 21:05:55 +09:00
options . value . showHidden = ( await bind . sessionGetPeerOption (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2023-03-08 21:05:55 +09:00
name: isLocal ? " local_show_hidden " : " remote_show_hidden " ) )
2022-08-03 22:13:40 +08:00
. isNotEmpty ;
2023-03-16 11:23:15 +09:00
options . value . isWindows = isLocal
? Platform . isWindows
: rootState . target ? . ffiModel . pi . platform = = kPeerPlatformWindows ;
2022-03-17 21:03:52 +08:00
2022-04-07 16:20:32 +08:00
await Future . delayed ( Duration ( milliseconds: 100 ) ) ;
2023-03-08 21:05:55 +09:00
final dir = ( await bind . sessionGetPeerOption (
2023-06-06 07:39:44 +08:00
sessionId: sessionId , name: isLocal ? " local_dir " : " remote_dir " ) ) ;
2023-03-08 21:05:55 +09:00
openDirectory ( dir . isEmpty ? options . value . home : dir ) ;
2022-04-07 16:20:32 +08:00
await Future . delayed ( Duration ( seconds: 1 ) ) ;
2023-03-08 21:05:55 +09:00
if ( directory . value . path . isEmpty ) {
openDirectory ( options . value . home ) ;
2022-04-07 16:20:32 +08:00
}
2022-03-17 21:03:52 +08:00
}
2023-03-08 21:05:55 +09:00
Future < void > close ( ) async {
2022-03-17 21:03:52 +08:00
// save config
2022-10-19 23:29:45 +09:00
Map < String , String > msgMap = { } ;
2023-03-08 21:05:55 +09:00
msgMap [ isLocal ? " local_dir " : " remote_dir " ] = directory . value . path ;
msgMap [ isLocal ? " local_show_hidden " : " remote_show_hidden " ] =
options . value . showHidden ? " Y " : " " ;
2022-08-03 22:03:31 +08:00
for ( final msg in msgMap . entries ) {
2023-03-08 21:05:55 +09:00
await bind . sessionPeerOption (
2023-06-06 07:39:44 +08:00
sessionId: sessionId , name: msg . key , value: msg . value ) ;
2022-07-01 11:26:32 +08:00
}
2023-03-08 21:05:55 +09:00
directory . value . clear ( ) ;
options . value . clear ( ) ;
2022-03-07 22:54:34 +08:00
}
2023-03-08 21:05:55 +09:00
void toggleShowHidden ( { bool ? showHidden } ) {
options . value . showHidden = showHidden ? ? ! options . value . showHidden ;
refresh ( ) ;
}
void changeSortStyle ( SortBy sort , { bool ? isLocal , bool ascending = true } ) {
2023-03-08 22:32:55 +09:00
sortBy . value = sort ;
2023-03-09 00:06:24 +09:00
sortAscending = ascending ;
2023-03-09 11:40:06 +09:00
directory . update ( ( dir ) {
dir ? . changeSortStyle ( sort , ascending: ascending ) ;
} ) ;
2023-03-08 21:05:55 +09:00
}
Future < void > refresh ( ) async {
await openDirectory ( directory . value . path ) ;
2022-03-07 22:54:34 +08:00
}
2022-03-09 17:07:24 +08:00
2023-03-08 21:05:55 +09:00
Future < void > openDirectory ( String path , { bool isBack = false } ) async {
2022-10-19 23:29:45 +09:00
if ( path = = " . " ) {
2023-03-08 21:05:55 +09:00
refresh ( ) ;
2022-10-19 23:29:45 +09:00
return ;
}
if ( path = = " .. " ) {
2023-03-08 21:05:55 +09:00
goToParentDirectory ( ) ;
2022-10-19 23:29:45 +09:00
return ;
}
2022-10-11 17:25:34 +09:00
if ( ! isBack ) {
2023-03-08 21:05:55 +09:00
pushHistory ( ) ;
2022-10-11 17:25:34 +09:00
}
2023-03-08 21:05:55 +09:00
final showHidden = options . value . showHidden ;
final isWindows = options . value . isWindows ;
2022-07-09 11:27:59 +08:00
// process /C:\ -> C:\ on Windows
2022-12-04 23:44:03 +09:00
if ( isWindows & & path . length > 1 & & path [ 0 ] = = ' / ' ) {
2022-07-09 11:27:59 +08:00
path = path . substring ( 1 ) ;
if ( path [ path . length - 1 ] ! = ' \\ ' ) {
2022-10-19 23:29:45 +09:00
path = " $ path \\ " ;
2022-07-09 11:27:59 +08:00
}
}
2022-03-16 15:33:00 +08:00
try {
2023-03-08 21:05:55 +09:00
final fd = await fileFetcher . fetchDirectory ( path , isLocal , showHidden ) ;
2023-03-08 22:32:55 +09:00
fd . format ( isWindows , sort: sortBy . value ) ;
2023-03-08 21:05:55 +09:00
directory . value = fd ;
2022-03-16 15:33:00 +08:00
} catch ( e ) {
2022-09-06 19:08:45 +08:00
debugPrint ( " Failed to openDirectory $ path : $ e " ) ;
2022-03-09 17:07:24 +08:00
}
}
2022-03-07 22:54:34 +08:00
2023-03-08 21:05:55 +09:00
void pushHistory ( ) {
if ( history . isNotEmpty & & history . last = = directory . value . path ) {
2022-10-11 17:25:34 +09:00
return ;
}
2023-03-08 21:05:55 +09:00
history . add ( directory . value . path ) ;
2022-10-11 17:25:34 +09:00
}
2023-03-08 21:05:55 +09:00
void goToHomeDirectory ( ) {
openDirectory ( homePath ) ;
2022-03-17 21:03:52 +08:00
}
2023-03-08 21:05:55 +09:00
void goBack ( ) {
2022-10-11 17:25:34 +09:00
if ( history . isEmpty ) return ;
final path = history . removeAt ( history . length - 1 ) ;
if ( path . isEmpty ) return ;
2023-03-08 21:05:55 +09:00
if ( directory . value . path = = path ) {
goBack ( ) ;
2022-10-11 17:25:34 +09:00
return ;
}
2023-03-08 21:05:55 +09:00
openDirectory ( path , isBack: true ) ;
2022-10-11 17:25:34 +09:00
}
2023-03-08 21:05:55 +09:00
void goToParentDirectory ( ) {
final isWindows = options . value . isWindows ;
final dirPath = directory . value . path ;
var parent = PathUtil . dirname ( dirPath , isWindows ) ;
2022-07-09 11:27:59 +08:00
// specially for C:\, D:\, goto '/'
2023-03-08 21:05:55 +09:00
if ( parent = = dirPath & & isWindows ) {
openDirectory ( ' / ' ) ;
2022-07-09 11:27:59 +08:00
return ;
}
2023-03-08 21:05:55 +09:00
openDirectory ( parent ) ;
2022-03-07 22:54:34 +08:00
}
2023-03-08 21:05:55 +09:00
// TODO deprecated this
void initDirAndHome ( Map < String , dynamic > evt ) {
try {
final fd = FileDirectory . fromJson ( jsonDecode ( evt [ ' value ' ] ) ) ;
2023-03-08 22:32:55 +09:00
fd . format ( options . value . isWindows , sort: sortBy . value ) ;
2023-03-08 21:05:55 +09:00
if ( fd . id > 0 ) {
final jobIndex = jobController . getJob ( fd . id ) ;
if ( jobIndex ! = - 1 ) {
final job = jobController . jobTable [ jobIndex ] ;
var totalSize = 0 ;
var fileCount = fd . entries . length ;
for ( var element in fd . entries ) {
totalSize + = element . size ;
}
job . totalSize = totalSize ;
job . fileCount = fileCount ;
2023-03-15 22:44:07 +08:00
debugPrint ( " update receive details: ${ fd . path } " ) ;
2023-03-09 11:40:06 +09:00
jobController . jobTable . refresh ( ) ;
2023-03-08 21:05:55 +09:00
}
} else if ( options . value . home . isEmpty ) {
options . value . home = fd . path ;
2023-03-15 22:44:07 +08:00
debugPrint ( " init remote home: ${ fd . path } " ) ;
2023-03-08 21:05:55 +09:00
directory . value = fd ;
2022-07-01 12:08:52 +08:00
}
2023-03-08 21:05:55 +09:00
} catch ( e ) {
debugPrint ( " initDirAndHome err= $ e " ) ;
}
}
2023-03-09 22:34:43 +09:00
/// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
2023-03-08 23:06:34 +09:00
void sendFiles ( SelectedItems items , DirectoryData otherSideData ) {
/// ignore wrong items side status
if ( items . isLocal ! = isLocal ) {
2023-03-08 21:05:55 +09:00
return ;
}
// alias
2023-03-08 23:06:34 +09:00
final isRemoteToLocal = ! isLocal ;
2023-03-08 21:05:55 +09:00
2023-03-08 23:06:34 +09:00
final toPath = otherSideData . directory . path ;
final isWindows = otherSideData . options . isWindows ;
final showHidden = otherSideData . options . showHidden ;
2023-03-08 21:05:55 +09:00
for ( var from in items . items ) {
final jobID = jobController . add ( from , isRemoteToLocal ) ;
bind . sessionSendFiles (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2023-03-08 21:05:55 +09:00
actId: jobID ,
path: from . path ,
to: PathUtil . join ( toPath , from . name , isWindows ) ,
fileNum: 0 ,
includeHidden: showHidden ,
isRemote: isRemoteToLocal ) ;
debugPrint (
2023-03-15 22:44:07 +08:00
" path: ${ from . path } , toPath: $ toPath , to: ${ PathUtil . join ( toPath , from . name , isWindows ) } " ) ;
2022-03-12 21:42:05 +08:00
}
}
2023-03-08 21:05:55 +09:00
bool _removeCheckboxRemember = false ;
2022-03-16 15:33:00 +08:00
2023-03-08 21:05:55 +09:00
Future < void > removeAction ( SelectedItems items ) async {
_removeCheckboxRemember = false ;
2023-03-08 23:06:34 +09:00
if ( items . isLocal ! = isLocal ) {
2023-03-08 21:05:55 +09:00
debugPrint ( " Failed to removeFile, wrong files " ) ;
2022-03-12 21:42:05 +08:00
return ;
}
2023-03-08 21:05:55 +09:00
final isWindows = options . value . isWindows ;
2022-03-16 15:33:00 +08:00
await Future . forEach ( items . items , ( Entry item ) async {
2023-03-08 21:05:55 +09:00
final jobID = JobController . jobID . next ( ) ;
2022-03-16 15:33:00 +08:00
var title = " " ;
var content = " " ;
late final List < Entry > entries ;
if ( item . isFile ) {
2022-03-23 15:28:21 +08:00
title = translate ( " Are you sure you want to delete this file? " ) ;
2022-10-19 23:29:45 +09:00
content = item . name ;
2022-03-16 15:33:00 +08:00
entries = [ item ] ;
} else if ( item . isDirectory ) {
2022-03-24 15:29:12 +08:00
title = translate ( " Not an empty directory " ) ;
2023-03-08 21:05:55 +09:00
dialogManager ? . showLoading ( translate ( " Waiting " ) ) ;
final fd = await fileFetcher . fetchDirectoryRecursive (
2023-03-16 11:23:15 +09:00
jobID , item . path , items . isLocal , true ) ;
2022-04-07 23:45:19 +08:00
if ( fd . path . isEmpty ) {
fd . path = item . path ;
}
2022-03-17 21:03:52 +08:00
fd . format ( isWindows ) ;
2023-03-08 21:05:55 +09:00
dialogManager ? . dismissAll ( ) ;
2022-03-17 21:03:52 +08:00
if ( fd . entries . isEmpty ) {
2022-04-07 16:20:32 +08:00
final confirm = await showRemoveDialog (
translate (
" Are you sure you want to delete this empty directory? " ) ,
item . name ,
false ) ;
2022-03-17 21:03:52 +08:00
if ( confirm = = true ) {
2023-03-08 21:05:55 +09:00
sendRemoveEmptyDir ( item . path , 0 ) ;
2022-03-16 15:33:00 +08:00
}
return ;
}
entries = fd . entries ;
} else {
entries = [ ] ;
}
for ( var i = 0 ; i < entries . length ; i + + ) {
2022-04-07 16:20:32 +08:00
final dirShow = item . isDirectory
? " ${ translate ( " Are you sure you want to delete the file of this directory? " ) } \n "
: " " ;
final count = entries . length > 1 ? " ${ i + 1 } / ${ entries . length } " : " " ;
2023-02-27 20:56:45 +01:00
content = " $ dirShow \n \n ${ entries [ i ] . path } " . trim ( ) ;
final confirm = await showRemoveDialog (
2023-03-01 14:50:50 +01:00
count . isEmpty ? title : " $ title ( $ count ) " ,
content ,
item . isDirectory ,
) ;
2022-03-16 15:33:00 +08:00
try {
if ( confirm = = true ) {
2023-03-08 21:05:55 +09:00
sendRemoveFile ( entries [ i ] . path , i ) ;
final res = await jobController . jobResultListener . start ( ) ;
2022-03-16 15:33:00 +08:00
// handle remove res;
if ( item . isDirectory & &
res [ ' file_num ' ] = = ( entries . length - 1 ) . toString ( ) ) {
2023-03-08 21:05:55 +09:00
sendRemoveEmptyDir ( item . path , i ) ;
2022-03-16 15:33:00 +08:00
}
}
2023-03-08 21:05:55 +09:00
if ( _removeCheckboxRemember ) {
2022-03-16 15:33:00 +08:00
if ( confirm = = true ) {
for ( var j = i + 1 ; j < entries . length ; j + + ) {
2023-03-08 21:05:55 +09:00
sendRemoveFile ( entries [ j ] . path , j ) ;
final res = await jobController . jobResultListener . start ( ) ;
2022-03-16 15:33:00 +08:00
if ( item . isDirectory & &
res [ ' file_num ' ] = = ( entries . length - 1 ) . toString ( ) ) {
2023-03-08 21:05:55 +09:00
sendRemoveEmptyDir ( item . path , i ) ;
2022-03-16 15:33:00 +08:00
}
}
}
break ;
}
2022-07-12 11:51:58 +08:00
} catch ( e ) {
2022-10-19 23:29:45 +09:00
print ( " remove error: $ e " ) ;
2022-07-12 11:51:58 +08:00
}
2022-03-12 21:42:05 +08:00
}
} ) ;
2023-03-08 21:05:55 +09:00
refresh ( ) ;
2022-03-16 15:33:00 +08:00
}
2022-03-17 21:03:52 +08:00
Future < bool ? > showRemoveDialog (
String title , String content , bool showCheckbox ) async {
2023-06-06 07:39:44 +08:00
return await dialogManager ? . show < bool > (
( setState , Function ( bool v ) close , context ) {
2022-09-03 18:19:50 +08:00
cancel ( ) = > close ( false ) ;
submit ( ) = > close ( true ) ;
return CustomAlertDialog (
title: Row (
2023-02-27 20:56:45 +01:00
mainAxisAlignment: MainAxisAlignment . center ,
2022-09-03 18:19:50 +08:00
children: [
2023-02-27 20:56:45 +01:00
const Icon ( Icons . warning_rounded , color: Colors . red ) ,
Text ( title ) . paddingOnly (
left: 10 ,
) ,
2022-09-03 18:19:50 +08:00
] ,
) ,
2022-09-29 21:09:40 +08:00
contentBoxConstraints:
2023-03-01 14:50:50 +01:00
BoxConstraints ( minHeight: 100 , minWidth: 400 , maxWidth: 400 ) ,
2022-09-03 18:19:50 +08:00
content: Column (
2023-02-27 20:56:45 +01:00
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text ( content ) ,
Text (
translate ( " This is irreversible! " ) ,
style: const TextStyle (
fontWeight: FontWeight . bold ,
color: Colors . red ,
) ,
) . paddingOnly ( top: 20 ) ,
showCheckbox
? CheckboxListTile (
contentPadding: const EdgeInsets . all ( 0 ) ,
dense: true ,
controlAffinity: ListTileControlAffinity . leading ,
title: Text (
translate ( " Do this for all conflicts " ) ,
) ,
2023-03-08 21:05:55 +09:00
value: _removeCheckboxRemember ,
2023-02-27 20:56:45 +01:00
onChanged: ( v ) {
if ( v = = null ) return ;
2023-03-08 21:05:55 +09:00
setState ( ( ) = > _removeCheckboxRemember = v ) ;
2023-02-27 20:56:45 +01:00
} ,
)
2023-03-01 14:50:50 +01:00
: const SizedBox . shrink ( )
2023-02-27 20:56:45 +01:00
] ,
) ,
2023-03-01 14:50:50 +01:00
actions: [
dialogButton (
" Cancel " ,
icon: Icon ( Icons . close_rounded ) ,
onPressed: cancel ,
isOutline: true ,
) ,
dialogButton (
" OK " ,
icon: Icon ( Icons . done_rounded ) ,
onPressed: submit ,
) ,
] ,
2022-09-03 18:19:50 +08:00
onSubmit: submit ,
onCancel: cancel ,
) ;
} , useAnimation: false ) ;
2022-03-12 21:42:05 +08:00
}
2023-03-08 21:05:55 +09:00
void sendRemoveFile ( String path , int fileNum ) {
2022-08-03 22:03:31 +08:00
bind . sessionRemoveFile (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2023-03-08 21:05:55 +09:00
actId: JobController . jobID . next ( ) ,
2022-08-03 22:03:31 +08:00
path: path ,
isRemote: ! isLocal ,
fileNum: fileNum ) ;
2022-03-16 15:33:00 +08:00
}
2023-03-08 21:05:55 +09:00
void sendRemoveEmptyDir ( String path , int fileNum ) {
2022-10-11 17:25:34 +09:00
history . removeWhere ( ( element ) = > element . contains ( path ) ) ;
2022-08-03 22:03:31 +08:00
bind . sessionRemoveAllEmptyDirs (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2023-03-08 21:05:55 +09:00
actId: JobController . jobID . next ( ) ,
2022-08-03 22:03:31 +08:00
path: path ,
isRemote: ! isLocal ) ;
2022-03-16 15:33:00 +08:00
}
2023-03-08 21:05:55 +09:00
Future < void > createDir ( String path ) async {
2022-08-03 22:03:31 +08:00
bind . sessionCreateDir (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2023-03-08 21:05:55 +09:00
actId: JobController . jobID . next ( ) ,
2022-08-03 22:03:31 +08:00
path: path ,
isRemote: ! isLocal ) ;
2022-03-16 15:33:00 +08:00
}
2023-03-08 21:05:55 +09:00
}
2022-03-07 22:54:34 +08:00
2023-03-08 21:05:55 +09:00
class JobController {
static final JobID jobID = JobID ( ) ;
2023-03-09 00:06:24 +09:00
final jobTable = List < JobProgress > . empty ( growable: true ) . obs ;
2023-03-08 21:05:55 +09:00
final jobResultListener = JobResultListener < Map < String , dynamic > > ( ) ;
2023-03-08 22:32:55 +09:00
final GetSessionID getSessionID ;
2023-06-06 07:39:44 +08:00
SessionID get sessionId = > getSessionID ( ) ;
2023-03-08 21:05:55 +09:00
2023-03-08 22:32:55 +09:00
JobController ( this . getSessionID ) ;
2023-03-08 21:05:55 +09:00
int getJob ( int id ) {
return jobTable . indexWhere ( ( element ) = > element . id = = id ) ;
2022-03-29 23:10:43 +08:00
}
2022-03-17 21:03:52 +08:00
2023-03-08 21:05:55 +09:00
// JobProgress? getJob(int id) {
// return jobTable.firstWhere((element) => element.id == id);
// }
// return jobID
int add ( Entry from , bool isRemoteToLocal ) {
final jobID = JobController . jobID . next ( ) ;
jobTable . add ( JobProgress ( )
. . fileName = path . basename ( from . path )
. . jobName = from . path
. . totalSize = from . size
. . state = JobState . inProgress
. . id = jobID
. . isRemoteToLocal = isRemoteToLocal ) ;
return jobID ;
2022-06-21 17:58:27 +08:00
}
2022-07-11 10:30:45 +08:00
2023-03-08 21:05:55 +09:00
void tryUpdateJobProgress ( Map < String , dynamic > evt ) {
try {
int id = int . parse ( evt [ ' id ' ] ) ;
// id = index + 1
final jobIndex = getJob ( id ) ;
if ( jobIndex > = 0 & & jobTable . length > jobIndex ) {
final job = jobTable [ jobIndex ] ;
job . fileNum = int . parse ( evt [ ' file_num ' ] ) ;
job . speed = double . parse ( evt [ ' speed ' ] ) ;
job . finishedSize = int . parse ( evt [ ' finished_size ' ] ) ;
debugPrint ( " update job $ id with $ evt " ) ;
2023-03-09 11:40:06 +09:00
jobTable . refresh ( ) ;
2023-03-08 21:05:55 +09:00
}
} catch ( e ) {
2023-03-15 22:44:07 +08:00
debugPrint ( " Failed to tryUpdateJobProgress, evt: ${ evt . toString ( ) } " ) ;
2023-03-08 21:05:55 +09:00
}
}
void jobDone ( Map < String , dynamic > evt ) async {
if ( jobResultListener . isListening ) {
jobResultListener . complete ( evt ) ;
return ;
}
int id = int . parse ( evt [ ' id ' ] ) ;
2022-07-11 10:30:45 +08:00
final jobIndex = getJob ( id ) ;
if ( jobIndex ! = - 1 ) {
final job = jobTable [ jobIndex ] ;
2023-03-08 21:05:55 +09:00
job . finishedSize = job . totalSize ;
job . state = JobState . done ;
job . fileNum = int . parse ( evt [ ' file_num ' ] ) ;
2023-03-09 11:40:06 +09:00
jobTable . refresh ( ) ;
2022-07-11 10:30:45 +08:00
}
}
2022-07-11 16:07:49 +08:00
2023-03-08 21:05:55 +09:00
void jobError ( Map < String , dynamic > evt ) {
final err = evt [ ' err ' ] . toString ( ) ;
int jobIndex = getJob ( int . parse ( evt [ ' id ' ] ) ) ;
if ( jobIndex ! = - 1 ) {
final job = jobTable [ jobIndex ] ;
job . state = JobState . error ;
job . err = err ;
job . fileNum = int . parse ( evt [ ' file_num ' ] ) ;
if ( err = = " skipped " ) {
job . state = JobState . done ;
job . finishedSize = job . totalSize ;
}
2023-03-09 11:40:06 +09:00
jobTable . refresh ( ) ;
2023-03-08 21:05:55 +09:00
}
debugPrint ( " jobError $ evt " ) ;
}
2023-03-15 10:43:27 +08:00
Future < void > cancelJob ( int id ) async {
2023-06-06 07:39:44 +08:00
await bind . sessionCancelJob ( sessionId: sessionId , actId: id ) ;
2023-03-08 21:05:55 +09:00
}
2022-07-11 18:23:58 +08:00
void loadLastJob ( Map < String , dynamic > evt ) {
2022-10-19 23:29:45 +09:00
debugPrint ( " load last job: $ evt " ) ;
2022-08-03 22:03:31 +08:00
Map < String , dynamic > jobDetail = json . decode ( evt [ ' value ' ] ) ;
2022-07-11 18:23:58 +08:00
// int id = int.parse(jobDetail['id']);
String remote = jobDetail [ ' remote ' ] ;
String to = jobDetail [ ' to ' ] ;
bool showHidden = jobDetail [ ' show_hidden ' ] ;
int fileNum = jobDetail [ ' file_num ' ] ;
bool isRemote = jobDetail [ ' is_remote ' ] ;
2023-03-08 21:05:55 +09:00
final currJobId = JobController . jobID . next ( ) ;
2023-03-03 20:26:13 +01:00
String fileName = path . basename ( isRemote ? remote : to ) ;
2022-07-11 18:23:58 +08:00
var jobProgress = JobProgress ( )
2023-03-03 20:26:13 +01:00
. . fileName = fileName
2022-07-11 18:23:58 +08:00
. . jobName = isRemote ? remote : to
. . id = currJobId
2023-03-08 21:05:55 +09:00
. . isRemoteToLocal = isRemote
2022-07-11 18:23:58 +08:00
. . fileNum = fileNum
. . remote = remote
. . to = to
. . showHidden = showHidden
. . state = JobState . paused ;
jobTable . add ( jobProgress ) ;
2022-08-03 22:03:31 +08:00
bind . sessionAddJob (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2022-08-03 22:03:31 +08:00
isRemote: isRemote ,
includeHidden: showHidden ,
actId: currJobId ,
path: isRemote ? remote : to ,
to: isRemote ? to : remote ,
fileNum: fileNum ,
2022-07-11 18:23:58 +08:00
) ;
}
2023-03-08 21:05:55 +09:00
void resumeJob ( int jobId ) {
2022-07-11 18:23:58 +08:00
final jobIndex = getJob ( jobId ) ;
if ( jobIndex ! = - 1 ) {
final job = jobTable [ jobIndex ] ;
2022-08-03 22:03:31 +08:00
bind . sessionResumeJob (
2023-06-06 07:39:44 +08:00
sessionId: sessionId , actId: job . id , isRemote: job . isRemoteToLocal ) ;
2022-07-11 18:23:58 +08:00
job . state = JobState . inProgress ;
2023-03-09 11:40:06 +09:00
jobTable . refresh ( ) ;
2022-07-11 18:23:58 +08:00
} else {
2022-10-19 23:29:45 +09:00
debugPrint ( " jobId $ jobId is not exists " ) ;
2022-07-11 18:23:58 +08:00
}
2023-03-08 21:05:55 +09:00
}
void updateFolderFiles ( Map < String , dynamic > evt ) {
// ret: "{\"id\":1,\"num_entries\":12,\"total_size\":1264822.0}"
Map < String , dynamic > info = json . decode ( evt [ ' info ' ] ) ;
int id = info [ ' id ' ] ;
int num_entries = info [ ' num_entries ' ] ;
double total_size = info [ ' total_size ' ] ;
final jobIndex = getJob ( id ) ;
if ( jobIndex ! = - 1 ) {
final job = jobTable [ jobIndex ] ;
job . fileCount = num_entries ;
job . totalSize = total_size . toInt ( ) ;
2023-03-09 11:40:06 +09:00
jobTable . refresh ( ) ;
2023-03-08 21:05:55 +09:00
}
debugPrint ( " update folder files: $ info " ) ;
2022-07-11 18:23:58 +08:00
}
2022-03-09 17:07:24 +08:00
}
2022-03-16 15:33:00 +08:00
class JobResultListener < T > {
Completer < T > ? _completer ;
Timer ? _timer ;
2022-10-19 23:29:45 +09:00
final int _timeoutSecond = 5 ;
2022-03-16 15:33:00 +08:00
bool get isListening = > _completer ! = null ;
clear ( ) {
if ( _completer ! = null ) {
_timer ? . cancel ( ) ;
_timer = null ;
_completer ! . completeError ( " Cancel manually " ) ;
_completer = null ;
return ;
}
}
Future < T > start ( ) {
if ( _completer ! = null ) return Future . error ( " Already start listen " ) ;
_completer = Completer ( ) ;
_timer = Timer ( Duration ( seconds: _timeoutSecond ) , ( ) {
if ( ! _completer ! . isCompleted ) {
_completer ! . completeError ( " Time out " ) ;
}
_completer = null ;
} ) ;
return _completer ! . future ;
}
complete ( T res ) {
if ( _completer ! = null ) {
_timer ? . cancel ( ) ;
_timer = null ;
_completer ! . complete ( res ) ;
_completer = null ;
return ;
}
}
}
class FileFetcher {
2022-10-19 23:29:45 +09:00
// Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
Map < String , Completer < FileDirectory > > remoteTasks = { } ;
Map < int , Completer < FileDirectory > > readRecursiveTasks = { } ;
2022-03-16 15:33:00 +08:00
2023-03-08 22:32:55 +09:00
final GetSessionID getSessionID ;
2023-06-06 07:39:44 +08:00
SessionID get sessionId = > getSessionID ( ) ;
2022-06-21 17:58:27 +08:00
2023-03-08 22:32:55 +09:00
FileFetcher ( this . getSessionID ) ;
2022-06-21 17:58:27 +08:00
2022-03-16 15:33:00 +08:00
Future < FileDirectory > registerReadTask ( bool isLocal , String path ) {
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
final tasks = remoteTasks ; // bypass now
if ( tasks . containsKey ( path ) ) {
throw " Failed to registerReadTask, already have same read job " ;
}
final c = Completer < FileDirectory > ( ) ;
tasks [ path ] = c ;
Timer ( Duration ( seconds: 2 ) , ( ) {
tasks . remove ( path ) ;
2022-04-04 01:21:44 +08:00
if ( c . isCompleted ) return ;
2023-03-15 22:44:07 +08:00
c . completeError ( " Failed to read dir, timeout " ) ;
2022-03-16 15:33:00 +08:00
} ) ;
return c . future ;
}
2023-03-08 21:05:55 +09:00
Future < FileDirectory > registerReadRecursiveTask ( int actID ) {
2022-03-16 15:33:00 +08:00
final tasks = readRecursiveTasks ;
2023-03-08 21:05:55 +09:00
if ( tasks . containsKey ( actID ) ) {
2022-03-16 15:33:00 +08:00
throw " Failed to registerRemoveTask, already have same ReadRecursive job " ;
}
final c = Completer < FileDirectory > ( ) ;
2023-03-08 21:05:55 +09:00
tasks [ actID ] = c ;
2022-03-16 15:33:00 +08:00
Timer ( Duration ( seconds: 2 ) , ( ) {
2023-03-08 21:05:55 +09:00
tasks . remove ( actID ) ;
2022-04-04 01:21:44 +08:00
if ( c . isCompleted ) return ;
2023-03-15 22:44:07 +08:00
c . completeError ( " Failed to read dir, timeout " ) ;
2022-03-16 15:33:00 +08:00
} ) ;
return c . future ;
}
tryCompleteTask ( String ? msg , String ? isLocalStr ) {
if ( msg = = null | | isLocalStr = = null ) return ;
2022-10-19 23:29:45 +09:00
late final Map < Object , Completer < FileDirectory > > tasks ;
2022-03-16 15:33:00 +08:00
try {
final fd = FileDirectory . fromJson ( jsonDecode ( msg ) ) ;
if ( fd . id > 0 ) {
// fd.id > 0 is result for read recursive
2022-04-19 13:07:45 +08:00
// to-do later,will be better if every fetch use ID,so that there will only one task map for read and recursive read
2022-03-16 15:33:00 +08:00
tasks = readRecursiveTasks ;
final completer = tasks . remove ( fd . id ) ;
completer ? . complete ( fd ) ;
} else if ( fd . path . isNotEmpty ) {
// result for normal read dir
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
tasks = remoteTasks ; // bypass now
final completer = tasks . remove ( fd . path ) ;
completer ? . complete ( fd ) ;
}
} catch ( e ) {
2023-03-15 22:44:07 +08:00
debugPrint ( " tryCompleteJob err: $ e " ) ;
2022-03-16 15:33:00 +08:00
}
}
2022-03-17 21:03:52 +08:00
Future < FileDirectory > fetchDirectory (
String path , bool isLocal , bool showHidden ) async {
2022-03-16 15:33:00 +08:00
try {
if ( isLocal ) {
2022-08-03 22:03:31 +08:00
final res = await bind . sessionReadLocalDirSync (
2023-06-06 07:39:44 +08:00
sessionId: sessionId , path: path , showHidden: showHidden ) ;
2022-03-16 15:33:00 +08:00
final fd = FileDirectory . fromJson ( jsonDecode ( res ) ) ;
return fd ;
} else {
2022-08-03 22:03:31 +08:00
await bind . sessionReadRemoteDir (
2023-06-06 07:39:44 +08:00
sessionId: sessionId , path: path , includeHidden: showHidden ) ;
2022-03-16 15:33:00 +08:00
return registerReadTask ( isLocal , path ) ;
}
} catch ( e ) {
return Future . error ( e ) ;
}
}
Future < FileDirectory > fetchDirectoryRecursive (
2023-03-08 21:05:55 +09:00
int actID , String path , bool isLocal , bool showHidden ) async {
2022-03-17 21:03:52 +08:00
// TODO test Recursive is show hidden default?
2022-03-16 15:33:00 +08:00
try {
2022-08-03 22:03:31 +08:00
await bind . sessionReadDirRecursive (
2023-06-06 07:39:44 +08:00
sessionId: sessionId ,
2023-03-08 21:05:55 +09:00
actId: actID ,
2022-08-03 22:03:31 +08:00
path: path ,
isRemote: ! isLocal ,
showHidden: showHidden ) ;
2023-03-08 21:05:55 +09:00
return registerReadRecursiveTask ( actID ) ;
2022-03-16 15:33:00 +08:00
} catch ( e ) {
return Future . error ( e ) ;
}
}
}
2022-03-11 01:28:13 +08:00
class FileDirectory {
List < Entry > entries = [ ] ;
int id = 0 ;
String path = " " ;
FileDirectory ( ) ;
2022-03-17 21:03:52 +08:00
FileDirectory . fromJson ( Map < String , dynamic > json ) {
2022-03-11 01:28:13 +08:00
id = json [ ' id ' ] ;
path = json [ ' path ' ] ;
2022-03-17 21:03:52 +08:00
json [ ' entries ' ] . forEach ( ( v ) {
2022-10-19 23:29:45 +09:00
entries . add ( Entry . fromJson ( v ) ) ;
2022-03-17 21:03:52 +08:00
} ) ;
2022-03-11 01:28:13 +08:00
}
2022-03-17 21:03:52 +08:00
// generate full path for every entry , init sort style if need.
format ( bool isWindows , { SortBy ? sort } ) {
2022-10-19 23:29:45 +09:00
for ( var entry in entries ) {
2022-03-17 21:03:52 +08:00
entry . path = PathUtil . join ( path , entry . name , isWindows ) ;
2022-10-19 23:29:45 +09:00
}
2022-03-17 21:03:52 +08:00
if ( sort ! = null ) {
changeSortStyle ( sort ) ;
2022-03-16 15:33:00 +08:00
}
}
2022-07-11 16:07:49 +08:00
changeSortStyle ( SortBy sort , { bool ascending = true } ) {
entries = _sortList ( entries , sort , ascending ) ;
2022-03-11 01:28:13 +08:00
}
clear ( ) {
entries = [ ] ;
id = 0 ;
path = " " ;
}
}
class Entry {
int entryType = 4 ;
int modifiedTime = 0 ;
String name = " " ;
2022-03-12 21:42:05 +08:00
String path = " " ;
2022-03-11 01:28:13 +08:00
int size = 0 ;
Entry ( ) ;
2022-03-17 21:03:52 +08:00
Entry . fromJson ( Map < String , dynamic > json ) {
2022-03-11 01:28:13 +08:00
entryType = json [ ' entry_type ' ] ;
modifiedTime = json [ ' modified_time ' ] ;
name = json [ ' name ' ] ;
size = json [ ' size ' ] ;
}
bool get isFile = > entryType > 3 ;
2022-10-19 10:52:29 +09:00
bool get isDirectory = > entryType < 3 ;
bool get isDrive = > entryType = = 3 ;
2022-03-11 01:28:13 +08:00
DateTime lastModified ( ) {
return DateTime . fromMillisecondsSinceEpoch ( modifiedTime * 1000 ) ;
}
}
2022-07-11 18:23:58 +08:00
enum JobState { none , inProgress , done , error , paused }
2022-03-11 01:28:13 +08:00
2022-07-11 10:30:45 +08:00
extension JobStateDisplay on JobState {
String display ( ) {
switch ( this ) {
case JobState . none:
return translate ( " Waiting " ) ;
case JobState . inProgress:
return translate ( " Transfer File " ) ;
case JobState . done:
return translate ( " Finished " ) ;
case JobState . error:
return translate ( " Error " ) ;
default :
return " " ;
}
}
}
2022-03-11 01:28:13 +08:00
class JobProgress {
JobState state = JobState . none ;
var id = 0 ;
var fileNum = 0 ;
2022-03-12 21:42:05 +08:00
var speed = 0.0 ;
2022-03-11 01:28:13 +08:00
var finishedSize = 0 ;
2022-07-11 10:30:45 +08:00
var totalSize = 0 ;
var fileCount = 0 ;
2023-03-08 21:05:55 +09:00
// [isRemote == true] means [remote -> local]
// var isRemote = false;
// to-do use enum
var isRemoteToLocal = false ;
2022-07-11 10:30:45 +08:00
var jobName = " " ;
2023-03-03 20:26:13 +01:00
var fileName = " " ;
2022-07-11 18:23:58 +08:00
var remote = " " ;
var to = " " ;
var showHidden = false ;
2022-12-05 20:09:48 +08:00
var err = " " ;
2022-03-11 01:28:13 +08:00
clear ( ) {
state = JobState . none ;
id = 0 ;
fileNum = 0 ;
speed = 0 ;
finishedSize = 0 ;
2022-07-11 10:30:45 +08:00
jobName = " " ;
2023-03-03 20:26:13 +01:00
fileName = " " ;
2022-07-11 10:30:45 +08:00
fileCount = 0 ;
2022-07-11 18:23:58 +08:00
remote = " " ;
to = " " ;
2022-12-05 20:09:48 +08:00
err = " " ;
}
String display ( ) {
if ( state = = JobState . done & & err = = " skipped " ) {
return translate ( " Skipped " ) ;
}
return state . display ( ) ;
2022-03-11 01:28:13 +08:00
}
}
2022-03-09 17:07:24 +08:00
class _PathStat {
final String path ;
final DateTime dateTime ;
2022-03-09 22:43:05 +08:00
2022-03-09 17:07:24 +08:00
_PathStat ( this . path , this . dateTime ) ;
}
2022-03-17 21:03:52 +08:00
class PathUtil {
2022-10-19 23:29:45 +09:00
static final windowsContext = path . Context ( style: path . Style . windows ) ;
static final posixContext = path . Context ( style: path . Style . posix ) ;
2022-03-17 21:03:52 +08:00
static String join ( String path1 , String path2 , bool isWindows ) {
final pathUtil = isWindows ? windowsContext : posixContext ;
return pathUtil . join ( path1 , path2 ) ;
}
static List < String > split ( String path , bool isWindows ) {
final pathUtil = isWindows ? windowsContext : posixContext ;
return pathUtil . split ( path ) ;
}
2022-04-07 20:19:07 +08:00
2022-04-07 22:58:47 +08:00
static String dirname ( String path , bool isWindows ) {
2022-04-07 20:19:07 +08:00
final pathUtil = isWindows ? windowsContext : posixContext ;
return pathUtil . dirname ( path ) ;
}
2022-03-17 21:03:52 +08:00
}
2023-03-08 00:49:14 +09:00
class DirectoryOptions {
2022-03-17 21:03:52 +08:00
String home ;
bool showHidden ;
bool isWindows ;
2023-03-08 00:49:14 +09:00
DirectoryOptions (
2022-03-17 21:03:52 +08:00
{ this . home = " " , this . showHidden = false , this . isWindows = false } ) ;
2022-04-07 16:20:32 +08:00
clear ( ) {
home = " " ;
showHidden = false ;
isWindows = false ;
}
2022-03-17 21:03:52 +08:00
}
2022-10-17 23:07:40 +09:00
class SelectedItems {
2023-03-08 21:05:55 +09:00
final bool isLocal ;
2023-03-09 19:55:38 +09:00
final items = RxList < Entry > . empty ( growable: true ) ;
2022-10-17 23:07:40 +09:00
2023-03-08 22:32:55 +09:00
SelectedItems ( { required this . isLocal } ) ;
2022-10-17 23:07:40 +09:00
2023-03-09 18:05:09 +09:00
void add ( Entry e ) {
2022-10-19 11:49:32 +09:00
if ( e . isDrive ) return ;
2023-03-09 19:55:38 +09:00
if ( ! items . contains ( e ) ) {
items . add ( e ) ;
2022-10-17 23:07:40 +09:00
}
}
2023-03-09 18:05:09 +09:00
void remove ( Entry e ) {
2023-03-09 19:55:38 +09:00
items . remove ( e ) ;
2022-10-17 23:07:40 +09:00
}
2023-03-09 18:05:09 +09:00
void clear ( ) {
2023-03-09 19:55:38 +09:00
items . clear ( ) ;
2022-10-17 23:07:40 +09:00
}
2022-10-19 10:52:29 +09:00
void selectAll ( List < Entry > entries ) {
2023-03-09 19:55:38 +09:00
items . clear ( ) ;
items . addAll ( entries ) ;
2022-10-19 10:52:29 +09:00
}
2023-03-09 18:05:09 +09:00
2023-03-09 19:55:38 +09:00
static bool valid ( RxList < Entry > items ) {
if ( items . isNotEmpty ) {
2023-03-09 18:05:09 +09:00
// exclude DirDrive type
return items . any ( ( item ) = > ! item . isDrive ) ;
}
return false ;
}
2022-10-17 23:07:40 +09:00
}
2022-10-19 11:24:44 +09:00
// edited from [https://github.com/DevsOnFlutter/file_manager/blob/c1bf7f0225b15bcb86eba602c60acd5c4da90dd8/lib/file_manager.dart#L22]
2022-07-11 16:07:49 +08:00
List < Entry > _sortList ( List < Entry > list , SortBy sortType , bool ascending ) {
2022-10-19 23:29:45 +09:00
if ( sortType = = SortBy . name ) {
2022-03-09 17:07:24 +08:00
// making list of only folders.
2022-10-19 11:24:44 +09:00
final dirs = list
. where ( ( element ) = > element . isDirectory | | element . isDrive )
. toList ( ) ;
2022-03-09 17:07:24 +08:00
// sorting folder list by name.
2022-03-09 22:43:05 +08:00
dirs . sort ( ( a , b ) = > a . name . toLowerCase ( ) . compareTo ( b . name . toLowerCase ( ) ) ) ;
2022-03-09 17:07:24 +08:00
// making list of only flies.
2022-03-09 22:43:05 +08:00
final files = list . where ( ( element ) = > element . isFile ) . toList ( ) ;
2022-03-09 17:07:24 +08:00
// sorting files list by name.
2022-03-09 22:43:05 +08:00
files . sort ( ( a , b ) = > a . name . toLowerCase ( ) . compareTo ( b . name . toLowerCase ( ) ) ) ;
2022-03-09 17:07:24 +08:00
// first folders will go to list (if available) then files will go to list.
2022-08-03 22:03:31 +08:00
return ascending
? [ . . . dirs , . . . files ]
: [ . . . dirs . reversed . toList ( ) , . . . files . reversed . toList ( ) ] ;
2022-10-19 23:29:45 +09:00
} else if ( sortType = = SortBy . modified ) {
2022-03-09 17:07:24 +08:00
// making the list of Path & DateTime
2022-10-19 23:29:45 +09:00
List < _PathStat > pathStat = [ ] ;
2022-03-09 22:43:05 +08:00
for ( Entry e in list ) {
2022-10-19 23:29:45 +09:00
pathStat . add ( _PathStat ( e . name , e . lastModified ( ) ) ) ;
2022-03-09 17:07:24 +08:00
}
// sort _pathStat according to date
2022-10-19 23:29:45 +09:00
pathStat . sort ( ( b , a ) = > a . dateTime . compareTo ( b . dateTime ) ) ;
2022-03-09 17:07:24 +08:00
2022-03-09 22:43:05 +08:00
// sorting [list] according to [_pathStat]
2022-10-19 23:29:45 +09:00
list . sort ( ( a , b ) = > pathStat
2022-03-09 22:43:05 +08:00
. indexWhere ( ( element ) = > element . path = = a . name )
2022-10-19 23:29:45 +09:00
. compareTo ( pathStat . indexWhere ( ( element ) = > element . path = = b . name ) ) ) ;
2022-07-11 16:07:49 +08:00
return ascending ? list : list . reversed . toList ( ) ;
2022-10-19 23:29:45 +09:00
} else if ( sortType = = SortBy . type ) {
2022-03-09 17:07:24 +08:00
// making list of only folders.
2022-03-09 22:43:05 +08:00
final dirs = list . where ( ( element ) = > element . isDirectory ) . toList ( ) ;
2022-03-09 17:07:24 +08:00
// sorting folders by name.
2022-03-09 22:43:05 +08:00
dirs . sort ( ( a , b ) = > a . name . toLowerCase ( ) . compareTo ( b . name . toLowerCase ( ) ) ) ;
2022-03-09 17:07:24 +08:00
// making the list of files
2022-03-09 22:43:05 +08:00
final files = list . where ( ( element ) = > element . isFile ) . toList ( ) ;
2022-03-09 17:07:24 +08:00
// sorting files list by extension.
2022-03-09 22:43:05 +08:00
files . sort ( ( a , b ) = > a . name
2022-03-09 17:07:24 +08:00
. toLowerCase ( )
. split ( ' . ' )
. last
2022-03-09 22:43:05 +08:00
. compareTo ( b . name . toLowerCase ( ) . split ( ' . ' ) . last ) ) ;
2022-08-03 22:03:31 +08:00
return ascending
? [ . . . dirs , . . . files ]
: [ . . . dirs . reversed . toList ( ) , . . . files . reversed . toList ( ) ] ;
2022-10-19 23:29:45 +09:00
} else if ( sortType = = SortBy . size ) {
2022-03-09 17:07:24 +08:00
// create list of path and size
2022-10-19 23:29:45 +09:00
Map < String , int > sizeMap = { } ;
2022-03-09 22:43:05 +08:00
for ( Entry e in list ) {
2022-10-19 23:29:45 +09:00
sizeMap [ e . name ] = e . size ;
2022-03-09 17:07:24 +08:00
}
// making list of only folders.
2022-03-09 22:43:05 +08:00
final dirs = list . where ( ( element ) = > element . isDirectory ) . toList ( ) ;
2022-03-09 17:07:24 +08:00
// sorting folder list by name.
2022-03-09 22:43:05 +08:00
dirs . sort ( ( a , b ) = > a . name . toLowerCase ( ) . compareTo ( b . name . toLowerCase ( ) ) ) ;
2022-03-09 17:07:24 +08:00
// making list of only flies.
2022-03-09 22:43:05 +08:00
final files = list . where ( ( element ) = > element . isFile ) . toList ( ) ;
2022-03-09 17:07:24 +08:00
// creating sorted list of [_sizeMapList] by size.
2022-10-19 23:29:45 +09:00
final List < MapEntry < String , int > > sizeMapList = sizeMap . entries . toList ( ) ;
sizeMapList . sort ( ( b , a ) = > a . value . compareTo ( b . value ) ) ;
2022-03-09 17:07:24 +08:00
// sort [list] according to [_sizeMapList]
2022-10-19 23:29:45 +09:00
files . sort ( ( a , b ) = > sizeMapList
2022-03-09 22:43:05 +08:00
. indexWhere ( ( element ) = > element . key = = a . name )
2022-10-19 23:29:45 +09:00
. compareTo ( sizeMapList . indexWhere ( ( element ) = > element . key = = b . name ) ) ) ;
2022-08-03 22:03:31 +08:00
return ascending
? [ . . . dirs , . . . files ]
: [ . . . dirs . reversed . toList ( ) , . . . files . reversed . toList ( ) ] ;
2022-03-09 17:07:24 +08:00
}
return [ ] ;
}
2023-03-15 10:43:27 +08:00
/// Define a general queue which can accepts different dialog type.
///
/// [Visibility]
/// The `_FileDialogType` and `_DialogEvent` are invisible for other models.
2023-03-15 15:13:23 +08:00
enum FileDialogType { overwrite , unknown }
2023-03-15 10:43:27 +08:00
2023-03-15 15:13:23 +08:00
class _FileDialogEvent extends BaseEvent < FileDialogType , Map < String , dynamic > > {
2023-03-15 10:43:27 +08:00
WeakReference < FileModel > fileModel ;
bool ? _overrideConfirm ;
2023-03-15 15:13:23 +08:00
bool _skip = false ;
2023-03-15 10:43:27 +08:00
_FileDialogEvent ( this . fileModel , super . type , super . data ) ;
void setOverrideConfirm ( bool ? confirm ) {
_overrideConfirm = confirm ;
}
2023-03-15 15:13:23 +08:00
void setSkip ( bool skip ) {
_skip = skip ;
}
2023-03-15 10:43:27 +08:00
@ override
2023-03-15 15:13:23 +08:00
EventCallback < Map < String , dynamic > > ? findCallback ( FileDialogType type ) {
2023-03-15 10:43:27 +08:00
final model = fileModel . target ;
if ( model = = null ) {
return null ;
}
switch ( type ) {
2023-03-15 15:13:23 +08:00
case FileDialogType . overwrite:
2023-03-15 10:43:27 +08:00
return ( data ) async {
2023-03-15 15:13:23 +08:00
return await model . overrideFileConfirm ( data ,
overrideConfirm: _overrideConfirm , skip: _skip ) ;
2023-03-15 10:43:27 +08:00
} ;
default :
2023-03-15 15:13:23 +08:00
debugPrint ( " Unknown event type: $ type with $ data " ) ;
return null ;
2023-03-15 10:43:27 +08:00
}
}
}
2023-03-15 15:13:23 +08:00
class FileDialogEventLoop
extends BaseEventLoop < FileDialogType , Map < String , dynamic > > {
2023-03-15 15:20:50 +08:00
bool ? _overrideConfirm ;
2023-03-15 15:13:23 +08:00
bool _skip = false ;
2023-03-15 10:43:27 +08:00
@ override
2023-03-15 15:13:23 +08:00
Future < void > onPreConsume (
BaseEvent < FileDialogType , Map < String , dynamic > > evt ) async {
2023-03-15 10:43:27 +08:00
var event = evt as _FileDialogEvent ;
2023-03-15 15:20:50 +08:00
event . setOverrideConfirm ( _overrideConfirm ) ;
2023-03-15 15:13:23 +08:00
event . setSkip ( _skip ) ;
2023-03-15 17:18:59 +08:00
debugPrint (
2023-03-15 22:44:07 +08:00
" FileDialogEventLoop: consuming<jobId: ${ evt . data [ ' id ' ] } overrideConfirm: $ _overrideConfirm , skip: $ _skip > " ) ;
2023-03-15 10:43:27 +08:00
}
2023-03-15 15:13:23 +08:00
2023-03-15 10:43:27 +08:00
@ override
Future < void > onEventsClear ( ) {
2023-03-15 15:20:50 +08:00
_overrideConfirm = null ;
2023-03-15 15:13:23 +08:00
_skip = false ;
2023-03-15 10:43:27 +08:00
return super . onEventsClear ( ) ;
}
2023-03-15 15:13:23 +08:00
void setOverrideConfirm ( bool ? confirm ) {
2023-03-15 15:20:50 +08:00
_overrideConfirm = confirm ;
2023-03-15 10:43:27 +08:00
}
2023-03-15 15:13:23 +08:00
void setSkip ( bool skip ) {
_skip = skip ;
}
}