I need Xamarin to call a GET API that downloads a photo from a url and save it in the phone.
When xamarin calls the API, it will download a image from a url and then return it as response to xamarin, after this I will save the photo in the phone.
The API is a C# 4.0 MVC. I created a Get API that downloads the image successfully and returns it as FileStreamResult.
The API downloads the image from a url and return it
public FileStreamResult DownloadFoto()
{
Stream rtn = null;
string aURL = "https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg";
HttpWebRequest aRequest = (HttpWebRequest)WebRequest.Create(aURL);
HttpWebResponse aResponse = (HttpWebResponse)aRequest.GetResponse();
rtn = aResponse.GetResponseStream();
return File(rtn, "image/jpeg", foto);
}
Xamarin Forms
HttpConnectionis an instance of HttpClient.
var Response = await Application.HttpConnection.GetAsync("DownloadFoto");
DependencyService.Get<IFileService>().SavePicture("ImageName.jpg", Response.Content.ReadAsStreamAsync().Result, "imagesFolder");
Xamarin Android
[assembly: Dependency(typeof(FileService))]
namespace Diario.Droid
{
public class FileService : IFileService
{
public void SavePicture(string name, Stream data, string location = "temp")
{
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
string filePath = Path.Combine(documentsPath, name);
byte[] bArray = new byte[data.Length];
using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate))
{
using (data)
{
data.Read(bArray, 0, (int)data.Length);
}
int length = bArray.Length;
fs.Write(bArray, 0, length);
}
}
}
}
I don't get any error, it looks to be saving the image, but when I look for it in File Management I don't find the image there.
Yeah the directory path your are getting with:
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
It is pointing at somewhere in /data/user/ which will be private to the App. If you want to make it browse-able using a File Explorer App, you will probably need to save it somewhere in External Storage (public storage).
You can get a path for that with:
var externalDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments);
If you are targeting Android API 18 and lower, remember to add permissions for that in your AndroidManifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
Since some devices on Android can have their External Storage on microSD cards, you will need to check whether you can write to it first:
var stateOk = Android.OS.Environment.ExternalStorageState == Android.OS.Environment.MediaMounted;
if (stateOk)
{
// I can write!
}
From Android API 23 and up, you will need to request permission before being able to read and write to external storage. You could use this plugin to easily ask for permissions: https://github.com/jamesmontemagno/PermissionsPlugin
Another couple of things to notice. You are using .Result on your ReadAsStreamAsync() call. Just keep in mind that you will be blocking the thread you are on. I highly suggest you design your methods to be async all the way.
Related
I am integrating my Xamarin.Android app with Google Drive API. My main problem is that even though all API calls ends up with success, some of my binary files are not persisted in Google Drive.
I try to send 5-10 of audio files (wav, mp3, ogg, etc) sized 10-80 MB each.
Here's my approach of sending binary files:
string mimeType = GetMimeType(localFilePath);
string fullSourceFilePath = Path.Combine(documentsPath, localFilePath);
byte[] buffer = new byte[BinaryStreamsBufferLength];
using (var contentResults = await DriveClass.DriveApi.NewDriveContentsAsync(_googleApiClient).ConfigureAwait(false))
{
if (contentResults.Status.IsSuccess) //always returns true
{
using (var readStream = System.IO.File.OpenRead(fullSourceFilePath))
{
using (var fileChangeSet = new MetadataChangeSet.Builder()
.SetTitle(path[1])
.SetMimeType(mimeType)
.Build())
{
using (var writeStream = new BinaryWriter(contentResults.DriveContents.OutputStream))
{
int bytesRead = readStream.Read(buffer, 0, BinaryStreamsBufferLength);
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, BinaryStreamsBufferLength);
}
writeStream.Close();
}
readStream.Close();
var singleFileCopyResult = await targetCloudFolder.CreateFileAsync(_googleApiClient, fileChangeSet, contentResults.DriveContents,
new ExecutionOptions.Builder().SetConflictStrategy(ExecutionOptions.ConflictStrategyOverwriteRemote).Build()).ConfigureAwait(false);
return singleFileCopyResult.Status.IsSuccess; //always returns true
}
}
}
}
Above method is called in foreach loop with list of locally stored files that needs to be sent.
Also, when connecting to API for the first time, I requestSync:
if (_isSyncRequired)
syncResult = await DriveClass.DriveApi.RequestSyncAsync(_googleApiClient).ConfigureAwait(false);
I am already after few days of trying various solutions including:
- changing stream types
- changing order of API method calls
- using non-Async API methods (which in fact are also async)
- experimented with different overloads and parameters when possible
- tried to refactor to non-obsolete API calls (impossible to do as it requires ultra-heavy GoogleSignIn NuGet with tons of dependencies and my app is heavy enough)
None of above worked and my files are later successfully downloaded in like 80% of all files being sent (all upload statuses are success).
Does anyone of you experienced similar issues so far? Do you have any tips what can be done in order to make file downloadable if all upload methods claims everything succeeds?
Many thanks in advance for any clues :)
I'm trying to stream a Video, that is saved as an attachment in a Ravendb-Database, through an ASP.NET MVC 5 Action to a WebBrowser. It is working with the following Code, but the Video gets fully downloaded before the Video starts. I don't get what I'm doing wrong.
I found some ways to do Streaming in MVC, but they seem to expect a Seekable Stream - but the stream I receive from Ravendb is not seekable; it even does not provide a length. So the only way of doing it would be to copy the ravendb-stream to a memorystream and provide a PartialContent or similar from there.
Does anybody have a better solution? I cannot be the only one that wants to stream a Video from a database without loading the full Video into Memory before sending it.
I'm fetching the attachment from ravendb like this:
public async Task<System.IO.Stream> GetAttachmentAsync(IAttachmentPossible attachedObject, string key)
{
using (var ds = InitDatabase())
{
using (var session = ds.OpenAsyncSession())
{
try
{
var result = await session.Advanced.Attachments.GetAsync(attachedObject.Id, key);
return result.Stream;
}
catch (Exception ex)
{
return null;
}
}
}
}
After that I send the stream to the browser like this:
var file = await _database.GetAttachmentAsync(entry, attachmentId);
HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.OK);
msg.Content = new StreamContent(file);
msg.Content.Headers.ContentType = new MediaTypeHeaderValue("video/mp4");
return msg;
Any ideas? Thank you very much!
I think that it is correct to copy the stream into a memory stream. With the answer you linked (https://stackoverflow.com/a/39247028/10291808) you can do streaming.
Maybe could be an idea to think about the multiple calls that will done to retrieve the file from the db (maybe you could cache the file for a limited time to improve performance).
I have tried to download video file from video URL. Video file created but downloading does not happen here. just 57KB file generate. My code is here.
public async void DownloadTrack(Uri SongUri, string fileName)
{
using (HttpClient httpClient = new HttpClient())
{
var data = await httpClient.GetByteArrayAsync(SongUri);
StorageFolder storageFolder = KnownFolders.VideosLibrary;
var file = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);
///////////////////
using (var targetStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await targetStream.AsStreamForWrite().WriteAsync(data, 0, data.Length);
await targetStream.FlushAsync();
}
}
}
Here video file creates but does not file download. I want to download file. I searched much about this. here is accepted Stackoverflow answer link but this also does the same. Example
can you please provide any better solution.
I am using the below code to downlaod the picture form a remote url and save to Local storage folder
try
{
var rootFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync( "MyAppName\\CoverPics", CreationCollisionOption.OpenIfExists);
var coverpic_file = await rootFolder.CreateFileAsync(filename, CreationCollisionOption.FailIfExists);
try
{
var httpWebRequest = HttpWebRequest.CreateHttp(coverUrl);
HttpWebResponse response = (HttpWebResponse)await httpWebRequest.GetResponseAsync();
Stream resStream = response.GetResponseStream();
using (var stream = await coverpic_file.OpenAsync(FileAccessMode.ReadWrite))
{
await resStream.CopyToAsync(stream.AsStreamForWrite());
}
response.Dispose();
}
catch //any exceptions happend while saving the picture
{
saved = false;
}
}
catch
{
//https://msdn.microsoft.com/en-us/library/windows/apps/br227250.aspx
//Raise an exception if file already present
saved = true;
}
This code is working for me in most of the cases , but i noticed that for few pictures the image is not downloading completely.
I am callling this function in an async block for more tahn 100 images in a single go inside the foreach loop and in the end few of them are failed downloads
[ Either i can see some invalid file is getting created
or part of image only in downloading and rest of the area i can see a black colour block [ looks like image is corrupted].
Size of all images is less than 1 MB only
Can some one help me to optimize this code or point out the mistake in code so i can able to download all the images completely
I am not seeing any error in my code. But after trying some different ways of downloading and saving a file my code looks like this and
try
{
HttpClient client = new HttpClient(); // Create HttpClient
byte[] buffer = await client.GetByteArrayAsync(coverUrl); // Download file
using (Stream stream = await coverpic_file.OpenStreamForWriteAsync())
stream.Write(buffer, 0, buffer.Length); // Save
}
catch
{
saved = false;
}
And this code is working fine without causing any issues All images are downloading completely and no more issues of black block on images.
If any one can points out the difference with my first code will be really helpful to understood the reason for error
Have you tried using new Windows.Web.Http.HttpClient instead of HttpWebRequest?
Also take a look this SO question :
How do I use the new HttpClient from Windows.Web.Http to download an image?
If you not familiar with HttpClient, I sugest to watch CH9 presentation :
https://channel9.msdn.com/Events/Build/2013/4-092
I tried your download and experienced the same issues.
var myFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("MyFolderPath", CreationCollisionOption.OpenIfExists);
var myFile = await myFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
BackgroundDownloader downloader = new BackgroundDownloader();
DownloadOperation download = downloader.CreateDownload(new Uri(URL), myFile);
await download.StartAsync();
I am trying to use SharpZip.unzipper for my windows phone 8.1. However it is not reading some of the code. Since i am fairly new to windows phone development please let me know the alternatives of following code for WP8.1
using System.Windows.Resources;
public Stream GetFileStream(string filename)
{
if (fileEntries == null)
fileEntries = ParseCentralDirectory(); //We need to do this in case the zip is in a format Silverligth doesn't like
long position = this.stream.Position;
this.stream.Seek(0, SeekOrigin.Begin);
Uri fileUri = new Uri(filename, UriKind.Relative);
StreamResourceInfo info = new StreamResourceInfo(this.stream, null);
StreamResourceInfo stream = System.Windows.Application.GetResourceStream(info, fileUri);
this.stream.Position = position;
if (stream != null)
return stream.Stream;
return null;
}
Windows.Resources seems missing
I can't call StreamResourceInfo, System.Windows.Application
I have tried using App. but there is no function for GetResourceSteam
I am not sure what to do here
There are quite a few differences between WP8.1 runtime and WP8.1 Silverlight.
The code sample that you have will run on WP8.0 SL and WP8.1 SL.
It looks like you created a Windows Phone 8.1 runtime project. There is no System.Windows.Application.GetResourceStream()
Either convert it to a compatible 8.1 runtime stream or recreate your project to target Silverlight instead.
// sample
private async void ZipLibraryTest()
{
// create the full path
string zip = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "filename.zip");
// create the storage file with the file name
StorageFile sf = await StorageFile.GetFileFromPathAsync(zip);
// create the IO Stream
using (System.IO.Stream output = await sf.OpenStreamForWriteAsync())
{
// open the zip file for writing, use the stream from the file
ZipFile zf = ZipFile.Create(output);
// rest of your code
// ...
// ...
// close the zip file
zf.Close();
}
}