forked from cwtch.im/cwtch-ui
Merge pull request 'download resumption, hash verification' (#231) from filefix2 into trunk
Reviewed-on: cwtch.im/cwtch-ui#231
This commit is contained in:
commit
0dc4849a5d
|
@ -1 +1 @@
|
|||
v1.3.1-25-g4adb501-2021-11-04-04-20
|
||||
v1.3.1-31-g706be7f-2021-11-04-22-49
|
|
@ -112,6 +112,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
if (dlID == null) {
|
||||
dlID = 0;
|
||||
}
|
||||
if (progress >= 0) {
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createDownloadNotificationChannel(fileKey, fileKey)
|
||||
|
@ -130,6 +131,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
//.setSilent(true)
|
||||
.build();
|
||||
notificationManager.notify(dlID, newNotification);
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
|
||||
}
|
||||
|
@ -241,6 +243,12 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.checkDownloadStatus(profile, fileKey)
|
||||
}
|
||||
"VerifyOrResumeDownload" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.verifyOrResumeDownload(profile, handle, fileKey)
|
||||
}
|
||||
"SendProfileEvent" -> {
|
||||
val onion = (a.get("onion") as? String) ?: ""
|
||||
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
||||
|
|
|
@ -52,6 +52,8 @@ abstract class Cwtch {
|
|||
void CreateDownloadableFile(String profile, String handle, String filenameSuggestion, String filekey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CheckDownloadStatus(String profile, String fileKey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profile, String handle, String filekey);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profile, String handle);
|
||||
|
|
|
@ -366,7 +366,12 @@ class CwtchNotifier {
|
|||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
|
||||
break;
|
||||
case "FileDownloadProgressUpdate":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], int.parse(data["Progress"]));
|
||||
var progress = int.parse(data["Progress"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], progress, int.parse(data["FileSizeInChunks"]));
|
||||
// progress == -1 is a "download was interrupted" message and should contain a path
|
||||
if (progress < 0) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadSetPath(data["FileKey"], data["FilePath"]);
|
||||
}
|
||||
break;
|
||||
case "FileDownloaded":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkFinished(data["FileKey"], data["FilePath"]);
|
||||
|
|
|
@ -413,6 +413,22 @@ class CwtchFfi implements Cwtch {
|
|||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
var fn = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_VerifyOrResumeDownload");
|
||||
// ignore: non_constant_identifier_names
|
||||
final VerifyOrResumeDownload = fn.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filekey.toNativeUtf8();
|
||||
VerifyOrResumeDownload(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
|
|
@ -154,6 +154,12 @@ class CwtchGomobile implements Cwtch {
|
|||
cwtchPlatform.invokeMethod("CheckDownloadStatus", {"ProfileOnion": profileOnion, "fileKey": fileKey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
cwtchPlatform.invokeMethod("VerifyOrResumeDownload", {"ProfileOnion": profileOnion, "handle": contactHandle, "filekey": filekey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2021-11-04T22:56:21+01:00",
|
||||
"@@last_modified": "2021-11-04T23:42:54+01:00",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
|
|
|
@ -362,11 +362,21 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||
}
|
||||
|
||||
void downloadUpdate(String fileKey, int progress) {
|
||||
void downloadUpdate(String fileKey, int progress, int numChunks) {
|
||||
if (!downloadActive(fileKey)) {
|
||||
print("error: received progress for unknown download " + fileKey);
|
||||
if (progress < 0) {
|
||||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||
this._downloads[fileKey]!.interrupted = true;
|
||||
notifyListeners();
|
||||
} else {
|
||||
print("error: received progress for unknown download " + fileKey);
|
||||
}
|
||||
} else {
|
||||
if (this._downloads[fileKey]!.interrupted) {
|
||||
this._downloads[fileKey]!.interrupted = false;
|
||||
}
|
||||
this._downloads[fileKey]!.chunksDownloaded = progress;
|
||||
this._downloads[fileKey]!.chunksTotal = numChunks;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +404,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
bool downloadActive(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey);
|
||||
return this._downloads.containsKey(fileKey) && !this._downloads[fileKey]!.interrupted;
|
||||
}
|
||||
|
||||
bool downloadGotManifest(String fileKey) {
|
||||
|
@ -405,10 +415,27 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.complete;
|
||||
}
|
||||
|
||||
bool downloadInterrupted(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.interrupted;
|
||||
}
|
||||
|
||||
void downloadMarkResumed(String fileKey) {
|
||||
if (this._downloads.containsKey(fileKey)) {
|
||||
this._downloads[fileKey]!.interrupted = false;
|
||||
}
|
||||
}
|
||||
|
||||
double downloadProgress(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.progress() : 0.0;
|
||||
}
|
||||
|
||||
// used for loading interrupted download info; use downloadMarkFinished for successful downloads
|
||||
void downloadSetPath(String fileKey, String path) {
|
||||
if (this._downloads.containsKey(fileKey)) {
|
||||
this._downloads[fileKey]!.downloadedTo = path;
|
||||
}
|
||||
}
|
||||
|
||||
String? downloadFinalPath(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.downloadedTo : null;
|
||||
}
|
||||
|
@ -431,6 +458,7 @@ class FileDownloadProgress {
|
|||
int chunksTotal = 1;
|
||||
bool complete = false;
|
||||
bool gotManifest = false;
|
||||
bool interrupted = false;
|
||||
String? downloadedTo;
|
||||
DateTime? timeStart;
|
||||
DateTime? timeEnd;
|
||||
|
|
|
@ -12,6 +12,7 @@ import '../../model.dart';
|
|||
class FileMessage extends Message {
|
||||
final MessageMetadata metadata;
|
||||
final String content;
|
||||
final RegExp nonHex = RegExp(r'[^a-f0-9]');
|
||||
|
||||
FileMessage(this.metadata, this.content);
|
||||
|
||||
|
@ -30,6 +31,10 @@ class FileMessage extends Message {
|
|||
String nonce = shareObj['n'] as String;
|
||||
int fileSize = shareObj['s'] as int;
|
||||
|
||||
if (!validHash(rootHash, nonce)) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
|
||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
@ -47,6 +52,9 @@ class FileMessage extends Message {
|
|||
String rootHash = shareObj['h'] as String;
|
||||
String nonce = shareObj['n'] as String;
|
||||
int fileSize = shareObj['s'] as int;
|
||||
if (!validHash(rootHash, nonce)) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
return FileBubble(
|
||||
nameSuggestion,
|
||||
rootHash,
|
||||
|
@ -61,4 +69,8 @@ class FileMessage extends Message {
|
|||
MessageMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
bool validHash(String hash, String nonce) {
|
||||
return hash.length == 128 && nonce.length == 48 && !hash.contains(nonHex) && !nonce.contains(nonHex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ class FileBubble extends StatefulWidget {
|
|||
}
|
||||
|
||||
class FileBubbleState extends State<FileBubble> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
|
@ -71,7 +76,7 @@ class FileBubbleState extends State<FileBubble> {
|
|||
} else if (Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey())) {
|
||||
// in this case, whatever marked download.complete would have also set the path
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey())!;
|
||||
wdgDecorations = Text('Saved to: ' + path + '\u202F');
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F');
|
||||
} else if (Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey())) {
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey())) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F');
|
||||
|
@ -84,12 +89,15 @@ class FileBubbleState extends State<FileBubble> {
|
|||
} else if (flagStarted) {
|
||||
// in this case, the download was done in a previous application launch,
|
||||
// so we probably have to request an info lookup
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey());
|
||||
if (path == null) {
|
||||
wdgDecorations = Text('Checking download status...' + '\u202F');
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey()) ) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey());
|
||||
} else {
|
||||
wdgDecorations = Text('Saved to: ' + path + '\u202F');
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
||||
wdgDecorations = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:[Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
wdgDecorations = Center(
|
||||
|
@ -167,6 +175,13 @@ class FileBubbleState extends State<FileBubble> {
|
|||
}
|
||||
}
|
||||
|
||||
void _btnResume() async {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
var handle = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).downloadMarkResumed(widget.fileKey());
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.VerifyOrResumeDownload(profileOnion, handle, widget.fileKey());
|
||||
}
|
||||
|
||||
// Construct an file chrome for the sender
|
||||
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
|
||||
return ListTile(
|
||||
|
|
Loading…
Reference in New Issue