Xamarin iOS DocumentPicker: How to import large files? - ios

In the DidPickDocument event of UIDocumentPickerViewController I try to import/write the selected file into the app's local Documents directory.
This works fine with "small" files (e.g < 100MB) using a subclassed UIDocument class with overriden
public override bool LoadFromContents(NSObject contents, string typeName, out NSError outError)
{
outError = null;
if (contents != null)
{
Content = ((NSData)contents).ToArray();
}
...
...and by calling
MySubclassedDoc mySubclassedDoc = new MySubclassedDoc (nsurl);
bool success = await mySubclassedDoc.OpenAsync();
File.WriteAllBytes("targetFile.xyz", mySubclassedDoc.Content);
But if the file is larger (eg. 400MB) the app crashes before LoadFromContents is called because of insufficent memory (RAM).
So there need to be a way to stream the selected file directly to a file.
How can you do this using the given NSUrl ?

Since you have got the url, there's no need to convert it to NSData then store the data to a file. We can just use this url with NSFileManager like:
url.StartAccessingSecurityScopedResource();
string docPath = NSSearchPath.GetDirectories(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User)[0];
string filePath = Path.Combine(docPath, "fileName.type");
NSError error;
NSFileManager fileManager = new NSFileManager();
fileManager.Copy(url.Path, filePath, out error);
url.StopAccessingSecurityScopedResource();
In this way we can store the file to our app's own document directory.

why don’t you just clone the file over to your Documents directory before reading it instead of deserializing the contents and reserializing them?
your code seems really inefficient. If your files can be 400MB you should clearly not load all the contents into memory. I guess you must have very large binary objects in the files if they can be 400MB ; try mmapping the file and storing pointers to the individual objects instead?

Related

Download Zip file in Xamarin iOS Native

I have to download a Zip File in Xamarin iOS.
URL - https://osdn.net/projects/sfnet_fotohound/downloads/sample-pictures/Sample/Sample-Pictures.zip/
As soon as i hit this URL, the download should start and the same should get saved in a particular folder in the documents directory.
How should I implement the same in Xamarin Native iOS.
You can use NSURLSession to download the zip file.
Firstly, you should find the real download link of this download site.
You can refer to this.
In Chrome - run download as normal - then go to Menu - Downloads - and you should see the direct link which was used.
Actually, your file link is https://mirrors.netix.net/sourceforge/f/fo/fotohound/sample-pictures/Sample/Sample-Pictures.zip.
Now start to code. Create download task via NSURLSession:
public void downloadTask()
{
// Your file link.
NSUrl url = NSUrl.FromString("https://mirrors.netix.net/sourceforge/f/fo/fotohound/sample-pictures/Sample/Sample-Pictures.zip");
// Configure your download session.
var config = NSUrlSessionConfiguration.DefaultSessionConfiguration;
NSUrlSession session = NSUrlSession.FromConfiguration(config, new SimpleSessionDelegate(), new NSOperationQueue());
var downloadTask = session.CreateDownloadTask(NSUrlRequest.FromUrl(url));
// Start the session.
downloadTask.Resume();
Console.WriteLine("Start DownloadTask!!!");
}
Configure the callback DidFinishDownloading:
class SimpleSessionDelegate : NSUrlSessionDownloadDelegate
{
public override void DidFinishDownloading(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
{
// Configure your destination path. Here's saved to /Documents/ folder.
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var destinationPath = Path.Combine(documents, "Sample.zip");
if (File.Exists(location.Path))
{
NSFileManager fileManager = NSFileManager.DefaultManager;
NSError error;
// Remove the same name file in destination path.
fileManager.Remove(destinationPath, out error);
// Copy the file from the tmp directory to your destination path. The tmp file will be removed when this delegate finishes.
bool success = fileManager.Copy(location.Path, destinationPath, out error);
if (!success)
{
Console.WriteLine("Error during the copy: {0}", error.LocalizedDescription);
}
}
}
}
Now the file has been saved at the documents directory and named Sample.zip.

How to save PDF files from Firebase Storage into App Documents for future use?

I have connected my App with the Firebase Storage where my 19ea PDF files exists.
I would like to download those files and save them locally for future use.
Those PDF files will be used inside UIWebviews but they may need to be updated in time. Therefore, I have configured version control system with Firebase Database, so I will be able to push the newer versions when I update the files in the storage.
So, how I can save those files locally? (to a folder like: user/myapp/Documents/PDF etc?)
Also, how I can check if that folder contains any documents and how to delete them before downloading new files?
Here is what I have got so far.
I appreciate all the help.
// Firebase Storage Connection
static var refStorage:FIRStorageReference?
static var dataPDF = [NSData]()
func newDataDownload(){
// Compare Current Data Version with Online Data Version
if myFirebaseData.localDataVersion < myFirebaseData.onlineDataVersion {
// Set Firebase Storage Reference
myFirebaseData.refStorage = FIRStorage.storage().reference()
for i in 1...myFirebaseData.onlineTotalPDFCount {
// Create a reference to the file you want to download
let pulledPDF = FIRStorage.storage().reference().child("/PDF/\(i).pdf")
// Create local filesystem URL
let localURL = URL(string: "myApp/Documents/PDF/\(i)")!
pulledPDF.data(withMaxSize: myFirebaseData.maxPDFdownloadSize, completion: { (downPDF, err) in
if err == nil {
// Accessed the data
myFirebaseData.dataPDF.append(downPDF! as NSData)
print(myFirebaseData.dataPDF)
} else {
// If there is an error print it
print(err.debugDescription)
}
})
}
}
// If Data is successfully downloaded update Local Data Version
myFirebaseData.localDataVersion = myFirebaseData.onlineDataVersion
Use storageRef.write(toFile: completion:) (docs), like:
// Create a reference to the file you want to download
let pdfRef = storageRef.child("files/file.pdf")
// Create local filesystem URL
let localURL = URL(string: "path/to/local/file.pdf")!
// Download to the local filesystem
let downloadTask = pdfRef.write(toFile: localURL) { url, error in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Local file URL for "path/to/local/file.pdf" is returned
}
}
Note that you can only write to /tmp and /Documents due to app sandboxing requirements (see Downloading Firebase Storage Files Device Issue for an example of how this fails otherwise).

Can't get data from file stored in asset catalog

I am storing several .svg files in an asset catalog.
To retrieve an asset I use the following code:
NSDataAsset *asset = [[NSDataAsset alloc] initWithName:#"p"];
When running this code I get the following log message:
CoreUI: attempting to lookup a named data 'p' with a type that is not a data type in the AssertCatalog
The name of the file is 'p.svg' which is stored in a folder with 'dataset' as extension. I tried using other extensions for the file but nothing works. I always get the same error and the asset is nil.
This may be related to a problem I'm having, in that the UTI I'm identifying my assets as being could run up the UTI dependancies and find public.image, thus making CoreUI think the asset is an image type and not a data type.
SVGs inherit from public.image, so you will need to set the File Type to something that doesn't inherit from public.image. Having it be just public.data will suffice.
Just reference the svg or whatever file in your project like .h/.m:
NSString *path = [[NSBundle mainBundle] pathForResource:#"p" ofType:#"svg"];
[NSData dataWithContentsOfFile:path];
If path is nil, try:
In the Xcode target "Build Phases" add the file under "Copy Bundle Resources"".
I also have this issue; I wanted to provide images etc from local assets store. Dragging the files into the catalog I would fetch them like so - here a String:
extension NSString {
class func string(fromAsset: String) -> String {
guard let asset = NSDataAsset.init(name: fromAsset) else {
return String(format: "Unable to locate asset:\n%#", fromAsset)
}
let data = NSData.init(data: (asset.data))
let text = String.init(data: data as Data, encoding: String.Encoding.utf8)
if fromAsset.hasSuffix(".md"), let html = try? Down(markdownString: text!).toHTML()
{
let htmlDoc = String(format: "<html><body>%#</body></html>", html)
let data = Data(htmlDoc.utf8)
if let attrs = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
return attrs.string
}
}
return text!
}
}
the premise is they have to exist. I think a per type / class extension would make retrieval simple. Eventually a generic object extension could dispatch to the proper extension, as here, you'd have to know what it was, so the decision which to use is currently a manual one.

Swift iOS filemanager copy changes sqlite file

I have a routine that when my dataModelObject is initiate, its to copy myDataBase.sqlite file from the App Bundle folder, to the app document directory so that the app can use it. It only copies it if its not there, meaning in live environment it will not replace users existing db. The operation executes with no problem, however the database at the destination location is completely different than the database i designed. Has anyone else run into this, and might know what is causing this?
2 tables in said database one record only in one table with a weird number and a blob.
Yes, i have confirmed that the file in the bundle points to the correct .sqlite file in the project folder.
Here is my routine that does the copy, does something stand out as bad? Should i use move instead?
class func copyFile(fileName: NSString) {
let dbPath: String = getPath(fileName)
println("copyFile fileName=\(fileName) to path=\(dbPath)")
var fileManager = NSFileManager.defaultManager()
var fromPath: String? = NSBundle.mainBundle().resourcePath?.stringByAppendingPathComponent(fileName)
if !fileManager.fileExistsAtPath(dbPath) {
println("dB not found in document directory filemanager will copy this file from this path=\(fromPath) :::TO::: path=\(dbPath)")
fileManager.copyItemAtPath(fromPath!, toPath: dbPath, error: nil)
} else {
println("DID-NOT copy dB file, file allready exists at path:\(dbPath)")
}
}
class func getPath(fileName: String) -> String {
return NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent(fileName)
}

Getting a file without an extensions mime-type

I'm downloading a jpg into the applicationDataDirectory with the Titanium HTTP Client.
Im saving the filename as a md5 hash of the URL to be able to do simple caching via the URL.
However if I save the file without an extension eg. just as sf98isdi8j3k34k3kj34k no .jpg when I run
var file = Ti.Filesystem.getFile( filePath );
var mimeType = file.blob.getMimeType();
it returns "application/octet-stream"
This is because Mimetypes.m contains the following code
+ (NSString *)mimeTypeForExtension:(NSString *)ext
{
[Mimetypes initialize];
NSString *result=[mimeTypeFromExtensionDict objectForKey:[[ext pathExtension] lowercaseString]];
if (result == nil){
result = #"application/octet-stream";
}
return result;
}
It soley relies on the extension, so you could change the file extension and cause issues.
How would I go about checking the mime-type based off the raw data of the file, rather than file extensions in titanium- specifically iOS
Thanks
Ti.Filesystem.File has no MimeType property, maybe you are thinking of a Ti.Blob?
I have used this code with success for fetching the mimeType of some data, similar to what you already have!
var file = Ti.Filesystem.getFile( filePath );
var blob = file.read();
var mimeType = blob.getMimeType();
Looking at TiBlob.m and Mimetypes.m this may not solve the problem, but I seem to recall having success with this.

Resources