Xamarin Forms - Sending multiple images to filestream with MediaGallery plugin - c#

I'm working on an Xamarin Forms application and I have added some code to select multiple images from the phone gallery, I then need to send all selected images to file steam.
Selecting multiple images from the gallery is working fine, but only the last selected image is being sent.
Below is my code
private async Task OnGetExistingPhotoAsync() {
try {
// Selecting multiple images with the MediaGallery plugin
var results = await MediaGallery.PickAsync(3, MediaFileType.Image);
var parameter = new NavigationParameters();
if (results?.Files == null) {
return;
}
foreach(var photo in results.Files) {
if (photo == null) {
PhotoPath = null;
return;
}
string newFile = Path.Combine(FileSystem.CacheDirectory, photo.NameWithoutExtension);
using(var stream = await photo.OpenReadAsync())
using(var newStream = File.OpenWrite(newFile))
await stream.CopyToAsync(newStream);
PhotoPath = newFile;
// This here is only sending the PATH of the last selected image from the gallery
parameter.Add("PhotoPath", PhotoPath);
// In my attempt here, I tried to add each image PATH to a list Array
// But this is not working!
/*
List<string> imageFiles = new List<string>();
imageFiles.Add(Path.Combine(FileSystem.CacheDirectory, photo.NameWithoutExtension));
String[] newFile = imageFiles.ToArray();
using (var stream = await photo.OpenReadAsync())
using (var newStream = File.OpenWrite(newFile)) // This here says that it cannot convert ( string[] to string )
await stream.CopyToAsync(newStream);
parameter.Add("PhotoPath", PhotoPath);
*/
}
//OUTPUT : The PATH is : /data/user/0/se.company.trret.cyn/cache/IMG_20220307_005342
Console.WriteLine("The PATH is : " + PhotoPath);
await _navigationService.GoBackAsync(parameter);
} catch (FeatureNotSupportedException fnsEx) {
await App.Current.MainPage.DisplayAlert("Ett fel inträffade", "Den här funktionen stöds inte av din enhet.", "Ok");
} catch (PermissionException pEx) {
await App.Current.MainPage.DisplayAlert("Ett fel inträffade", "Cykelstaden saknar rättigheter för att läsa dina filer.", "Ok");
} catch (Exception e) {
await App.Current.MainPage.DisplayAlert("Ett fel inträffade", "Ett oväntat fel inträffade. Var god försök igen.", "Ok");
Crashes.TrackError(e);
}
}

Related

Access token empty error when uploading large files to a ToDoTask using Graph Api

I am trying to attach large files to a ToDoTask using the Graph Api using the example in the docs for attaching large files for ToDoTask and the recommend class LargeFileUploadTask for uploading large files.
I have done this sucessfully before with attaching large files to emails and sending so i used that as base for the following method.
public async Task CreateTaskBigAttachments( string idList, string title, List<string> categories,
BodyType contentType, string content, Importance importance, bool isRemindOn, DateTime? dueTime, cAttachment[] attachments = null)
{
try
{
var _newTask = new TodoTask
{
Title = title,
Categories = categories,
Body = new ItemBody()
{
ContentType = contentType,
Content = content,
},
IsReminderOn = isRemindOn,
Importance = importance
};
if (dueTime.HasValue)
{
var _timeZone = TimeZoneInfo.Local;
_newTask.DueDateTime = DateTimeTimeZone.FromDateTime(dueTime.Value, _timeZone.StandardName);
}
var _task = await _graphServiceClient.Me.Todo.Lists[idList].Tasks.Request().AddAsync(_newTask);
//Add attachments
if (attachments != null)
{
if (attachments.Length > 0)
{
foreach (var _attachment in attachments)
{
var _attachmentContentSize = _attachment.ContentBytes.Length;
var _attachmentInfo = new AttachmentInfo
{
AttachmentType = AttachmentType.File,
Name = _attachment.FileName,
Size = _attachmentContentSize,
ContentType = _attachment.ContentType
};
var _uploadSession = await _graphServiceClient.Me
.Todo.Lists[idList].Tasks[_task.Id]
.Attachments.CreateUploadSession(_attachmentInfo).Request().PostAsync();
using (var _stream = new MemoryStream(_attachment.ContentBytes))
{
_stream.Position = 0;
LargeFileUploadTask<TaskFileAttachment> _largeFileUploadTask = new LargeFileUploadTask<TaskFileAttachment>(_uploadSession, _stream, MaxChunkSize);
try
{
await _largeFileUploadTask.UploadAsync();
}
catch (ServiceException errorGraph)
{
if (errorGraph.StatusCode == HttpStatusCode.InternalServerError || errorGraph.StatusCode == HttpStatusCode.BadGateway
|| errorGraph.StatusCode == HttpStatusCode.ServiceUnavailable || errorGraph.StatusCode == HttpStatusCode.GatewayTimeout)
{
Thread.Sleep(1000); //Wait time until next attempt
//Try again
await _largeFileUploadTask.ResumeAsync();
}
else
throw errorGraph;
}
}
}
}
}
}
catch (ServiceException errorGraph)
{
throw errorGraph;
}
catch (Exception ex)
{
throw ex;
}
}
Up to the point of creating the task everything goes well, it does create the task for the user and its properly shown in the user tasks list. Also, it does create an upload session properly.
The problem comes when i am trying to upload the large file in the UploadAsync instruction.
The following error happens.
Code: InvalidAuthenticationToken Message: Access token is empty.
But according to the LargeFileUploadTask doc , the client does not need to set Auth Headers.
param name="baseClient" To use for making upload requests. The client should not set Auth headers as upload urls do not need them.
Is not LargeFileUploadTask allowed to be used to upload large files to a ToDoTask?
If not then what is the proper way to upload large files to a ToDoTask using the Graph Api, can someone provide an example?
If you want, you can raise an issue for the same with the details here, so that they can have look: https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues.
It seems like its a bug and they are working on it.
Temporarily I did this code to deal with the issue of the large files.
var _task = await _graphServiceClient.Me.Todo.Lists[idList].Tasks.Request().AddAsync(_newTask);
//Add attachments
if (attachments != null)
{
if (attachments.Length > 0)
{
foreach (var _attachment in attachments)
{
var _attachmentContentSize = _attachment.ContentBytes.Length;
var _attachmentInfo = new AttachmentInfo
{
AttachmentType = AttachmentType.File,
Name = _attachment.FileName,
Size = _attachmentContentSize,
ContentType = _attachment.ContentType
};
var _uploadSession = await _graphServiceClient.Me
.Todo.Lists[idList].Tasks[_task.Id]
.Attachments.CreateUploadSession(_attachmentInfo).Request().PostAsync();
// Get the upload URL and the next expected range from the response
string _uploadUrl = _uploadSession.UploadUrl;
using (var _stream = new MemoryStream(_attachment.ContentBytes))
{
_stream.Position = 0;
// Create a byte array to hold the contents of each chunk
byte[] _chunk = new byte[MaxChunkSize];
//Bytes to read
int _bytesRead = 0;
//Times the stream has been read
var _ind = 0;
while ((_bytesRead = _stream.Read(_chunk, 0, _chunk.Length)) > 0)
{
// Calculate the range of the current chunk
string _currentChunkRange = $"bytes {_ind * MaxChunkSize}-{_ind * MaxChunkSize + _bytesRead - 1}/{_stream.Length}";
//Despues deberiamos calcular el next expected range en caso de ocuparlo
// Create a ByteArrayContent object from the chunk
ByteArrayContent _byteArrayContent = new ByteArrayContent(_chunk, 0, _bytesRead);
// Set the header for the current chunk
_byteArrayContent.Headers.Add("Content-Range", _currentChunkRange);
_byteArrayContent.Headers.Add("Content-Type", _attachment.ContentType);
_byteArrayContent.Headers.Add("Content-Length", _bytesRead.ToString());
// Upload the chunk using the httpClient Request
var _client = new HttpClient();
var _requestMessage = new HttpRequestMessage()
{
RequestUri = new Uri(_uploadUrl + "/content"),
Method = HttpMethod.Put,
Headers =
{
{ "Authorization", bearerToken },
}
};
_requestMessage.Content = _byteArrayContent;
var _response = await _client.SendAsync(_requestMessage);
if (!_response.IsSuccessStatusCode)
throw new Exception("File attachment failed");
_ind++;
}
}
}
}
}

Xamarin: How to delete files using CrossFilePicker Xamarin Forms

I am using the following code to pick a file using CrossFilePicker in Xamarin forms. Goal is to delete the selected file in Xamarin.Android project, file still intact after running the command.
FileData filedata = await CrossFilePicker.Current.PickFile();
Android.Net.Uri uri = Android.Net.Uri.Parse(filedata.FilePath);
Java.IO.File file = new Java.IO.File(uri.Path);
File.Delete(file.AbsolutePath);
await DisplayAlert("✅", file.AbsolutePath, "Okay");
When using the DisplayAlert to check the filepath, I saw the below path, but file doesn't delete!!
/storage/emulated/0/documents/SalesRecords.db3
I recommend you to use Essentials.FilePicker, it allows you to pick file with code like:
async Task<FileResult> PickAndShow(PickOptions options)
{
try
{
var result = await FilePicker.PickAsync(options);
if (result != null)
{
Text = $"File Name: {result.FileName}";
if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase))
{
var stream = await result.OpenReadAsync();
Image = ImageSource.FromStream(() => stream);
}
}
return result;
}
catch (Exception ex)
{
// The user canceled or something went wrong
}
return null;
}
And once you get the result you can delete the file like:
FileResult file= await PickAndShow(PickOptions.Default);
File.Delete(file.FullPath);

How to create zip file in memory?

I have to create a zip file from set of urls. and it should have a proper folder structure.
So i tried like
public async Task<byte[]> CreateZip(Guid ownerId)
{
try
{
string startPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "zipFolder");//base folder
if (Directory.Exists(startPath))
{
DeleteAllFiles(startPath);
Directory.Delete(startPath);
}
Directory.CreateDirectory(startPath);
string zipPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{ownerId.ToString()}"); //folder based on ownerid
if (Directory.Exists(zipPath))
{
DeleteAllFiles(zipPath);
Directory.Delete(zipPath);
}
Directory.CreateDirectory(zipPath);
var attachemnts = await ReadByOwnerId(ownerId);
attachemnts.Data.ForEach(i =>
{
var fileLocalPath = $"{startPath}\\{i.Category}";
if (!Directory.Exists(fileLocalPath))
{
Directory.CreateDirectory(fileLocalPath);
}
using (var client = new WebClient())
{
client.DownloadFile(i.Url, $"{fileLocalPath}//{i.Flags ?? ""}_{i.FileName}");
}
});
var zipFilename = $"{zipPath}//result.zip";
if (File.Exists(zipFilename))
{
File.Delete(zipFilename);
}
ZipFile.CreateFromDirectory(startPath, zipFilename, CompressionLevel.Fastest, true);
var result = System.IO.File.ReadAllBytes(zipFilename);
return result;
}
catch (Exception ex)
{
var a = ex;
return null;
}
}
currently im writing all files in my base directory(may be not a good idea).corrently i have to manually delete all folders and files to avoid exception/unwanted files. Can everything be written in memory?
What changes required to write all files and folder structure in memory?
No you can't. Not with the built in Dotnet any way.
As per my comment I would recommend storing the files in a custom location based on a Guid or similar. Eg:
"/xxxx-xxxx-xxxx-xxxx/Folder-To-Zip/....".
This would ensure you could handle multiple requests with the same files or similar file / folder names.
Then you just have to cleanup and delete the folder again afterwards so you don't run out of space.
Hope the below code does the job.
public async Task<byte[]> CreateZip(Guid ownerId)
{
try
{
string startPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}_zipFolder");//folder to add
Directory.CreateDirectory(startPath);
var attachemnts = await ReadByOwnerId(ownerId);
attachemnts.Data = filterDuplicateAttachments(attachemnts.Data);
//filtering youtube urls
attachemnts.Data = attachemnts.Data.Where(i => !i.Flags.Equals("YoutubeUrl", StringComparison.OrdinalIgnoreCase)).ToList();
attachemnts.Data.ForEach(i =>
{
var fileLocalPath = $"{startPath}\\{i.Category}";
if (!Directory.Exists(fileLocalPath))
{
Directory.CreateDirectory(fileLocalPath);
}
using (var client = new WebClient())
{
client.DownloadFile(i.Url, $"{fileLocalPath}//{i.Flags ?? ""}_{i.FileName}");
}
});
using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
System.IO.DirectoryInfo di = new DirectoryInfo(startPath);
var allFiles = di.GetFiles("",SearchOption.AllDirectories);
foreach (var attachment in allFiles)
{
var file = File.OpenRead(attachment.FullName);
var type = attachemnts.Data.Where(i => $"{ i.Flags ?? ""}_{ i.FileName}".Equals(attachment.Name, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
var entry = zipArchive.CreateEntry($"{type.Category}/{attachment.Name}", CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
file.CopyTo(entryStream);
}
}
}
var result = ms.ToArray();
return result;
}
}
catch (Exception ex)
{
var a = ex;
return null;
}
}

How to write the contents of a Onedrive<Item> to a local file

In my app, I am using OneDrive to keep data in sync. I am successfully writing the file to OneDrive, but am having no luck replacing the local outdated data with the newer OneDrive data.
My current method, which completes without throwing an exception, does not return the same text data that the file on OneDrive contains.
Goal of the method is to compare the datemodified to the OneDrive file to the local file, and if OneDrive is newer, write the contents of the OndeDrive file to the local StorageFile, and then return it to be de-serialized.
private async Task<string> GetSavedDataFileAsync(string filename)
{
string filepath = _appFolder + #"\" + KOWGame + #"\" + filename;
StorageFile localread;
BasicProperties localprops = null;
string txt;
try
{
localread = await local.GetFileAsync(filepath);
localprops = await localread.GetBasicPropertiesAsync();
}
catch (FileNotFoundException)
{ localread = null; }
if (_userDrive != null)
{
if (_userDrive.IsAuthenticated)
{
try
{
Item item = await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Request().GetAsync();
if (item != null)
{
DateTimeOffset drivemodified = (DateTimeOffset)item.FileSystemInfo.LastModifiedDateTime;
if (localprops != null)
{
if (drivemodified > localprops.DateModified)
{
Stream stream = await localread.OpenStreamForWriteAsync();
using (stream)
{ await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Request().GetAsync(); }
}
}
}
}
catch (OneDriveException e)
{
if (e.IsMatch(OneDriveErrorCode.ActivityLimitReached.ToString()))
{ string stop; }
}
}
}
if (localread == null) return string.Empty;
txt = await FileIO.ReadTextAsync(localread);
return txt;
}
I tried to reverse engineer another answer I found on Stack regarding writing a StorageFile to OneDrive, in that I needed to open the stream of the local file, but I doesn't appear to be working properly.
To get the content of a OneDrive item, we need use following method:
var contentStream = await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Content.Request().GetAsync();
While using
await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Request().GetAsync();
you are getting the OneDrive Item not its content.
So you can change your code like following to write the content of a Onedrive item to a local file:
if (drivemodified > localprops.DateModified)
{
using (var stream = await localread.OpenStreamForWriteAsync())
{
using (var contentStream = await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Content.Request().GetAsync())
{
contentStream.CopyTo(stream);
}
}
}

How Get ALL photos with out filepicker

I m make a simple window phone 8.1 app i want to get aLl photos to display in app and then user select PickMultipleFilesAndContinue ..... but im dont know how to do it . i made this code openfiler picker taking me to phone library ..... Is there any other way to get photos in windows phone 8.1 ?
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.Thumbnail;
openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");
openPicker.PickMultipleFilesAndContinue();
view.Activated += view_Activated;
}
private async void view_Activated(CoreApplicationView sender, Windows.ApplicationModel.Activation.IActivatedEventArgs args1)
{
FileOpenPickerContinuationEventArgs args = args1 as FileOpenPickerContinuationEventArgs;
bitmapImages = new ObservableCollection<BitmapImage>();
IReadOnlyList<StorageFile> files = args.Files;
if (files.Count > 0)
{
StringBuilder output = new StringBuilder("Picked files:\n");
// Application now has read/write access to the picked file(s)
foreach (StorageFile file in files)
{
output.Append(file.Name + "\n");
using (var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
try
{
BitmapImage bitmapImage = new Windows.UI.Xaml.Media.Imaging.BitmapImage();
bitmapImage.DecodePixelHeight = 200;
bitmapImage.SetSource(stream);
bitmapImages.Add(bitmapImage);
}
catch (ArgumentException Ex)
{
Debug.WriteLine("Exception ", Ex.Message);
}
}
}
ImageCollection.ItemsSource = bitmapImages;
OutputTextBlock.Text = output.ToString();
}
else
{
OutputTextBlock.Text = "Operation cancelled.";
}
}
by this im geting only selected photos . i want all to display and then user select from them .....
You can access the photos programmatically and then add them to your ImageCollection. I've resized the photos, because otherwise the App crashes on my phone.
StorageFolder pictureFolder = KnownFolders.PicturesLibrary; //or another folder
IReadOnlyList<IStorageItem> nameList = await pictureFolder.GetItemsAsync();
var bitmapImages = new ObservableCollection<BitmapImage>();
foreach (var item in nameList)
{
if (item is StorageFile)
{
if (item.Name.Substring(item.Name.Length - 4, 3).ToLower() == "jpeg" || item.Name.Substring(item.Name.Length - 3, 3).ToLower() == "jpg" || item.Name.Substring(item.Name.Length - 3, 3).ToLower() == "png")
{
Image image = new Image();
StorageFile file = await pictureFolder.GetFileAsync(item.Name);
IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(fileStream);
if (bitmapImage.DecodePixelHeight >= bitmapImage.DecodePixelWidth)
{
bitmapImage.DecodePixelWidth = bitmapImage.DecodePixelHeight / 100;
bitmapImage.DecodePixelHeight = 100;
}
else
{
bitmapImage.DecodePixelHeight = bitmapImage.DecodePixelWidth / 100;
bitmapImage.DecodePixelWidth = 100;
}
bitmapImages.Add(bitmapImage);
}
}
}

Categories

Resources