Uploading large pictures to FTP freezes the app - xamarin.forms

I am trying to upload some pictures to my FTP in a form. It´s working great on my Huawei P20, but it has been reported to me that on a phone with less RAM the app freezes when they are trying to upload larger pictures.
After the picture selection (max of 4) I resize the images and compress them to reduce the size, but with no luck
Code:
public static byte[] RotateImage(string path)
{
byte[] imageBytes;
var originalImage = BitmapFactory.DecodeFile(path);
var rotation = GetRotation(path);
//Width 3000 Height 4000
var width = (originalImage.Width * 0.25);
var height = (originalImage.Height * 0.25);
if(originalImage.Height>2400)
{
width = (originalImage.Width * 0.20);
height = (originalImage.Height * 0.20);
}
if (originalImage.Height < 600)
{
width = (originalImage.Width * 0.80);
height = (originalImage.Height * 0.80);
}
var scaledImage = Bitmap.CreateScaledBitmap(originalImage, (int)width, (int)height, true);
Bitmap rotatedImage = scaledImage;
if (rotation != 0)
{
var matrix = new Matrix();
matrix.PostRotate(rotation);
rotatedImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
scaledImage.Recycle();
scaledImage.Dispose();
}
using (var ms = new MemoryStream())
{
if (rotatedImage.Width > 1000 || rotatedImage.Height > 1000)
{
rotatedImage.Compress(Bitmap.CompressFormat.Jpeg, 30, ms);
}
if (rotatedImage.Width < 500 || rotatedImage.Height < 500)
{
rotatedImage.Compress(Bitmap.CompressFormat.Jpeg, 60, ms);
}
if (rotatedImage.Width <= 1000 && rotatedImage.Width >= 500)
{
rotatedImage.Compress(Bitmap.CompressFormat.Jpeg, 45, ms);
}
imageBytes = ms.ToArray();
}
originalImage.Recycle();
rotatedImage.Recycle();
originalImage.Dispose();
rotatedImage.Dispose();
GC.Collect();
return imageBytes;
}
Then I send them to the MessagingCenter and retrieve them in PCL.
The application freezes when I try to upload it to FTP
Code in PCL:
for (int i = 0; i < _images.Count; i++)
{
DependencyService.Get<IFtpWebRequest>().upload("FTP", _images[i], "SITE", "PASSWORD", "DIRECTORY");
}
and the platform specific code I am calling is:
public string upload(string FtpUrl, string fileName, string userName, string password, string UploadDirectory = "")
{
try
{
WebRequest request = WebRequest.Create(FtpUrl+UploadDirectory);
request.Method = WebRequestMethods.Ftp.MakeDirectory;
request.Credentials = new NetworkCredential(userName, password);
using (var resp = (FtpWebResponse)request.GetResponse())
{
}
}
catch(Exception e) { }
try
{
string PureFileName = new FileInfo(fileName).Name;
String uploadUrl = String.Format("{0}{1}/{2}", FtpUrl, UploadDirectory, PureFileName);
FtpWebRequest req = (FtpWebRequest)FtpWebRequest.Create(uploadUrl);
req.Proxy = null;
req.Method = WebRequestMethods.Ftp.UploadFile;
req.Credentials = new NetworkCredential(userName, password);
req.UseBinary = true;
req.UsePassive = true;
byte[] data = File.ReadAllBytes(fileName);
req.ContentLength = data.Length;
Stream stream = req.GetRequestStream();
stream.Write(data, 0, data.Length);
stream.Close();
FtpWebResponse res = (FtpWebResponse)req.GetResponse();
return res.StatusDescription;
}
catch (Exception err)
{
return err.ToString();
}
}
The expected result should be the app not freezing on any phone.
What could I do to prevent it?
Further increasing compression isnt best solution either as some phones upload it no problem and therefore I could achieve higher quality.
EDIT: When uploading a large picture to FTP and I check the pic on FTP its like 1/10 of the picture is uploaded, rest is blank
EDIT2: Moving the function to a different thread does not freeze the application anymore but still only part of the iamge is uploaded on devices with less memory, how do I somehow force the whole image to be uploaded?

When uploading a large picture to FTP and I check the pic on FTP its like 1/10 of the picture is uploaded, rest is blank
If the uploaded image is very large, then this will be a time-consuming operation. If it is placed in the main UI thread, it will consume a lot of memory and time. When the memory of the mobile phone is large, it may be able to do the task, but when When the phone is not big, then the problem will arise.
You need to move upload method to a backgroud thread, then it will not affect UI thread.
If in Android , Have a try with Task :
public async Task<string> upload(string FtpUrl, string fileName, string userName, string password, string UploadDirectory = "")
{
await Task.Run(() =>
{
try
{
WebRequest request = WebRequest.Create(FtpUrl+UploadDirectory);
request.Method = WebRequestMethods.Ftp.MakeDirectory;
request.Credentials = new NetworkCredential(userName, password);
using (var resp = (FtpWebResponse)request.GetResponse())
{
}
}
catch(Exception e) { }
...
});
}

Related

Retrieving Image from Chooser Intent in Android 10

I'm trying to update my app to work with androids new scoped storage rules in Android 10 and up, but am having the hardest time with it. I know I need to rebuild my app with new versions of java, but I just want to get it to work while I study and learn enough to do so. In a nutshell, I really need help. I have read so many different ways to make scoped storage work, and everybody seems to be doing it differently.
Just for clarification, what I am trying to do with the uri is both display in an imageview, then upload to database.
This code is working to take a picture and select images and videos in android 9, but in android 10, it only works when camera component captures a picture or a video. When a user selects an image or video from file, it returns a null pointer exception. Because I am pretty sure the error is in how I am dealing with the different chooser intents, I have shown the on result code first.
I have been unable to find a clear example of how to retrieve a usable image or video uri in android 10. If anybody can help, I would really appreciate it. I know I have much to learn.
if ((new java.io.File(_filePath)).exists()){
} else {
_filePath = vidfile.getAbsolutePath();
if ((new java.io.File(_filePath)).exists()){
} else {
ArrayList<String> _filePath_1 = new ArrayList<>();
if (_data != null) {
if (_data.getClipData() != null) {
for (int _index = 0; _index < _data.getClipData().getItemCount(); _index++) {
ClipData.Item _item = _data.getClipData().getItemAt(_index);
_filePath_1.add(FileUtil.convertUriToFilePath(getApplicationContext(),
_item.getUri()));
}
}
else {
_filePath_1.add(FileUtil.convertUriToFilePath(getApplicationContext(),
_data.getData()));
}
}
_filePath = _filePath_1.get((int)0);
}
}
Just in case I am wrong, here is the code for the click event to launch the chooser...
SimpleDateFormat date1 = new SimpleDateFormat("yyyyMMdd_HHmmss");
String fileName1 = date1.format(new Date()) + ".jpg";
picfile = new
File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath() +
File.separator + fileName1);
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri _uri_camr1 = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
_uri_camr1 = FileProvider.getUriForFile(getApplicationContext(),
getApplicationContext().getPackageName() + ".provider", picfile);
}
else {
_uri_camr1 = Uri.fromFile(picfile);
}
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, _uri_camr1);
takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
SimpleDateFormat date2 = new SimpleDateFormat("yyyyMMdd_HHmmss");
String fileName2 = date2.format(new Date()) + ".mp4";
vidfile = new
File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath() +
File.separator + fileName2);
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
Uri _uri_camr2 = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
_uri_camr2 = FileProvider.getUriForFile(getApplicationContext(),
getApplicationContext().getPackageName() + ".provider", vidfile);
}
else {
_uri_camr2 = Uri.fromFile(vidfile);
}
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, _uri_camr2);
takeVideoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*");
contentSelectionIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent[] intentArray = new Intent[]{ takePictureIntent, takeVideoIntent};
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Choose an action");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, REQ_CD_CAMR);
try this code. it copies the selected file to scoped storage and gives you the final path of scoped storage from where you can access it. try it out & let me know if you face any problem.
android.net.Uri sharedFileUri = android.net.Uri.fromFile(new java.io.File(_filepath));
java.io.FileInputStream input = null;
java.io.FileOutputStream output = null;
try {
String filePath = new java.io.File(getCacheDir(), "tmp").getAbsolutePath();
android.os.ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(sharedFileUri, "rw");
if (pfd != null) {
java.io.FileDescriptor fd = pfd.getFileDescriptor();
input = new java.io.FileInputStream (fd);
output = new java.io.FileOutputStream (filePath);
int read;
byte[] bytes = new byte[4096];
while ((read = input.read(bytes)) != -1) {
output.write(bytes, 0, read);
}
java.io.File sharedFile = new java.io.File(filePath);
String finalPath = sharedFile.getPath(); // this will provide you path to scoped storage. use this final path to access the selected file from scoped storage.
}
}catch(Exception ex) {
android.widget.Toast.makeText(this, ex.toString(), android.widget.Toast.LENGTH_SHORT).show();
} finally {
try {
input.close();
output.close();
} catch (Exception ignored) {
}
}

Passing images from one page to another in Xamarin Forms

In my app I need to pass images from one page to another page image view to display. I am taking a photo from camera and do some stuffs, then I want to send that images to the second page.
if (await isCamAvailable())
{
MediaFile photo1 = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions() { Directory = "NewBusiness", Name = "image1.jpg", PhotoSize = PhotoSize.MaxWidthHeight, MaxWidthHeight = 1024, CompressionQuality = 95 });
if (photo1 != null)
{
PhotoImage1.Source = ImageSource.FromStream(() => { return photo1.GetStream(); });
countList.Remove("a");
countList.Add("a");
}
}
Then I am added it to a string array by doing
private List<string> sendImgList = new List<string>();
sendImgList.Add(createImgByteString(photo1.GetStream()));
private string createImgByteString(Stream data)
{
var bytes = new byte[data.Length];
return Convert.ToBase64String(bytes);
}
Then from second page (for testing i just added only one image)
foreach (string ss in imgList) {
byte[] Base64Stream = Convert.FromBase64String(ss);
imgView.Source = ImageSource.FromStream(() => new MemoryStream(Base64Stream));
}
I followed this example. But image not showing.
https://forums.xamarin.com/discussion/139360/how-to-transfer-images-from-one-page-to-another
Also getting this in logcat..
[0:] ImageLoaderSourceHandler: Image data was invalid: Xamarin.Forms.StreamImageSource05-29 14:22:43.758 W/monodroid-assembly( 8737): typemap: unable to find mapping to a Java type from managed type 'System.Byte, mscorlib'
It seems that you used the Media.Plugin . Why don't you pass the ImageSource directly?
If you do want to convert it to byte array , check the following code
public byte[] GetImageStreamAsBytes(Stream input)
{
var buffer = new byte[16*1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
var imgDate = GetImageStreamAsBytes(photo1.GetStream());
It would be better to pass the byte array directly .
The best mode to pass parameter in pages is with Prism.
https://prismlibrary.com/docs/xamarin-forms/navigation/passing-parameters.html
>
_navigationService.NavigateAsync(new Uri("MainPage", new NavigationParameters
{
{ "key_parameter", image }
})));
And on other page:
>
public override void OnNavigatedTo(INavigationParameters parameters)
{
image = (Image)parameters["key_parameter"];
}

Decompress stream from HttpClient using SharpZipLib in Xamarin.Forms

I am trying decompress stream from HttpClient using SharpZipLib in Xamarin.Forms. This code perfectly works on iOS, but on Android CanDecompressEntry() always returns false. What i'm missing? Maybe Android need some permissions?
var zipStream = await httpClient.GetStreamAsync(url);
using (ZipInputStream s = new ZipInputStream(zipStream))
{
ZipEntry theEntry;
//const int size = 2048;
byte[] data = new byte[2048];
Debug.WriteLine(s.CanDecompressEntry);
while ((theEntry = s.GetNextEntry()) != null)
{
if (theEntry.IsFile)
{
string str = "";
while (true)
{
int size = s.Read(data, 0, data.Length);
if (size > 0)
{
str += new UTF8Encoding().GetString(data, 0, size);
}
else
{
files.Add(theEntry.Name.Substring(0,theEntry.Name.Length-5), str);
break;
}
}
}
}
}
return files;
Ok. I just set ConfigureAwait to false, and it works.
var zipStream = await httpClient.GetStreamAsync(url).ConfigureAwait(false);

Cant upload file ~200mb to azure blob storage by asp.net core server side

I have server part on Asp.net core which receives a file in Content-Type: multipart/form-data format header and send it to azure blob storage in a stream. But when I'm sending file about 200 MB and more I have an error
“The request body is too large and exceeds the maximum permissible limit”
as I searched it could happen in the old version of WindowsAzure.Storage but I have version 9.1.1. And as I looked deeper method UploadFromStreamAsync chank blob on 4 MB by default. So I don't know what to only to ask your help.
My controller:
public async Task<IActionResult> Post(string folder)
{
string azureBlobConnectionString = _configuration.GetConnectionString("BlobConnection");
// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(azureBlobConnectionString);
HttpResponseUploadClass responseUploadClass = await Request.StreamFile(folder, storageAccount);
FormValueProvider formModel = responseUploadClass.FormValueProvider;
var viewModel = new MyViewModel();
var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
valueProvider: formModel);
if (!bindingSuccessful)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
return Ok(responseUploadClass.Url);
}
And class where i send stream filestream to azure blob
public static async Task<HttpResponseUploadClass> StreamFile(this HttpRequest request, string folder, CloudStorageAccount blobAccount)
{
CloudBlobClient blobClient = blobAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(folder);
CloudBlockBlob blockBlob = null;
if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
{
throw new Exception($"Expected a multipart request, but got {request.ContentType}");
}
var formAccumulator = new KeyValueAccumulator();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(request.ContentType),
DefaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
ContentDispositionHeaderValue contentDisposition;
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
var disposition = ContentDispositionHeaderValue.Parse(section.ContentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
try
{
string fileName = HttpUtility.UrlEncode(disposition.FileName.Value.Replace("\"", ""), Encoding.UTF8);
blockBlob = container.GetBlockBlobReference(Guid.NewGuid().ToString());
blockBlob.Properties.ContentType = GetMimeTypeByWindowsRegistry(fileName);
blockBlob.Properties.ContentDisposition = "attachment; filename*=UTF-8''" + fileName;
await blockBlob.UploadFromStreamAsync(section.Body);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
{
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
var encoding = GetEncoding(section);
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
var value = await streamReader.ReadToEndAsync();
if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
{
value = String.Empty;
}
formAccumulator.Append(key.Value, value);
if (formAccumulator.ValueCount > DefaultFormOptions.ValueCountLimit)
{
throw new InvalidDataException($"Form key count limit {DefaultFormOptions.ValueCountLimit} exceeded.");
}
}
}
}
section = await reader.ReadNextSectionAsync();
}
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
return new HttpResponseUploadClass{FormValueProvider = formValueProvider, Url = blockBlob?.Uri.ToString()};
}
As you have said, each block blob can be a different size, up to a maximum of 100 MB (4 MB for requests using REST versions before 2016-05-31), and a block blob can include up to 50,000 blocks.
If you are writing a block blob that is no more than 256 MB (64 MB for requests using REST versions before 2016-05-31) in size, you can upload it in its entirety with a single write operation, see Put Blob.
Storage clients default to a 32 MB maximum single block upload, settable using the SingleBlobUploadThresholdInBytes property.
When a block blob upload is larger than the value in this property, storage clients break the file into blocks.
You can set the number of threads used to upload the blocks in parallel using the ParallelOperationThreadCount property.
BlobRequestOptions requestoptions = new BlobRequestOptions()
{
SingleBlobUploadThresholdInBytes = 1024 * 1024 * 50, //50MB
ParallelOperationThreadCount = 12,
};
CloudStorageAccount account = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient blobclient = account.CreateCloudBlobClient();
blobclient.DefaultRequestOptions = requestoptions;
CloudBlobContainer blobcontainer = blobclient.GetContainerReference("uploadfiles");
blobcontainer.CreateIfNotExists();
CloudBlockBlob blockblob = blobcontainer.GetBlockBlobReference("bigfiles");
For more details, you could refer to this thread.

Download large file in small chunks in C#

I need to download some file which is more than 25 MB large, but my network only allow to request a file of 25 MB only.
I am using following code
const long DefaultSize = 26214400;
long Chunk = 26214400;
long offset = 0;
byte[] bytesInStream;
public void Download(string url, string filename)
{
long size = Size(url);
int blocksize = Convert.ToInt32(size / DefaultSize);
int remainder = Convert.ToInt32(size % DefaultSize);
if (remainder > 0) { blocksize++; }
FileStream fileStream = File.Create(#"D:\Download TEST\" + filename);
for (int i = 0; i < blocksize; i++)
{
if (i == blocksize - 1)
{
Chunk = remainder;
}
HttpWebRequest req = (HttpWebRequest)System.Net.WebRequest.Create(url);
req.Method = "GET";
req.AddRange(Convert.ToInt32(offset), Convert.ToInt32(Chunk+offset));
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
// StreamReader sr = new StreamReader(resp.GetResponseStream());
using (Stream responseStream = resp.GetResponseStream())
{
bytesInStream = new byte[Chunk];
responseStream.Read(bytesInStream, 0, (int)bytesInStream.Length);
// Use FileStream object to write to the specified file
fileStream.Seek((int)offset, SeekOrigin.Begin);
fileStream.Write(bytesInStream,0, bytesInStream.Length);
}
offset += Chunk;
}
fileStream.Close();
}
public long Size(string url)
{
System.Net.WebRequest req = System.Net.HttpWebRequest.Create(url);
req.Method = "HEAD";
System.Net.WebResponse resp = req.GetResponse();
resp.Close();
return resp.ContentLength;
}
It is properly writing content on disk but content is not working
You should check how much was read before write, something like this (and you don't need to remember the offset to seek, the seek is automatic when you write):
int read;
do
{
read = responseStream.Read(bytesInStream, 0, (int)bytesInStream.Length);
if (read > 0)
fileStream.Write(bytesInStream, 0, read);
}
while(read > 0);
There is a similar SO questions that might help you
Segmented C# file downloader
and
How to open multiple connections to download single file?
Also this code project article
http://www.codeproject.com/Tips/307548/Resume-Suppoert-Downloading
Range is zero based and you should subtract 1 from upper bound.
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, chunkSize + offset - 1);
I published correct code fragment at the following link:
https://stackoverflow.com/a/48019611/1099716
Akka streams can help download file in small chunks from a System.IO.Stream using multithreading. https://getakka.net/articles/intro/what-is-akka.html
The Download method will append the bytes to the file starting with long fileStart. If the file does not exist, fileStart value must be 0.
using Akka.Actor;
using Akka.IO;
using Akka.Streams;
using Akka.Streams.Dsl;
using Akka.Streams.IO;
private static Sink<ByteString, Task<IOResult>> FileSink(string filename)
{
return Flow.Create<ByteString>()
.ToMaterialized(FileIO.ToFile(new FileInfo(filename), FileMode.Append), Keep.Right);
}
private async Task Download(string path, Uri uri, long fileStart)
{
using (var system = ActorSystem.Create("system"))
using (var materializer = system.Materializer())
{
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.AddRange(fileStart);
using (WebResponse response = request.GetResponse())
{
Stream stream = response.GetResponseStream();
await StreamConverters.FromInputStream(() => stream, chunkSize: 1024)
.RunWith(FileSink(path), materializer);
}
}
}

Resources