Bitwarden-app-android-iphon.../src/iOS.Core/Services/FileService.cs

214 lines
8.6 KiB
C#

using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.iOS.Core.Utilities;
using CoreGraphics;
using Foundation;
using MobileCoreServices;
using Photos;
using UIKit;
namespace Bit.iOS.Core.Services
{
public class FileService : IFileService
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
public FileService(IStateService stateService, IMessagingService messagingService)
{
_stateService = stateService;
_messagingService = messagingService;
}
public bool OpenFile(byte[] fileData, string id, string fileName)
{
var filePath = Path.Combine(GetTempPath(), fileName);
File.WriteAllBytes(filePath, fileData);
var url = NSUrl.FromFilename(filePath);
var viewer = UIDocumentInteractionController.FromUrl(url);
var controller = UIViewControllerExtensions.GetVisibleViewController();
var rect = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad ?
new CGRect(100, 5, 320, 320) : controller.View.Frame;
return viewer.PresentOpenInMenu(rect, controller.View, true);
}
public bool CanOpenFile(string fileName)
{
// Not sure of a way to check this ahead of time on iOS
return true;
}
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
{
// OpenFile behavior is appropriate here as iOS prompts to save file
return OpenFile(fileData, id, fileName);
}
public async Task ClearCacheAsync()
{
var url = new NSUrl(GetTempPath());
var tmpFiles = NSFileManager.DefaultManager.GetDirectoryContent(url, null,
NSDirectoryEnumerationOptions.SkipsHiddenFiles, out NSError error);
if (error == null && tmpFiles.Length > 0)
{
foreach (var item in tmpFiles)
{
NSFileManager.DefaultManager.Remove(item, out NSError itemError);
}
}
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
}
public Task SelectFileAsync()
{
var controller = UIViewControllerExtensions.GetVisibleViewController();
var picker = new UIDocumentMenuViewController(new string[] { UTType.Data }, UIDocumentPickerMode.Import);
picker.AddOption(AppResources.Camera, UIImage.FromBundle("camera"), UIDocumentMenuOrder.First, () =>
{
var imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.Camera
};
imagePicker.FinishedPickingMedia += ImagePicker_FinishedPickingMedia;
imagePicker.Canceled += ImagePicker_Canceled;
controller.PresentModalViewController(imagePicker, true);
});
picker.AddOption(AppResources.Photos, UIImage.FromBundle("photo"), UIDocumentMenuOrder.First, () =>
{
var imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary
};
imagePicker.FinishedPickingMedia += ImagePicker_FinishedPickingMedia;
imagePicker.Canceled += ImagePicker_Canceled;
controller.PresentModalViewController(imagePicker, true);
});
picker.DidPickDocumentPicker += (sender, e) =>
{
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
e.DocumentPicker.Delegate = new PickerDelegate(this);
}
else
{
e.DocumentPicker.DidPickDocument += DocumentPicker_DidPickDocument;
}
controller.PresentViewController(e.DocumentPicker, true, null);
};
var root = UIApplication.SharedApplication.KeyWindow.RootViewController;
if (picker.PopoverPresentationController != null && root != null)
{
picker.PopoverPresentationController.SourceView = root.View;
picker.PopoverPresentationController.SourceRect = root.View.Bounds;
}
controller.PresentViewController(picker, true, null);
return Task.CompletedTask;
}
// ref: //https://developer.xamarin.com/guides/ios/application_fundamentals/working_with_the_file_system/
public string GetTempPath()
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
return Path.Combine(documents, "..", "tmp");
}
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
if (sender is UIImagePickerController picker)
{
string fileName = null;
if (e.Info.TryGetValue(UIImagePickerController.ReferenceUrl, out NSObject urlObj))
{
var result = PHAsset.FetchAssets(new NSUrl[] { (urlObj as NSUrl) }, null);
fileName = result?.firstObject?.ValueForKey(new NSString("filename"))?.ToString();
}
fileName = fileName ?? $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
var lowerFilename = fileName?.ToLowerInvariant();
byte[] data;
if (lowerFilename != null && (lowerFilename.EndsWith(".jpg") || lowerFilename.EndsWith(".jpeg")))
{
using (var imageData = e.OriginalImage.AsJPEG())
{
data = new byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, data, 0,
Convert.ToInt32(imageData.Length));
}
}
else
{
using (var imageData = e.OriginalImage.AsPNG())
{
data = new byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, data, 0,
Convert.ToInt32(imageData.Length));
}
}
SelectFileResult(data, fileName);
picker.DismissViewController(true, null);
}
}
private void ImagePicker_Canceled(object sender, EventArgs e)
{
if (sender is UIImagePickerController picker)
{
picker.DismissViewController(true, null);
}
}
private void DocumentPicker_DidPickDocument(object sender, UIDocumentPickedEventArgs e)
{
PickedDocument(e.Url);
}
public void PickedDocument(NSUrl url)
{
url.StartAccessingSecurityScopedResource();
var doc = new UIDocument(url);
var fileName = doc.LocalizedName;
if (string.IsNullOrWhiteSpace(fileName))
{
var path = doc.FileUrl?.ToString();
if (path != null)
{
path = WebUtility.UrlDecode(path);
var split = path.LastIndexOf('/');
fileName = path.Substring(split + 1);
}
}
var fileCoordinator = new NSFileCoordinator();
fileCoordinator.CoordinateRead(url, NSFileCoordinatorReadingOptions.WithoutChanges,
out NSError error, (u) =>
{
var data = NSData.FromUrl(u).ToArray();
SelectFileResult(data, fileName ?? "unknown_file_name");
});
url.StopAccessingSecurityScopedResource();
}
private void SelectFileResult(byte[] data, string fileName)
{
_messagingService.Send("selectFileResult", new Tuple<byte[], string>(data, fileName));
}
public class PickerDelegate : UIDocumentPickerDelegate
{
private readonly FileService _fileService;
public PickerDelegate(FileService fileService)
{
_fileService = fileService;
}
public override void DidPickDocument(UIDocumentPickerViewController controller, NSUrl url)
{
_fileService.PickedDocument(url);
}
}
}
}