Image loaded using send_data are half broken when loading multiple images in iOS - ios

For reasons I don't want to get into here I need to resize images that are coming from users cell phones (and are stupid large) from blobs on the fly when loading them using this tag
image_tag thumbnail_document_path(document.id), alt: "#{document.document_title}", class: "document-thumbnail"
this calls the thumbnail controller action
def thumbnail
document = Document.find params[:id]
image_meta = document.image_details.first
image = Document.get_image_for_display image_meta
send_data image, type: "image/jpg", disposition: "inline"
end
Document.get_image_for_display pulls the blob from a database, checks the metadata we save separately to see if it's a PDF that needs to be converted to an image or a huge image that needs resizing, and then does all that and asynchronously sends the blob. This works fine on everything except for iOS. on iOS safari and Chrome if you're loading multiple images this way at the same time this happens. If anyone has an explanation or solution for this I'd love to hear it!
Edit: This app shares a server and database with a C++ desktop app and does not(and can not) have access to the locations on the server where these files are stored and there are hundreds of thousands of files across hundreds of client servers so storing a resized version next to the full size one is off the table due to physical storage limitations. The issue only appears to happen when trying to convert and load more than 3 large images and then only the first image stays broken, the others will fully load, although it takes a while for them to do it.

Your thumbnail action code implies that get_image_for_display must be synchronous, because it's return value is used right away. Also it should be reenterable if you're using a threaded server (like puma) - so that two instances can run concurrently inside one process. This requires it to not write any state (class variables, other globals), or at least do it with synchronization. (mutexes and so on).
But even if your server is single-threaded, in production usually there're multiple processes. So for each step of the function think of how a concurrent run can interfere.
For example: I can guess that the function runs some external apps to process the data. First thing is to check that there's no temporary file name collision - when two instances write data to the same file (even if the data is going to be the same) - obviously it'll be broken.

This usually happens when the servers breaks the connection with the browser while streaming the data due to several reasons like networking or others.
Recommended approach here is to use send_file or use buffer by doing something like this. Streaming in chunks always ensures the data is completely streamed to the client
File.open('filename.jpeg', 'rb') do |file|
send_data(file.read, type: "image/jpg", disposition: "inline")
end

Related

Difference between three firebase storage download methods

I couldn't find resources discussing the difference between the three download methods in the firebase storage documentation and pros/cons of each. I would like some clarification about the firebase storage documentation.
My App
Displays 100 images ranging from 10 KB-500 KB in size on a table view
Will be used in a location where internet connection and/or phone service could be very weak
Could be used by many users
3 methods for downloading from Firebase storage
Download to NSData in memory
This is the easiest way to quickly download a file, but it must load entire contents of your file into memory. If you request a file larger than your app's available memory, your app will crash. To protect against memory issues, make sure to set the max size to something you know your app can handle, or use another download method.
Question: I tried this method to display 100 images that were 10KB-500KB in size on my table view cells. Although my app didn't crash, as I scrolled through my table, my memory usage increased to 268 mb. Would this method not be recommended for displaying a lot of images?
Download to an NSURL representing a file on device
The writeToFile:completion: method downloads a file directly to a local device. Use this if your users want to have access to the file while offline or to share in a different app.
Question: Does that mean all images from firebase storage will be downloaded on user's phone? Does that mean that the app will be taking up a large percentage of the available storage on the phone?
Generate an NSURL representing the file online
If you already have download infrastructure based around URLs, or just want a URL to share, you can get the download URL for a file by calling the downloadURLWithCompletion: method on a storage reference.
Question: Does this method require a strong internet connection and/or phone service connection to work?
Generally, your memory usage should not be affected by the method of retrieval. As long as you're displaying the 100 images, their data will be stored in the memory and should have the same size if they're identically formatted/compressed.
Either way you go with, I suggest you implement pagination (for your convenience, this question's answer might serve as a good implementation reference/guide) to possibly decrease the memory and network usage.
Now, down to comparing the methods:
Method 1
...but it must load entire contents of your file into memory.
This line might throw some people off thinking it's a
memory-inefficient solution, when all it really means is that you
cannot retrieve parts of the data, you can only download the entire
file. In the case of storing images, you probably would want that for
the data to make sense.
If your application needs to download the images every time the users
access it (i.e if your images are regularly updated), then this
method will probably suit you best. The images will be downloaded
every time the application starts, then they'll get discarded when
you kill it.
You stated that a part of your user base might have a weak internet
connection and so the next method might be more efficient and
user-friendly
Method 2
First off, the answers to your questions:
Yes. The images downloaded using this method will be stored on the users' devices.
The images should take up about the same size they're taking on Firebase storage.
Secondly, if you plan to use this method, then I suggest you store a
timestamp (or any sort of marker) in your database for when the last
change to the images occurred. Then, every time the app opens up, do
the following flow:
If no images are downloaded -> download images and store the database timestamp locally
If the local timestamp does not equal the timestamp on the database -> download images and store the new timestamp locally
Else -> use the images you already have, they should be identical to the ones in Firebase storage
That would be the best way to go if your network usage priority is
higher than that of the local storage.
And finally...
Method 3 (not really)
This is not a data download method, this simply generates a
download URL given a reference to the child. You can then use that
URL to download the data in your app or elsewhere as long as the used
app or API is authorized to access your Firebase storage.
Update:
The URL is generated from a Firebase reference (FIRDatabase.database().reference().child("exampleReference")) and would look like this: (Note: this is a fake link that will not actually work, just used for illustration purpose)
https://firebasestorage.googleapis.com/v0/b/projectName.appspot.com/o/somePathHere%2FchildName%2FsomeOtherChildName%2FimageName.jpg?alt=media&token=1a8f83a7-95xf-4d3s-nf9b-99a274927bcb
If you simply try to access that link you generate through any regular web-browser (assuming you don't have any Firebase rule that conflicts with that in your project), you can directly download that image from anywhere, not just through your app.
So in conclusion, this "Method" does not download data from Firebase storage, it just returns a download URL for your data in case you want a direct link.

How to cache images in Meteor?

I'm building a mobile app using Meteor. To allow for offline usage of the app, I want the app to be able to download a large-ish json file while online, then access the data in the json file, written to MongoDB, while offline.
This works fine. However, in the downloaded json file, there are plenty of references to online images that won't display in the app once the app is offline.
So, I want to be able to download (a selection of) the images referenced in the json file to the app, so that the app can access them even when offline.
(Downloading images could happen in the background for as long as a connection is available.)
There's an implementation of imgCache.js available on Atmosphere, which fails to initialize for me.
I suppose it's theoretically possible to individually load each image to a canvas, save the canvas content to MongoDB, then load the content when needed. Info on some of this is here. But, this feels rather convoluted and, if really feasible, I would expect someone to have done this before with success.
How can I do achieve caching of images for offline use in Meteor?
So, you've probably already read this article about application cache.
If the images are static, you can just include them in the manifest. Be sure you understand the manifest and cache expirations (see the article).
If the images are dynamic, you'll find some techniques to store images in local storage
If that's the case, this may be what you want.

Optimize image size in Parse.com

I have a strange issue with the resize methods on Parse Image Module:
First I get an image of 3.8MB (1920x1080px) in a Parse Cloud Code, then I resize it to 384x216px.
What I can't figure out is that when I download the resulting image in my front application, the file size is still 3.8MB while the image size reduced as expected.
I can't allow myself to force my users to download images so heavy.
Any suggestions on how to solve this out ?
I am ready to work with an external library as long as it can be executed on Parse Cloud Code.
Do not resize images in the cloud. Resize them on the client and send small files over the web. It takes shorter processing time to resize them in comparison to traveling over the network.
The reason you are seeing the old image in front of your application is probably because parse has an internal cache. Queries that have been sent before load from the cache to lower the amount of transferred data. You can easily observe this by deleting the application and reinstalling, your images will probably work fine.
http://blog.parse.com/2011/09/30/easy-caching-with-parse/
https://www.parse.com/docs/ios_guide#queries-caching/iOS

Rails: How to stream data to and from a binary column (blob)

I have a question about how to efficiently store and retrieve large amounts of data to and from a blob column (data_type :binary). Most examples and code out there show simple assignments but that cannot be efficient for large amounts of data. For instance storing data from a file may be something like this:
# assume a model MyFileStore has a column blob_content :binary
my_db_rec = MyFileStore.new
File.open("#{Rails.root}/test/fixtures/alargefile.txt", "rb") do |f|
my_db_rec.blob_content = f.read
end
my_db_rec.save
Clearly this would read the entire file content into memory before saving it to the database. This cannot be the only way you can save blobs. For instance, in Java and in .Net there are ways to stream to and from a blob column so you are not pulling every thing into memory (see Similar Questions to the right). Is there something similar in rails? Or are we limited to only small chunks of data being stored in blobs when it comes to Rails applications.
If this is Rails 4 you can use render stream. Here's an example Rails 4 Streaming
I would ask though what database you're using, and if it might be better to store the files in a filesystem (Amazon s3, Google Cloud Storage, etc..) as this can greatly affect your ability to manage blobs. Microsoft, for example, has this recommendation: To Blob or Not to Blob
Uploading is generally done through forms, all at once or multi-part. Multi-part chunks the data so you can upload larger files with more confidence. The chunks are reassembled and stored in whatever database field (and type) you have defined in your model.
Downloads can be streamed. There is a large tendency to hand off upload and streaming to third party cloud storage systems like amazon s3. This drastically reduces the burden on rails. You can also hand off upload duties to your web server. All modern web servers have a way to stream files from a user. Doing this avoids memory issues as only the currently uploading chunk is in memory at any give time. The web server should also be able to notify your app once the upload is completed.
For general streaming of output:
To add a stream to a template you need to pass the :stream option from within your controller like this: render stream: true. You also need to explicitly close the stream with response.stream.close. Since the method of rendering templates and layouts changes with streaming, it is important to pay attention to loading attributes like title, etc. This needs to be done with content_for not yield. You can explicitly open and close streams using the Live API. For this you need the puma gem. Also be aware that you need a web server that supports streaming. You can configure Unicorn to support streaming.

Large File Uploads

Do large file uploads block an applications request/response cycle? I have an app that allows users to upload multiple large files (images in particular). These files are stored on a remote host. I cannot use async background jobs to upload these images as these have to be immediately accessible to the user once the upload finishes. How best should i handle such large uploads? Does it affect concurrency? This is my first time with uploads on a large scale. What should i be wary of other than the huge bills of course? Any input from developers who have created apps which use large file uploads will be greatly appreciated.
Why can't you use an async upload, and just handle the event that signifies that it's done? That's generally how async operations work - you kick them off and then store the pointer somewhere, and then either handle the "Complete" event, or just periodically iterate through he pointers for uploads you've started and check each one to see if it's complete.
It's an old question but still, I was worried about the same problem with large files uploads thinking that the processes get blocked while the file is getting uploaded but it turned out, if I got it right, that the nginx and probably other servers as well buffer the content of the file while its being sent so no rails processes get blocked, only when the upload is finished and rails is processing it, like resizing images or something.

Resources