From 14b67698fe8488cb95351a933590dc934f6cf4e3 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 10 Jun 2016 14:48:58 +0200 Subject: [PATCH] Open up for saving to temporary location (you can by that skip requesting the permission `WRITE_EXTERNAL_STORAGE`) --- .../DownloadFileImplementation.cs | 4 ++- .../DownloadManagerImplementation.cs | 7 ++++- .../UrlSessionDownloadDelegate.cs | 29 ++++++++++++------ README.md | 30 +++++++++++-------- Sample/Droid/MainActivity.cs | 13 +++++--- Sample/Droid/Properties/AndroidManifest.xml | 5 +++- .../iOS/ExtendedUrlSessionDownloadDelegate.cs | 18 ++++++++++- Sample/iOS/ViewController.cs | 9 +++--- 8 files changed, 82 insertions(+), 33 deletions(-) diff --git a/DownloadManager/Plugin.DownloadManager.Android/DownloadFileImplementation.cs b/DownloadManager/Plugin.DownloadManager.Android/DownloadFileImplementation.cs index 19b19a6..99f6728 100644 --- a/DownloadManager/Plugin.DownloadManager.Android/DownloadFileImplementation.cs +++ b/DownloadManager/Plugin.DownloadManager.Android/DownloadFileImplementation.cs @@ -101,7 +101,9 @@ public void StartDownload (Android.App.DownloadManager downloadManager, string d request.AddRequestHeader (header.Key, header.Value); } - request.SetDestinationUri (Uri.FromFile (new Java.IO.File (destinationPathName))); + if (destinationPathName != null) { + request.SetDestinationUri (Uri.FromFile (new Java.IO.File (destinationPathName))); + } Id = downloadManager.Enqueue (request); diff --git a/DownloadManager/Plugin.DownloadManager.Android/DownloadManagerImplementation.cs b/DownloadManager/Plugin.DownloadManager.Android/DownloadManagerImplementation.cs index 5277682..0479599 100644 --- a/DownloadManager/Plugin.DownloadManager.Android/DownloadManagerImplementation.cs +++ b/DownloadManager/Plugin.DownloadManager.Android/DownloadManagerImplementation.cs @@ -50,7 +50,12 @@ public void Start (IDownloadFile i) { var file = (DownloadFileImplementation)i; - file.StartDownload (_downloadManager, PathNameForDownloadedFile (file)); + string destinationPathName = null; + if (PathNameForDownloadedFile != null) { + destinationPathName = PathNameForDownloadedFile (file); + } + + file.StartDownload (_downloadManager, destinationPathName); Queue.Add (file); } diff --git a/DownloadManager/Plugin.DownloadManager.iOS/UrlSessionDownloadDelegate.cs b/DownloadManager/Plugin.DownloadManager.iOS/UrlSessionDownloadDelegate.cs index 199a35a..ff5b3f2 100644 --- a/DownloadManager/Plugin.DownloadManager.iOS/UrlSessionDownloadDelegate.cs +++ b/DownloadManager/Plugin.DownloadManager.iOS/UrlSessionDownloadDelegate.cs @@ -69,31 +69,42 @@ public override void DidFinishDownloading (NSUrlSession session, NSUrlSessionDow return; } - MoveDownloadedFile (file, location); + var success = true; + if (Controller.PathNameForDownloadedFile != null) { + var destinationPathName = Controller.PathNameForDownloadedFile (file); + if (destinationPathName != null) { + success = MoveDownloadedFile (file, location, destinationPathName); + } + } + + // If the file destination is unknown or was moved successfully ... + if (success) { + file.Status = DownloadFileStatus.COMPLETED; + } + + Controller.Queue.Remove (file); } /** - * Move the downloaded file to it's destination and remove it from the download-queue. + * Move the downloaded file to it's destination */ - public void MoveDownloadedFile (DownloadFileImplementation file, NSUrl location) + public bool MoveDownloadedFile (DownloadFileImplementation file, NSUrl location, string destinationPathName) { NSFileManager fileManager = NSFileManager.DefaultManager; - var destinationURL = new NSUrl (Controller.PathNameForDownloadedFile (file), false); + var destinationURL = new NSUrl (destinationPathName, false); NSError removeCopy; NSError errorCopy; fileManager.Remove (destinationURL, out removeCopy); - bool success = fileManager.Copy (location, destinationURL, out errorCopy); + var success = fileManager.Copy (location, destinationURL, out errorCopy); - if (success) { - file.Status = DownloadFileStatus.COMPLETED; - } else { + if (!success) { file.StatusDetails = errorCopy.LocalizedDescription; file.Status = DownloadFileStatus.FAILED; } - Controller.Queue.Remove (file); + return success; } public override void DidFinishEventsForBackgroundSession(NSUrlSession session) diff --git a/README.md b/README.md index c6c1dae..bff4fce 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Add the nuget package to your cross-platform project and to every platform speci _AppDelegate.cs_ ``` /** - * Save the completion-handler we get when the app starts back from the background. + * Save the completion-handler we get when the app opens from the background. * This method informs iOS that the app has finished all internal processing and can sleep again. */ public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler) @@ -27,13 +27,8 @@ _AppDelegate.cs_ } ``` -As of iOS 9, your URL MUST be secured or you have to add the domain to the list of exceptions. See [https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14](https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14) -### Android - -Depending on the location you want the file to be saved, you may have to ask for the permission `WRITE_EXTERNAL_STORAGE`: -``` - -``` +As of iOS 9, your URL must be secured or you have to add the domain to the list of exceptions. See [https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14](https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14) +#### Android _Activity.cs_ ``` @@ -53,13 +48,19 @@ You can now start a download by adding the following code: downloadManager.Start(file); ``` -This will add the file to a native library, which starts the download of that file. You can watch the properties of the `IDownloadFile` instance and execute some code if e.g. the status changes to `COMPLETED`, but you can also watch the `IDownloadManager.Queue` and execute some code if the list of files, that will be downloaded or are currently downloading changes. +This will add the file to a native library, which starts the download of that file. You can watch the properties of the `IDownloadFile` instance and execute some code if e.g. the status changes to `COMPLETED`, you can also watch the `IDownloadManager.Queue` and execute some code if the list of files, that will be downloaded or are currently downloading changes. -After a download has completed, it is removed from `IDownloadManager.Queue`. +After a download has been completed, the instance of `IDownloadFile` is then removed from `IDownloadManager.Queue`. ### Where are the files stored? -Usually, you would expect to set the path to the `IDownloadFile` instance, you get when calling `downloadManager.CreateDownloadFile(url)`. But, as this background-downloader even continues when the app crashed, you have to be able to reconstruct the path in every stage of the app. The correct way is to register a method as early as possible, that, in every circumstance, can reconstruct the path the file should be saved. This code line could look like following: +#### Default Option - Temperory Location + +When you choose not to provide your own path before starting the download, the downloaded files are stored at a temporary directory and may be removed by the OS e.g. when the system runs out of space. You can move this file to a decided destination by listening on whether the status of the files changes to `DownloadFileStatus.COMPLETED`. + +#### Recommended Option - Custom Location + +Usually, you would expect to set the path to the `IDownloadFile` instance, you get when calling `downloadManager.CreateDownloadFile(url)`. But, as this download manager even continues downloading when the app crashed, you have to be able to reconstruct the path in every stage of the app. The correct way is to register a method as early as possible, that, in every circumstance, can reconstruct the path that the file should be saved. This method could look like following: ``` CrossDownloadManager.Current.PathNameForDownloadedFile = new System.Func (file => { #if __IOS__ @@ -74,8 +75,13 @@ Usually, you would expect to set the path to the `IDownloadFile` instance, you g #endif }); ``` +##### Additional for Andriod + +On Android, the destination location must be a located outside of your Apps internal directory (see [#10](https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/issues/10) for details). To allow your app to write to that location, you will need the the permission `WRITE_EXTERNAL_STORAGE` (See [Android API for DownloadManager.Request.setDestinationUri()](https://developer.android.com/reference/android/app/DownloadManager.Request.html#setDestinationUri%28android.net.Uri%29): +``` + +``` -Please be aware of that the destination on Android MUST be on an external storage. See [#10](https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/issues/10) ### I want to use $FAVORITE_IOC_LIBRARY diff --git a/Sample/Droid/MainActivity.cs b/Sample/Droid/MainActivity.cs index a2cfac1..2c1140c 100644 --- a/Sample/Droid/MainActivity.cs +++ b/Sample/Droid/MainActivity.cs @@ -19,10 +19,11 @@ void InitDownloadManager () CrossDownloadManager.Init (this); // Define where the files should be stored. MUST be an external storage. (see https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/issues/10) - CrossDownloadManager.Current.PathNameForDownloadedFile = new Func (file => { - string fileName = Android.Net.Uri.Parse (file.Url).Path.Split ('/').Last (); - return Path.Combine (ApplicationContext.GetExternalFilesDir (Android.OS.Environment.DirectoryDownloads).AbsolutePath, fileName); - }); + // If you skip this, you neither need the permission `WRITE_EXTERNAL_STORAGE`. + //CrossDownloadManager.Current.PathNameForDownloadedFile = new Func (file => { + // string fileName = Android.Net.Uri.Parse (file.Url).Path.Split ('/').Last (); + // return Path.Combine (ApplicationContext.GetExternalFilesDir (Android.OS.Environment.DirectoryDownloads).AbsolutePath, fileName); + //}); } NotificationClickedBroadcastReceiver _receiverNotificationClicked; @@ -80,6 +81,10 @@ protected override void OnCreate (Bundle savedInstanceState) case DownloadFileStatus.FAILED: case DownloadFileStatus.CANCELED: button.Text = "Downloading finished."; + + // Get the path this file was saved to. When you didn't set a custom path, this will be some temporary directory. + var nativeDownloadManager = (DownloadManager)ApplicationContext.GetSystemService (DownloadService); + System.Diagnostics.Debug.WriteLine (nativeDownloadManager.GetUriForDownloadedFile (((DownloadFileImplementation)sender).Id)); break; } } diff --git a/Sample/Droid/Properties/AndroidManifest.xml b/Sample/Droid/Properties/AndroidManifest.xml index ebc5d1b..b75d01d 100644 --- a/Sample/Droid/Properties/AndroidManifest.xml +++ b/Sample/Droid/Properties/AndroidManifest.xml @@ -1,7 +1,10 @@  - + diff --git a/Sample/iOS/ExtendedUrlSessionDownloadDelegate.cs b/Sample/iOS/ExtendedUrlSessionDownloadDelegate.cs index d40fd74..a7eb010 100644 --- a/Sample/iOS/ExtendedUrlSessionDownloadDelegate.cs +++ b/Sample/iOS/ExtendedUrlSessionDownloadDelegate.cs @@ -1,4 +1,5 @@ -using Plugin.DownloadManager; +using Foundation; +using Plugin.DownloadManager; namespace DownloadExample.iOS { @@ -17,6 +18,21 @@ public override void DidFinishEventsForBackgroundSession (Foundation.NSUrlSessio base.DidFinishEventsForBackgroundSession (session); } + + public override void DidFinishDownloading (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location) + { + // In case you need to access the IDownloadFile implementation, you have to load it before calling the base-method. + var file = getDownloadFileByTask (downloadTask); + if (file == null) { + return; + } + + // This base-method sets the state to "COMPLETED" and moves the file if `PathNameForDownloadedFile` is set. + base.DidFinishDownloading (session, downloadTask, location); + + // If you don't set `PathNameForDownloadedFile`, you can do what you want with the file now. + System.Diagnostics.Debug.WriteLine (location.AbsoluteString); + } } } diff --git a/Sample/iOS/ViewController.cs b/Sample/iOS/ViewController.cs index 9feca03..fb0a54b 100644 --- a/Sample/iOS/ViewController.cs +++ b/Sample/iOS/ViewController.cs @@ -20,10 +20,11 @@ public override void ViewDidLoad () { base.ViewDidLoad (); - CrossDownloadManager.Current.PathNameForDownloadedFile = new Func (file => { - string fileName = (new NSUrl (file.Url, false)).LastPathComponent; - return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments), fileName); - }); + // If you want to take full control of the saved file, see `ExtendedUrlSessionDownloadDelegate` + //CrossDownloadManager.Current.PathNameForDownloadedFile = new Func (file => { + // string fileName = (new NSUrl (file.Url, false)).LastPathComponent; + // return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments), fileName); + //}); var foo = new Downloader ();