2022-03-16 15:33:00 +08:00
import ' dart:async ' ;
2022-03-07 22:54:34 +08:00
import ' dart:convert ' ;
2022-03-16 15:33:00 +08:00
import ' package:flutter_easyloading/flutter_easyloading.dart ' ;
import ' package:flutter_hbb/common.dart ' ;
2022-03-12 21:42:05 +08:00
import ' package:flutter_hbb/pages/file_manager_page.dart ' ;
2022-03-07 22:54:34 +08:00
import ' package:flutter/material.dart ' ;
2022-03-12 21:42:05 +08:00
import ' package:path/path.dart ' as Path ;
2022-03-07 22:54:34 +08:00
import ' model.dart ' ;
2022-03-23 15:28:21 +08:00
enum SortBy { Name , Type , Modified , Size }
2022-03-07 22:54:34 +08:00
2022-03-11 01:28:13 +08:00
class FileModel extends ChangeNotifier {
var _isLocal = false ;
var _selectMode = false ;
2022-03-07 22:54:34 +08:00
2022-03-17 21:03:52 +08:00
var _localOption = DirectoryOption ( ) ;
var _remoteOption = DirectoryOption ( ) ;
2022-03-11 01:28:13 +08:00
var _jobId = 0 ;
2022-03-07 22:54:34 +08:00
2022-03-11 01:28:13 +08:00
var _jobProgress = JobProgress ( ) ; // from rust update
2022-03-07 22:54:34 +08:00
2022-03-11 01:28:13 +08:00
bool get isLocal = > _isLocal ;
2022-03-09 22:43:05 +08:00
2022-03-11 01:28:13 +08:00
bool get selectMode = > _selectMode ;
2022-03-07 22:54:34 +08:00
2022-03-11 01:28:13 +08:00
JobProgress get jobProgress = > _jobProgress ;
2022-03-07 22:54:34 +08:00
2022-03-11 01:28:13 +08:00
JobState get jobState = > _jobProgress . state ;
2022-03-07 22:54:34 +08:00
2022-03-23 15:28:21 +08:00
SortBy _sortStyle = SortBy . Name ;
2022-03-09 22:43:05 +08:00
2022-03-09 17:07:24 +08:00
SortBy get sortStyle = > _sortStyle ;
2022-03-07 22:54:34 +08:00
2022-03-09 22:43:05 +08:00
FileDirectory _currentLocalDir = FileDirectory ( ) ;
FileDirectory get currentLocalDir = > _currentLocalDir ;
2022-03-09 17:07:24 +08:00
FileDirectory _currentRemoteDir = FileDirectory ( ) ;
2022-03-09 22:43:05 +08:00
2022-03-07 22:54:34 +08:00
FileDirectory get currentRemoteDir = > _currentRemoteDir ;
2022-03-11 01:28:13 +08:00
FileDirectory get currentDir = > _isLocal ? currentLocalDir : currentRemoteDir ;
2022-03-17 21:03:52 +08:00
String get currentHome = > _isLocal ? _localOption . home : _remoteOption . home ;
String get currentShortPath {
2022-04-07 16:20:32 +08:00
if ( currentDir . path . startsWith ( currentHome ) ) {
2022-03-17 21:03:52 +08:00
var path = currentDir . path . replaceFirst ( currentHome , " " ) ;
2022-04-07 16:20:32 +08:00
if ( path . length = = 0 ) return " " ;
if ( path [ 0 ] = = " / " | | path [ 0 ] = = " \\ " ) {
2022-03-17 21:03:52 +08:00
// remove more '/' or '\'
path = path . replaceFirst ( path [ 0 ] , " " ) ;
}
return path ;
2022-04-07 16:20:32 +08:00
} else {
2022-03-17 21:03:52 +08:00
return currentDir . path . replaceFirst ( currentHome , " " ) ;
}
}
bool get currentShowHidden = >
_isLocal ? _localOption . showHidden : _remoteOption . showHidden ;
bool get currentIsWindows = >
_isLocal ? _localOption . isWindows : _remoteOption . isWindows ;
2022-03-16 15:33:00 +08:00
final _fileFetcher = FileFetcher ( ) ;
final _jobResultListener = JobResultListener < Map < String , dynamic > > ( ) ;
2022-03-11 01:28:13 +08:00
toggleSelectMode ( ) {
2022-04-07 22:58:47 +08:00
if ( jobState = = JobState . inProgress ) {
return ;
}
2022-03-11 01:28:13 +08:00
_selectMode = ! _selectMode ;
notifyListeners ( ) ;
}
togglePage ( ) {
_isLocal = ! _isLocal ;
notifyListeners ( ) ;
}
2022-03-17 21:03:52 +08:00
toggleShowHidden ( { bool ? showHidden , bool ? local } ) {
final isLocal = local ? ? _isLocal ;
if ( isLocal ) {
_localOption . showHidden = showHidden ? ? ! _localOption . showHidden ;
} else {
_remoteOption . showHidden = showHidden ? ? ! _remoteOption . showHidden ;
}
refresh ( ) ;
}
2022-03-11 01:28:13 +08:00
tryUpdateJobProgress ( Map < String , dynamic > evt ) {
try {
int id = int . parse ( evt [ ' id ' ] ) ;
2022-03-12 21:42:05 +08:00
_jobProgress . id = id ;
_jobProgress . fileNum = int . parse ( evt [ ' file_num ' ] ) ;
_jobProgress . speed = double . parse ( evt [ ' speed ' ] ) ;
_jobProgress . finishedSize = int . parse ( evt [ ' finished_size ' ] ) ;
notifyListeners ( ) ;
2022-03-11 01:28:13 +08:00
} catch ( e ) {
debugPrint ( " Failed to tryUpdateJobProgress,evt: ${ evt . toString ( ) } " ) ;
}
}
2022-03-16 15:33:00 +08:00
receiveFileDir ( Map < String , dynamic > evt ) {
2022-04-07 16:20:32 +08:00
if ( _remoteOption . home . isEmpty & & evt [ ' is_local ' ] = = " false " ) {
// init remote home, the connection will automatic read remote home when established,
try {
final fd = FileDirectory . fromJson ( jsonDecode ( evt [ ' value ' ] ) ) ;
fd . format ( _remoteOption . isWindows , sort: _sortStyle ) ;
_remoteOption . home = fd . path ;
2022-04-07 20:19:07 +08:00
debugPrint ( " init remote home: ${ fd . path } " ) ;
2022-04-07 16:20:32 +08:00
_currentRemoteDir = fd ;
notifyListeners ( ) ;
return ;
} finally { }
}
2022-03-16 15:33:00 +08:00
_fileFetcher . tryCompleteTask ( evt [ ' value ' ] , evt [ ' is_local ' ] ) ;
}
2022-03-11 01:28:13 +08:00
jobDone ( Map < String , dynamic > evt ) {
2022-03-16 15:33:00 +08:00
if ( _jobResultListener . isListening ) {
_jobResultListener . complete ( evt ) ;
return ;
}
_selectMode = false ;
2022-03-11 01:28:13 +08:00
_jobProgress . state = JobState . done ;
2022-03-12 21:42:05 +08:00
refresh ( ) ;
2022-03-11 01:28:13 +08:00
}
jobError ( Map < String , dynamic > evt ) {
2022-03-16 15:33:00 +08:00
if ( _jobResultListener . isListening ) {
_jobResultListener . complete ( evt ) ;
return ;
}
2022-03-17 21:03:52 +08:00
debugPrint ( " jobError $ evt " ) ;
2022-03-16 15:33:00 +08:00
_selectMode = false ;
2022-03-11 01:28:13 +08:00
_jobProgress . clear ( ) ;
_jobProgress . state = JobState . error ;
notifyListeners ( ) ;
}
2022-03-12 21:42:05 +08:00
jobReset ( ) {
_jobProgress . clear ( ) ;
notifyListeners ( ) ;
}
2022-04-07 16:20:32 +08:00
onReady ( ) async {
_localOption . home = FFI . getByName ( " get_home_dir " ) ;
_localOption . showHidden =
FFI . getByName ( " peer_option " , " local_show_hidden " ) . isNotEmpty ;
_remoteOption . showHidden =
FFI . getByName ( " peer_option " , " remote_show_hidden " ) . isNotEmpty ;
_remoteOption . isWindows = FFI . ffiModel . pi . platform = = " Windows " ;
2022-03-17 21:03:52 +08:00
debugPrint ( " remote platform: ${ FFI . ffiModel . pi . platform } " ) ;
2022-04-07 16:20:32 +08:00
await Future . delayed ( Duration ( milliseconds: 100 ) ) ;
2022-03-17 21:03:52 +08:00
final local = FFI . getByName ( " peer_option " , " local_dir " ) ;
final remote = FFI . getByName ( " peer_option " , " remote_dir " ) ;
openDirectory ( local . isEmpty ? _localOption . home : local , isLocal: true ) ;
openDirectory ( remote . isEmpty ? _remoteOption . home : remote , isLocal: false ) ;
2022-04-07 16:20:32 +08:00
await Future . delayed ( Duration ( seconds: 1 ) ) ;
if ( _currentLocalDir . path . isEmpty ) {
openDirectory ( _localOption . home , isLocal: true ) ;
}
if ( _currentRemoteDir . path . isEmpty ) {
openDirectory ( _remoteOption . home , isLocal: false ) ;
}
2022-03-17 21:03:52 +08:00
}
onClose ( ) {
DialogManager . reset ( ) ;
EasyLoading . dismiss ( ) ;
// save config
Map < String , String > msg = Map ( ) ;
msg [ " name " ] = " local_dir " ;
msg [ " value " ] = _currentLocalDir . path ;
FFI . setByName ( ' peer_option ' , jsonEncode ( msg ) ) ;
msg [ " name " ] = " local_show_hidden " ;
msg [ " value " ] = _localOption . showHidden ? " Y " : " " ;
FFI . setByName ( ' peer_option ' , jsonEncode ( msg ) ) ;
msg [ " name " ] = " remote_dir " ;
msg [ " value " ] = _currentRemoteDir . path ;
FFI . setByName ( ' peer_option ' , jsonEncode ( msg ) ) ;
msg [ " name " ] = " remote_show_hidden " ;
msg [ " value " ] = _remoteOption . showHidden ? " Y " : " " ;
FFI . setByName ( ' peer_option ' , jsonEncode ( msg ) ) ;
_currentLocalDir . clear ( ) ;
_currentRemoteDir . clear ( ) ;
2022-04-07 16:20:32 +08:00
_localOption . clear ( ) ;
_remoteOption . clear ( ) ;
2022-03-07 22:54:34 +08:00
}
2022-03-11 01:28:13 +08:00
refresh ( ) {
2022-03-17 21:03:52 +08:00
openDirectory ( currentDir . path ) ;
2022-03-07 22:54:34 +08:00
}
2022-03-09 17:07:24 +08:00
2022-03-16 15:33:00 +08:00
openDirectory ( String path , { bool ? isLocal } ) async {
isLocal = isLocal ? ? _isLocal ;
2022-03-17 21:03:52 +08:00
final showHidden =
isLocal ? _localOption . showHidden : _remoteOption . showHidden ;
final isWindows =
isLocal ? _localOption . isWindows : _remoteOption . isWindows ;
2022-03-16 15:33:00 +08:00
try {
2022-03-17 21:03:52 +08:00
final fd = await _fileFetcher . fetchDirectory ( path , isLocal , showHidden ) ;
fd . format ( isWindows , sort: _sortStyle ) ;
2022-03-16 15:33:00 +08:00
if ( isLocal ) {
_currentLocalDir = fd ;
} else {
_currentRemoteDir = fd ;
}
notifyListeners ( ) ;
} catch ( e ) {
debugPrint ( " Failed to openDirectory : $ e " ) ;
2022-03-09 17:07:24 +08:00
}
}
2022-03-07 22:54:34 +08:00
2022-03-17 21:03:52 +08:00
goHome ( ) {
openDirectory ( currentHome ) ;
}
2022-03-11 01:28:13 +08:00
goToParentDirectory ( ) {
2022-04-07 20:40:51 +08:00
final parent = PathUtil . dirname ( currentDir . path , currentIsWindows ) ;
2022-04-07 20:19:07 +08:00
openDirectory ( parent ) ;
2022-03-07 22:54:34 +08:00
}
2022-03-12 21:42:05 +08:00
sendFiles ( SelectedItems items ) {
if ( items . isLocal = = null ) {
debugPrint ( " Failed to sendFiles ,wrong path state " ) ;
return ;
}
_jobProgress . state = JobState . inProgress ;
final toPath =
items . isLocal ! ? currentRemoteDir . path : currentLocalDir . path ;
2022-03-17 21:03:52 +08:00
final isWindows =
items . isLocal ! ? _localOption . isWindows : _remoteOption . isWindows ;
final showHidden =
items . isLocal ! ? _localOption . showHidden : _remoteOption . showHidden ;
2022-03-12 21:42:05 +08:00
items . items . forEach ( ( from ) {
_jobId + + ;
final msg = {
" id " : _jobId . toString ( ) ,
" path " : from . path ,
2022-03-17 21:03:52 +08:00
" to " : PathUtil . join ( toPath , from . name , isWindows ) ,
" show_hidden " : showHidden . toString ( ) ,
2022-04-04 01:21:44 +08:00
" is_remote " : ( ! ( items . isLocal ! ) ) . toString ( )
2022-03-12 21:42:05 +08:00
} ;
FFI . setByName ( " send_files " , jsonEncode ( msg ) ) ;
} ) ;
}
2022-03-16 15:33:00 +08:00
bool removeCheckboxRemember = false ;
removeAction ( SelectedItems items ) async {
removeCheckboxRemember = false ;
2022-03-12 21:42:05 +08:00
if ( items . isLocal = = null ) {
2022-03-24 15:29:12 +08:00
debugPrint ( " Failed to removeFile, wrong path state " ) ;
2022-03-12 21:42:05 +08:00
return ;
}
2022-03-17 21:03:52 +08:00
final isWindows =
items . isLocal ! ? _localOption . isWindows : _remoteOption . isWindows ;
2022-03-16 15:33:00 +08:00
await Future . forEach ( items . items , ( Entry item ) async {
2022-03-12 21:42:05 +08:00
_jobId + + ;
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-03-16 15:33:00 +08:00
content = " ${ item . name } " ;
entries = [ item ] ;
} else if ( item . isDirectory ) {
2022-03-24 15:29:12 +08:00
title = translate ( " Not an empty directory " ) ;
2022-03-23 15:28:21 +08:00
showLoading ( translate ( " Waiting " ) ) ;
2022-03-16 15:33:00 +08:00
final fd = await _fileFetcher . fetchDirectoryRecursive (
2022-03-17 21:03:52 +08: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 ) ;
2022-03-16 15:33:00 +08:00
EasyLoading . dismiss ( ) ;
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 ) {
2022-03-16 15:33:00 +08:00
sendRemoveEmptyDir ( item . path , 0 , items . isLocal ! ) ;
}
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 } " : " " ;
2022-03-16 15:33:00 +08:00
content = dirShow + " $ count \n ${ entries [ i ] . path } " ;
2022-03-17 21:03:52 +08:00
final confirm =
await showRemoveDialog ( title , content , item . isDirectory ) ;
2022-03-16 15:33:00 +08:00
try {
if ( confirm = = true ) {
sendRemoveFile ( entries [ i ] . path , i , items . isLocal ! ) ;
final res = await _jobResultListener . start ( ) ;
// handle remove res;
if ( item . isDirectory & &
res [ ' file_num ' ] = = ( entries . length - 1 ) . toString ( ) ) {
sendRemoveEmptyDir ( item . path , i , items . isLocal ! ) ;
}
}
if ( removeCheckboxRemember ) {
if ( confirm = = true ) {
for ( var j = i + 1 ; j < entries . length ; j + + ) {
sendRemoveFile ( entries [ j ] . path , j , items . isLocal ! ) ;
final res = await _jobResultListener . start ( ) ;
if ( item . isDirectory & &
res [ ' file_num ' ] = = ( entries . length - 1 ) . toString ( ) ) {
sendRemoveEmptyDir ( item . path , i , items . isLocal ! ) ;
}
}
}
break ;
}
} catch ( e ) { }
2022-03-12 21:42:05 +08:00
}
} ) ;
2022-03-29 23:10:43 +08:00
_selectMode = false ;
2022-03-16 15:33:00 +08:00
refresh ( ) ;
}
2022-03-17 21:03:52 +08:00
Future < bool ? > showRemoveDialog (
String title , String content , bool showCheckbox ) async {
return await DialogManager . show < bool > ( ( setState , Function ( bool v ) close ) = >
CustomAlertDialog (
2022-03-16 15:33:00 +08:00
title: Row (
children: [
Icon ( Icons . warning , color: Colors . red ) ,
SizedBox ( width: 20 ) ,
Text ( title )
] ,
) ,
content: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisSize: MainAxisSize . min ,
children: [
Text ( content ) ,
SizedBox ( height: 5 ) ,
2022-03-23 15:28:21 +08:00
Text ( translate ( " This is irreversible! " ) ,
2022-03-17 21:03:52 +08:00
style: TextStyle ( fontWeight: FontWeight . bold ) ) ,
showCheckbox
? CheckboxListTile (
contentPadding: const EdgeInsets . all ( 0 ) ,
dense: true ,
controlAffinity: ListTileControlAffinity . leading ,
title: Text (
2022-03-23 15:28:21 +08:00
translate ( " Do this for all conflicts " ) ,
2022-03-17 21:03:52 +08:00
) ,
value: removeCheckboxRemember ,
onChanged: ( v ) {
if ( v = = null ) return ;
setState ( ( ) = > removeCheckboxRemember = v ) ;
} ,
)
: SizedBox . shrink ( )
2022-03-16 15:33:00 +08:00
] ) ,
actions: [
TextButton (
style: flatButtonStyle ,
2022-03-23 15:28:21 +08:00
onPressed: ( ) = > close ( false ) ,
child: Text ( translate ( " Cancel " ) ) ) ,
2022-03-16 15:33:00 +08:00
TextButton (
style: flatButtonStyle ,
2022-03-23 15:28:21 +08:00
onPressed: ( ) = > close ( true ) ,
child: Text ( translate ( " OK " ) ) ) ,
2022-03-16 15:33:00 +08:00
] ) ) ;
2022-03-12 21:42:05 +08:00
}
2022-03-16 15:33:00 +08:00
sendRemoveFile ( String path , int fileNum , bool isLocal ) {
final msg = {
" id " : _jobId . toString ( ) ,
" path " : path ,
" file_num " : fileNum . toString ( ) ,
" is_remote " : ( ! ( isLocal ) ) . toString ( )
} ;
FFI . setByName ( " remove_file " , jsonEncode ( msg ) ) ;
}
sendRemoveEmptyDir ( String path , int fileNum , bool isLocal ) {
final msg = {
" id " : _jobId . toString ( ) ,
" path " : path ,
" is_remote " : ( ! isLocal ) . toString ( )
} ;
FFI . setByName ( " remove_all_empty_dirs " , jsonEncode ( msg ) ) ;
}
createDir ( String path ) {
2022-03-17 21:03:52 +08:00
_jobId + + ;
2022-03-16 15:33:00 +08:00
final msg = {
" id " : _jobId . toString ( ) ,
" path " : path ,
" is_remote " : ( ! isLocal ) . toString ( )
} ;
2022-03-17 21:03:52 +08:00
FFI . setByName ( " create_dir " , jsonEncode ( msg ) ) ;
2022-03-16 15:33:00 +08:00
}
2022-03-07 22:54:34 +08:00
2022-03-29 23:10:43 +08:00
cancelJob ( int id ) {
2022-04-07 16:20:32 +08:00
FFI . setByName ( " cancel_job " , id . toString ( ) ) ;
2022-04-07 22:58:47 +08:00
jobReset ( ) ;
2022-03-29 23:10:43 +08:00
}
2022-03-17 21:03:52 +08:00
2022-03-09 22:43:05 +08:00
changeSortStyle ( SortBy sort ) {
_sortStyle = sort ;
_currentLocalDir . changeSortStyle ( sort ) ;
_currentRemoteDir . changeSortStyle ( sort ) ;
notifyListeners ( ) ;
2022-03-07 22:54:34 +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 ;
int _timeoutSecond = 5 ;
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 {
// Map<String,Completer<FileDirectory>> localTasks = Map(); // now we only use read local dir sync
Map < String , Completer < FileDirectory > > remoteTasks = Map ( ) ;
Map < int , Completer < FileDirectory > > readRecursiveTasks = Map ( ) ;
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 ;
2022-03-16 15:33:00 +08:00
c . completeError ( " Failed to read dir,timeout " ) ;
} ) ;
return c . future ;
}
Future < FileDirectory > registerReadRecursiveTask ( int id ) {
final tasks = readRecursiveTasks ;
if ( tasks . containsKey ( id ) ) {
throw " Failed to registerRemoveTask, already have same ReadRecursive job " ;
}
final c = Completer < FileDirectory > ( ) ;
tasks [ id ] = c ;
Timer ( Duration ( seconds: 2 ) , ( ) {
tasks . remove ( id ) ;
2022-04-04 01:21:44 +08:00
if ( c . isCompleted ) return ;
2022-03-16 15:33:00 +08:00
c . completeError ( " Failed to read dir,timeout " ) ;
} ) ;
return c . future ;
}
tryCompleteTask ( String ? msg , String ? isLocalStr ) {
if ( msg = = null | | isLocalStr = = null ) return ;
late final isLocal ;
late final tasks ;
if ( isLocalStr = = " true " ) {
isLocal = true ;
} else if ( isLocalStr = = " false " ) {
isLocal = false ;
} else {
return ;
}
try {
final fd = FileDirectory . fromJson ( jsonDecode ( msg ) ) ;
if ( fd . id > 0 ) {
// fd.id > 0 is result for read recursive
// TODO later,will be better if every fetch use ID,so that there will only one task map for read and recursive read
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 ) {
debugPrint ( " tryCompleteJob err : $ e " ) ;
}
}
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 {
2022-03-17 21:03:52 +08:00
final msg = { " path " : path , " show_hidden " : showHidden . toString ( ) } ;
2022-03-16 15:33:00 +08:00
if ( isLocal ) {
2022-03-17 21:03:52 +08:00
final res = FFI . getByName ( " read_local_dir_sync " , jsonEncode ( msg ) ) ;
2022-03-16 15:33:00 +08:00
final fd = FileDirectory . fromJson ( jsonDecode ( res ) ) ;
return fd ;
} else {
2022-03-17 21:03:52 +08:00
FFI . setByName ( " read_remote_dir " , jsonEncode ( msg ) ) ;
2022-03-16 15:33:00 +08:00
return registerReadTask ( isLocal , path ) ;
}
} catch ( e ) {
return Future . error ( e ) ;
}
}
Future < FileDirectory > fetchDirectoryRecursive (
2022-03-17 21:03:52 +08:00
int id , String path , bool isLocal , bool showHidden ) async {
// TODO test Recursive is show hidden default?
2022-03-16 15:33:00 +08:00
try {
final msg = {
" id " : id . toString ( ) ,
" path " : path ,
2022-03-17 21:03:52 +08:00
" show_hidden " : showHidden . toString ( ) ,
2022-03-16 15:33:00 +08:00
" is_remote " : ( ! isLocal ) . toString ( )
} ;
FFI . setByName ( " read_dir_recursive " , jsonEncode ( msg ) ) ;
return registerReadRecursiveTask ( id ) ;
} 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 ) {
entries . add ( new Entry . fromJson ( v ) ) ;
} ) ;
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 } ) {
entries . forEach ( ( entry ) {
entry . path = PathUtil . join ( path , entry . name , isWindows ) ;
} ) ;
if ( sort ! = null ) {
changeSortStyle ( sort ) ;
2022-03-16 15:33:00 +08:00
}
}
2022-03-11 01:28:13 +08:00
changeSortStyle ( SortBy sort ) {
entries = _sortList ( entries , sort ) ;
}
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 ;
bool get isDirectory = > entryType < = 3 ;
DateTime lastModified ( ) {
return DateTime . fromMillisecondsSinceEpoch ( modifiedTime * 1000 ) ;
}
}
enum JobState { none , inProgress , done , error }
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 ;
clear ( ) {
state = JobState . none ;
id = 0 ;
fileNum = 0 ;
speed = 0 ;
finishedSize = 0 ;
}
}
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 {
static final windowsContext = Path . Context ( style: Path . Style . windows ) ;
static final posixContext = Path . Context ( style: Path . Style . posix ) ;
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
}
class DirectoryOption {
String home ;
bool showHidden ;
bool isWindows ;
DirectoryOption (
{ 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-03-09 17:07:24 +08:00
// code from file_manager pkg after edit
2022-03-09 22:43:05 +08:00
List < Entry > _sortList ( List < Entry > list , SortBy sortType ) {
2022-03-23 15:28:21 +08:00
if ( sortType = = SortBy . Name ) {
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
// 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.
return [ . . . dirs , . . . files ] ;
2022-03-23 15:28:21 +08:00
} else if ( sortType = = SortBy . Modified ) {
2022-03-09 17:07:24 +08:00
// making the list of Path & DateTime
List < _PathStat > _pathStat = [ ] ;
2022-03-09 22:43:05 +08:00
for ( Entry e in list ) {
_pathStat . add ( _PathStat ( e . name , e . lastModified ( ) ) ) ;
2022-03-09 17:07:24 +08:00
}
// sort _pathStat according to date
_pathStat . sort ( ( b , a ) = > a . dateTime . compareTo ( b . dateTime ) ) ;
2022-03-09 22:43:05 +08:00
// sorting [list] according to [_pathStat]
2022-03-09 17:07:24 +08:00
list . sort ( ( a , b ) = > _pathStat
2022-03-09 22:43:05 +08:00
. indexWhere ( ( element ) = > element . path = = a . name )
. compareTo ( _pathStat . indexWhere ( ( element ) = > element . path = = b . name ) ) ) ;
2022-03-09 17:07:24 +08:00
return list ;
2022-03-23 15:28:21 +08: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-03-09 17:07:24 +08:00
return [ . . . dirs , . . . files ] ;
2022-03-23 15:28:21 +08:00
} else if ( sortType = = SortBy . Size ) {
2022-03-09 17:07:24 +08:00
// create list of path and size
Map < String , int > _sizeMap = { } ;
2022-03-09 22:43:05 +08:00
for ( Entry e in list ) {
_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.
final List < MapEntry < String , int > > _sizeMapList = _sizeMap . entries . toList ( ) ;
_sizeMapList . sort ( ( b , a ) = > a . value . compareTo ( b . value ) ) ;
// sort [list] according to [_sizeMapList]
files . sort ( ( a , b ) = > _sizeMapList
2022-03-09 22:43:05 +08:00
. indexWhere ( ( element ) = > element . key = = a . name )
2022-03-09 17:07:24 +08:00
. compareTo (
2022-03-09 22:43:05 +08:00
_sizeMapList . indexWhere ( ( element ) = > element . key = = b . name ) ) ) ;
2022-03-09 17:07:24 +08:00
return [ . . . dirs , . . . files ] ;
}
return [ ] ;
}