Now I made a script that encrypts my videos and downloads them to the application storage, But now when I try to download a small file size I don't face any issues, but when I try to download a large file my application is stuck at 99% and take about 2 minutes to save the file to application storage.
`[![99% of file downloading](https://i.stack.imgur.com/W3UUu.jpg)](https://i.stack.imgur.com/W3UUu.jpg)
My Code
`
final String url = streamInfo.url.toString();
final dir = await getApplicationDocumentsDirectory();
String appDocPath = dir.path;
print("Downloading...");
var resp = await dio.get(url,
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
),
onReceiveProgress: (recivedBytes, totalBytes) {
setState(() {
progress = recivedBytes / totalBytes;
});
},
);
print(resp.data);
var encResult = _encryptData(resp.data);
_writeData(encResult, appDocPath + '/${widget.lessoneName.toString()}.aes');
print("File downloaded successfully");`
```
```
_encryptData(str){
final encrypted = MyEncrypt.myEncrypt.encryptBytes(str,iv:MyEncrypt.myIv);
return encrypted.bytes;
}
Future<String> _writeData(str,path) async{
print("Writting data");
File f = File(path);
print(f);
await f.writeAsBytes(str);
return f.absolute.toString();
}
````
```
class MyEncrypt{
static final myKey = esc.Key.fromUtf8('TechWithVPTechWithVPTechWithVP12');
static final myIv = esc.IV.fromUtf8('VivekPanacha1122');
static final myEncrypt = esc.Encrypter(esc.AES(myKey));
}
```
maybe you have found a solution to this issue
anyway I will write the correct solution for others who are facing such issues
you have to use isolate to reading data as bytes so that it has not affected on UI
with isolate, you send expensive operations to the background which means you made a new thread so that you catch the result after it done
by the way, remember your function (in this case read data as bytes) should be high-level method
here is the sample code
import 'dart:io';
import 'dart:isolate';
import 'package:dio/dio.dart' as dio;
import 'package:encrypt/encrypt.dart';
import 'package:path_provider/path_provider.dart';
class DownloadFileModel {
final SendPort sendPort;
final dio.Response<dynamic> response;
final String savePath;
DownloadVideoModel({
required this.response,
required this.sendPort,
required this.savePath,
});
}
class DownloadFile {
dio.Dio request = dio.Dio();
void downloadNewFile(String url) async {
final dir = await getApplicationDocumentsDirectory();
String appDocPath = dir.path;
var resp = await request.get(
url,
options: dio.Options(
responseType: dio.ResponseType.bytes,
followRedirects: false,
),
onReceiveProgress: (receivedBytes, totalBytes) {
print(receivedBytes / totalBytes);
},
);
ReceivePort port = ReceivePort();
Isolate.spawn(
whenDownloadCompleted,
DownloadFileModel(
response: resp, sendPort: port.sendPort, savePath: appDocPath),
);
port.listen((encryptedFilePath) {
print(encryptedFilePath);
port.close();
});
}
}
class MyEncrypt {
static final myKey = Key.fromUtf8('TechWithVPTechWithVPTechWithVP12');
static final myIv = IV.fromUtf8('VivekPanacha1122');
static final myEncrypt = Encrypter(AES(myKey));
}
void whenDownloadCompleted(DownloadVideoModel model) async {
SendPort sendPort = model.sendPort;
var encryptResult =
MyEncrypt.myEncrypt.encryptBytes(iv: MyEncrypt.myIv, model.response.data);
File encryptedFile = File("${model.savePath}/myFile.aes");
encryptedFile.writeAsBytes(encryptResult.bytes);
sendPort.send(encryptedFile.absolute.path);
}
For more info head over to flutter official document site
https://api.flutter.dev/flutter/dart-isolate/Isolate-class.html
Related
I have a project with encrypt an audio for secure my data audio and when the user purchase they can play the audio, but until now didn't find how to do that. I found another way by converting the audio data into a string and then encrypting it, I found a code but it's incomplete so I'm still confused about using it.
import 'dart:io';
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
class MediaFile {
Future<String> get docPath async {
Directory appDocDir = await getApplicationDocumentsDirectory();
return appDocDir.path;
}
Future<Uint8List> get audioFileData async {
final String path = await docPath;
final String theFilePath = '$path/test.mp3';
final File theFile = new File(theFilePath);
if (await theFile.exists()) {
return await theFile.readAsBytes();
} else {
File file = await FilePicker.getFile();
return await file.readAsBytes();
}
}
Future<String> dataToString() async {
Uint8List data = await audioFileData;
return new String.fromCharCodes(data);
}
Future<File> saveMediaAsString(String fileName, String fileContent) async {
String path = await docPath;
Directory dataDir = new Directory('$path/data');
if (await dataDir.exists()) {
File file = new File('$path/data/$fileName');
return file.writeAsString(fileContent);
}
await dataDir.create();
File file = new File('$path/data/$fileName');
return file.writeAsString(fileContent);
}
Future<String> readFromStringMediaFile(String fileName) async {
String path = await docPath;
File file = new File('$path/data/$fileName');
return file.readAsString();
}
Uint8List stringToData(dataStr) {
List<int> list = dataStr.codeUnits;
return new Uint8List.fromList(list);
}
Future<File> createAudioFile(String fileName, Uint8List bytes) async {
String path = await docPath;
File file = new File('$path/$fileName');
return await file.writeAsBytes(bytes);
}
//here you can see how to use them
Future<File> testFile() async {
String dataStr = await dataToString();
File savedFile = await saveMediaAsString('codedFile', dataStr);
String contentSavedFile = await readFromStringMediaFile('codedFile');
Uint8List bytes = stringToData(contentSavedFile);
return await createAudioFile('test.mp3', bytes);
}
}
Here is my code
Future<File> _downloadFile(String url, String filename) async {
http.Client _client = new http.Client();
var req = await _client.get(Uri.parse(url));
var bytes = req.bodyBytes;
// String dir = (await getApplicationDocumentsDirectory()).path;
String dir = await ExtStorage.getExternalStoragePublicDirectory(
ExtStorage.DIRECTORY_DOWNLOADS);
File file = new File('$dir/$filename');
await file.writeAsBytes(bytes);
return file;
}
/// Prints a sample pdf printer
void printPdfFile() async {
var file = await _downloadFile(
"http://www.africau.edu/images/default/sample.pdf", "test.pdf");
await FlutterPdfPrinter.printFile(file.path);
}
I am implementing print PDF files which is saved in my device. When trying to print the document I am getting error like "A problem occurred configuring project ':flutter_pdf_printer'."
I am using flutter_pdf_printer dependency to print PDF files.
Convert your string url to URI, please make sure you are adding http
http: ^0.13.4
import 'package:http/http.dart' as http;
Uri uri = Uri.parse('Your link here');
http.Response response = await http.get(uri);
var pdfData = response.bodyBytes;
await Printing.layoutPdf(onLayout: (PdfPageFormat
format) async => pdfData);
So I've created an app that creates a static file server using the httpserver API and I used VirtualDirectory to generate a list of items in a specified directory on Android. The app is working but whenever there is a large file it crashes and from what I understand it is because it loads way too much data into the memory.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:io';
import 'package:http_server/http_server.dart';
import 'package:path_provider/path_provider.dart';
void main(){
runApp(MaterialApp(
home:HomePage(),
));
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static Future<String> get getFilePath async{
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
static Future<File> getFile(String fileName) async{
final path = await getFilePath;
return File('$path/$fileName');
}
static Future<File> saveToFile(var data, String filePath, String fileName) async{
print("filePath+fileName: " + filePath+fileName);
final file = await getFile(filePath+fileName);
return file.writeAsString(data);
}
static Future<void> createDir(String filePath) async{
final path = await getFilePath;
Directory('$path/$filePath').create(recursive: true);
}
static Future<String> readFromFile(String filePath, String fileName) async{
try{
final file = await getFile('$filePath$fileName');
String fileContents = await file.readAsString();
return fileContents;
}catch(e){
final assetFile = await rootBundle.loadString('assets/$filePath$fileName');
if(filePath == ''){
await saveToFile(assetFile, '$filePath', '$fileName');
}else{
await createDir(filePath);
await saveToFile(assetFile, '$filePath', '$fileName');
}
print('copying the file from assets');
return '';
}
}
String data = '';
String rootDirPathStr;
assetFolder() async{
final v = await getFilePath;
Directory('$v').create(recursive: true);
await createDir('dist/css');
await createDir('dist/js');
await readFromFile('','index.html');
await readFromFile('dist/css','/style.min.css');
await readFromFile('dist/js','/serverCom.js');
await readFromFile('dist/js','/main.js');
await readFromFile('dist/js','/files.js');
await readFromFile('dist/js','/index.json');
}
serverInit() async{
// setState(() {
// data = "Server running on IP : "+server.address.toString()+" On Port : "+server.port.toString();
// });
//getting the dir
final rootDir = await getApplicationDocumentsDirectory();
rootDirPathStr = rootDir.path;
print("rootDirPathStr: " + rootDirPathStr);
//getting the dir
HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 8080);
print("Server running on IP : "+InternetAddress.anyIPv4.toString()+" On Port : "+server.port.toString());
VirtualDirectory rootVirDir = VirtualDirectory(rootDirPathStr)
..allowDirectoryListing = true;
VirtualDirectory userFilesVirDir = VirtualDirectory('/storage/emulated/0/IDM/')
..allowDirectoryListing = true;
// await userFilesVirDir.serve(server);
await for (HttpRequest request in server) {
String requestUriPath = request.uri.path;
String requestUriQuery = request.uri.query;
print('requestUriPath: $requestUriPath and requestUriQuery: $requestUriQuery');
if(requestUriPath == '/' && requestUriQuery == '') {
final path = await getFilePath;
await rootVirDir.serveFile(File('$path/index.html'), request);
}else if(requestUriQuery == 'file'){
print('file requested');
try{
await userFilesVirDir.serveRequest(request);
}catch(e){
print("error On file requested: $e");
}
}
else{
await rootVirDir.serveRequest(request);
}
}
}
#override
void initState() {
assetFolder();
serverInit();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child:Text('data'),
),
);
}
}
https://pub.dev/packages/shelf can return a ByteStream as the response... allowing virtually unlimited results. As reported in https://pub.dev/documentation/shelf/latest/shelf/Response/Response.ok.html:
body is the response body. It may be either a String, a List<int>, a Stream<List<int>>, or null to indicate no body.
So you can just open a stream on a large file, and hand it (perhaps through a StreamTransformer) directly in the response. I don't think http_server can do that.
Is there a way to upload large files to server?
I am using MultipartRequest with MultipartFile like:
List<int> fileBytes) async {
var request = new http.MultipartRequest("POST", Uri.parse(url));
request.files.add(http.MultipartFile.fromBytes(
'file',
fileBytes,
contentType: MediaType('application', 'octet-stream'),
filename: fileName));
request.headers.addAll(headers);
var streamedResponse = await request.send();
return await http.Response.fromStream(streamedResponse);
and reading the file like:
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.draggable = true;
uploadInput.click();
uploadInput.onChange.listen((e) {
final files = uploadInput.files;
final file = files[0];
final reader = new html.FileReader();
reader.onLoadEnd.listen((e) {
setState(() {
_bytesData =
Base64Decoder().convert(reader.result.toString().split(",").last);
_selectedFile = _bytesData;
});
});
reader.readAsDataUrl(file);
});
It is OK for files around 30 MB but for more than that, I am getting Error code: Out of Memory.
Am I doing something wrong? I saw somewhere
MultipartFile.fromBytes will give you some issues on bigger files, as the browser will limit your memory consumption.
And I think his solution is:
There’s a fromStream constructor. Usually, for bigger files, I just use HttpRequest, and put the File object in a FormData instance.
I used MultipartFile and MultipartFile.fromString and both times (for 150 MB file) that happened again.
How can I use this solution? or Is there a better way to do that for files more than 500 MB?
Update
Added an answer using Worker. This is not a great solution but I think this might help someone.
Currently, I solved the problem using this approach:
Import:
import 'package:universal_html/html.dart' as html;
Flutter part:
class Upload extends StatefulWidget {
#override
_UploadState createState() => _UploadState();
}
class _UploadState extends State<Upload> {
html.Worker myWorker;
html.File file;
_uploadFile() async {
String _uri = "/upload";
myWorker.postMessage({"file": file, "uri": _uri});
}
_selectFile() {
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.click();
uploadInput.onChange.listen((e) {
file = uploadInput.files.first;
});
}
#override
void initState() {
myWorker = new html.Worker('upload_worker.js');
myWorker.onMessage.listen((e) {
setState(() {
//progressbar,...
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
RaisedButton(
onPressed: _selectFile(),
child: Text("Select File"),
),
RaisedButton(
onPressed: _uploadFile(),
child: Text("Upload"),
),
],
);
}
}
Javascript part:
In the web folder (next to index.html), create the file 'upload_worker.js' .
self.addEventListener('message', async (event) => {
var file = event.data.file;
var url = event.data.uri;
uploadFile(file, url);
});
function uploadFile(file, url) {
var xhr = new XMLHttpRequest();
var formdata = new FormData();
var uploadPercent;
formdata.append('file', file);
xhr.upload.addEventListener('progress', function (e) {
//Use this if you want to have a progress bar
if (e.lengthComputable) {
uploadPercent = Math.floor((e.loaded / e.total) * 100);
postMessage(uploadPercent);
}
}, false);
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
postMessage("done");
}
}
xhr.onerror = function () {
// only triggers if the request couldn't be made at all
postMessage("Request failed");
};
xhr.open('POST', url, true);
xhr.send(formdata);
}
I solved the problem using only Dart code: The way to go is to use a chunk uploader.
This means to manually send the file in little parts. I send 99MB per request for example.
There is already a basic implementation of this online:
https://pub.dev/packages/chunked_uploader
You have to get a stream, this is possible with the file_picker or the drop_zone library. I used the drop_zone library because it provides the file picker and the drop zone functionality. In my code the dynamic file objects come from the drop_zone library.
Maybe you have to adjust the chunk uploader functionality depending one your backend. I use a django backend where I wrote a simple view that saves the files. In case of small files it can receive multipart requests with multiple files, in case of large files it can receive chunks and continiues to write a file if a previous chunk was received.
Here some parts of my code:
Python backend:
#api_view(["POST"])
def upload(request):
basePath = config.get("BasePath")
targetFolder = os.path.join(basePath, request.data["taskId"], "input")
if not os.path.exists(targetFolder):
os.makedirs(targetFolder)
for count, file in enumerate(request.FILES.getlist("Your parameter name on server side")):
path = os.path.join(targetFolder, file.name)
print(path)
with open(path, 'ab') as destination:
for chunk in file.chunks():
destination.write(chunk)
return HttpResponse("File(s) uploaded!")
flutter chunk uploader in my version:
import 'dart:async';
import 'dart:html';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter_dropzone/flutter_dropzone.dart';
import 'package:http/http.dart' as http;
class UploadRequest {
final Dio dio;
final String url;
final String method;
final String fileKey;
final Map<String, String>? bodyData;
final Map<String, String>? headers;
final CancelToken? cancelToken;
final dynamic file;
final Function(double)? onUploadProgress;
late final int _maxChunkSize;
int fileSize;
String fileName;
late DropzoneViewController controller;
UploadRequest(
this.dio, {
required this.url,
this.method = "POST",
this.fileKey = "file",
this.bodyData = const {},
this.cancelToken,
required this.file,
this.onUploadProgress,
int maxChunkSize = 1024 * 1024 * 99,
required this.controller,
required this.fileSize,
required this.fileName,
this.headers
}) {
_maxChunkSize = min(fileSize, maxChunkSize);
}
Future<Response?> upload() async {
Response? finalResponse;
for (int i = 0; i < _chunksCount; i++) {
final start = _getChunkStart(i);
print("start is $start");
final end = _getChunkEnd(i);
final chunkStream = _getChunkStream(start, end);
var request = http.MultipartRequest(
"POST",
Uri.parse(url),
);
//request.headers.addAll(_getHeaders(start, end));
request.headers.addAll(headers!);
//-----add other fields if needed
request.fields.addAll(bodyData!);
request.files.add(http.MultipartFile(
"Your parameter name on server side",
chunkStream,
fileSize,
filename: fileName// + i.toString(),
)
);
//-------Send request
var resp = await request.send();
//------Read response
String result = await resp.stream.bytesToString();
//-------Your response
print(result);
}
return finalResponse;
}
Stream<List<int>> _getChunkStream(int start, int end) async* {
print("reading from $start to $end");
final reader = FileReader();
final blob = file.slice(start, end);
reader.readAsArrayBuffer(blob);
await reader.onLoad.first;
yield reader.result as List<int>;
}
// Updating total upload progress
_updateProgress(int chunkIndex, int chunkCurrent, int chunkTotal) {
int totalUploadedSize = (chunkIndex * _maxChunkSize) + chunkCurrent;
double totalUploadProgress = totalUploadedSize / fileSize;
this.onUploadProgress?.call(totalUploadProgress);
}
// Returning start byte offset of current chunk
int _getChunkStart(int chunkIndex) => chunkIndex * _maxChunkSize;
// Returning end byte offset of current chunk
int _getChunkEnd(int chunkIndex) =>
min((chunkIndex + 1) * _maxChunkSize, fileSize);
// Returning a header map object containing Content-Range
// https://tools.ietf.org/html/rfc7233#section-2
Map<String, String> _getHeaders(int start, int end) {
var header = {'Content-Range': 'bytes $start-${end - 1}/$fileSize'};
if (headers != null) {
header.addAll(headers!);
}
return header;
}
// Returning chunks count based on file size and maximum chunk size
int get _chunksCount {
var result = (fileSize / _maxChunkSize).ceil();
return result;
}
}
Upload code that decides whether to upload multiple files in one request or one file divided to many requests:
//upload the large files
Map<String, String> headers = {
'Authorization': requester.loginToken!
};
fileUploadView.droppedFiles.sort((a, b) => b.size - a.size);
//calculate the sum of teh files:
double sumInMb = 0;
int divideBy = 1000000;
for (UploadableFile file in fileUploadView.droppedFiles) {
sumInMb += file.size / divideBy;
}
var dio = Dio();
int uploadedAlready = 0;
for (UploadableFile file in fileUploadView.droppedFiles) {
if (sumInMb < 99) {
break;
}
var uploadRequest = UploadRequest(
dio,
url: requester.backendApi+ "/upload",
file: file.file,
controller: fileUploadView.controller!,
fileSize: file.size,
fileName: file.name,
headers: headers,
bodyData: {
"taskId": taskId.toString(),
"user": requester.username!,
},
);
await uploadRequest.upload();
uploadedAlready++;
sumInMb -= file.size / divideBy;
}
if (uploadedAlready > 0) {
fileUploadView.droppedFiles.removeRange(0, uploadedAlready);
}
print("large files uploaded");
// upload the small files
//---Create http package multipart request object
var request = http.MultipartRequest(
"POST",
Uri.parse(requester.backendApi+ "/upload"),
);
request.headers.addAll(headers);
//-----add other fields if needed
request.fields["taskId"] = taskId.toString();
print("adding files selected with drop zone");
for (UploadableFile file in fileUploadView.droppedFiles) {
Stream<List<int>>? stream = fileUploadView.controller?.getFileStream(file.file);
print("sending " + file.name);
request.files.add(http.MultipartFile(
"Your parameter name on server side",
stream!,
file.size,
filename: file.name));
}
//-------Send request
var resp = await request.send();
//------Read response
String result = await resp.stream.bytesToString();
//-------Your response
print(result);
Hopefully this gives you a good overview how I solved the problem.
How to create folder in device storage to save files?
This is the code to download file into device :
import 'package:flutter_downloader/flutter_downloader.dart';
onTap: () async { //ListTile attribute
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
final taskId = await FlutterDownloader.enqueue(
url: 'http://myapp/${attach[index]}',
savedDir: '/sdcard/myapp',
showNotification: true, // show download progress in status bar (for Android)
clickToOpenDownloadedFile: true, // click on notification to open downloaded file (for Android)
);
},
You can create directory when app is launched.
In the initState() method of your first screen do the logic.
Ex.
createDir() async {
Directory baseDir = await getExternalStorageDirectory(); //only for Android
// Directory baseDir = await getApplicationDocumentsDirectory(); //works for both iOS and Android
String dirToBeCreated = "<your_dir_name>";
String finalDir = join(baseDir, dirToBeCreated);
var dir = Directory(finalDir);
bool dirExists = await dir.exists();
if(!dirExists){
dir.create(/*recursive=true*/); //pass recursive as true if directory is recursive
}
//Now you can use this directory for saving file, etc.
//In case you are using external storage, make sure you have storage permissions.
}
#override
initState(){
createDir(); //call your method here
super.initState();
}
You need to import these libraries:
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
From what I saw is, you are not using appDocDir and appDocPath anywhere, cause you are saving files in /sdcard/myapp.
Please check if you are asking and granting the storage permission and also there is no way to store files in sdcard like you are doing. Either make use of predefined directories like (Document, Pictures etc.) or use device root directory that starts with storage/emulated/0
//add in pubspec.yaml
path_provider:
//import this
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart';
//create Variable
String directory = (await getApplicationDocumentsDirectory()).path;
//initstate to create directory at launch time
#override
void initState() {
// TODO: implement initState
super.initState();
createFolder();
}
//call this method from init state to create folder if the folder is not exists
void createFolder() async {
if (await io.Directory(directory + "/yourDirectoryName").exists() != true) {
print("Directory not exist");
new io.Directory(directory + "/your DirectoryName").createSync(recursive: true);
//do your work
} else {
print("Directoryexist");
//do your work
}
}
Here is the Sample Codefor Creating a folder in Users internal storage Hope it Helps You
import 'dart:io' as Io;
Future _downloadImage() async {
try {
// request runtime permission
final permissionHandler = PermissionHandler();
final status = await permissionHandler
.checkPermissionStatus(PermissionGroup.storage);
if (status != PermissionStatus.granted) {
final requestRes = await permissionHandler
.requestPermissions([PermissionGroup.storage]);
if (requestRes[PermissionGroup.storage] != PermissionStatus.granted) {
_showSnackBar('Permission denined. Go to setting to granted!');
return _done();
}
}
}
var testdir =
await new Io.Directory('/storage/emulated/0/MyApp').create(recursive: true);
final filePath =
path.join(testdir.path, Filename + '.png');
print(filePath);
final file = File(filePath);
if (file.existsSync()) {
file.deleteSync();
}
//save image to storage
var request = await HttpClient().getUrl(Uri.parse(imageUrl));
var response = await request.close();
final Uint8List bytes = await consolidateHttpClientResponseBytes(response);
final saveFileResult =
saveImage({'filePath': filePath, 'bytes': bytes});
_showSnackBar(
saveFileResult
? 'Image downloaded successfully'
: 'Failed to download image',
);
} on PlatformException catch (e) {
_showSnackBar(e.message);
} catch (e, s) {
_showSnackBar('An error occurred');
debugPrint('Download image: $e, $s');
}
return _done();
}
First you need to import
1) import 'dart:io';
Second you need to create directory for the specified path in your async/await function
2) For example:
await new Directory('/storage/emulated/0/yourFolder').create(recursive: true);