Progress bar for HttpClient uploading - wpf

I want to run asynchronous uploads with a progress bar in WPF (and preferably use PCL for code reuse in Xamarin and SL too.)
I've been trying to use System.Net.HttpClient.
Unfortunately, PutAsync doesn't provide any progress notifications. (I know that it does for Windows.Web.Http.HttpClient, but that's not available for WPF, nor in the PCL).
For downloading, its fairly easy to implement your own progress bar, as described here. You just pass the ResponseHeadersRead option, which makes the stream available as soon as the headers are returned, and then you read it in chunk by chunk, incrementing your progress bar as you go. But for uploading, this technique doesn't work - you need to pass all your upload data into PutAsync in one go, so there's no chance to increment your counter.
I've also wondered about using HttpClient.SendAsync instead. I'd hoped I could just treat this like an asynchronous HttpWebRequest (in which you can increment the counter as you write to the HttpWebRequest.GetRequestStream as described here). But unfortunately HttpClient.SendAsync doesn't give you writeable stream, so that doesn't work.
So does HttpClient support uploads with a non-blocked UI and a progress bar? It seems like a modest need. Or is there another class I should be using? Thanks very much.

Assuming that HttpClient (and underlying network stack) isn't buffering you should be able to do this by overriding HttpContent.SerializeToStreamAsync. You can do something like the following:
const int chunkSize = 4096;
readonly byte[] bytes;
readonly Action<double> progress;
protected override async Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context)
{
for (int i = 0; i < this.bytes.Length; i += chunkSize)
{
await stream.WriteAsync(this.bytes, i, Math.Min(chunkSize, this.bytes.Length - i));
this.progress(100.0 * i / this.bytes.Length);
}
}
In order to avoid being buffered by HttpClient you either need to provide a content length (eg: implement HttpContent.TryComputeLength, or set the header) or enable HttpRequestHeaders.TransferEncodingChunked. This is necessary because otherwise HttpClient can't determine the content length header, so it reads in the entire content to memory first.
On the phone 8 you also need to disable AllowAutoRedirect because WP8 has a bug in the way it handles redirected posts (workaround is to issue a HEAD first, get the redirected URL, then send the post to the final URL with AllowAutoRedirect = false).

Simple way to upload a file with progress
I had the same need, and after some tries found out that you can easily get byte-accurate upload progress by tracking the Position of the FileStream of the file that you are going to upload.
Here is one way to do that...
FileStream fileToUpload = File.OpenRead(#"C:\test.mp3");
HttpContent content = new StreamContent(fileToUpload);
HttpRequestMessage msg = new HttpRequestMessage{
Content=content,
RequestUri = new Uri(--yourUploadURL--)
}
bool keepTracking = true; //to keep tracking thread running
new Task(new Action(() => { progressTracker(fileToUpload, ref keepTracking); })).Start();
var result = httpClient.SendAsync(msg).Result;
keepTracking = false; //to stop the tracking thread
the function progressTracker() is defined as,
void progressTracker(FileStream streamToTrack, ref bool keepTracking)
{
int prevPos = -1;
while (keepTracking)
{
int pos = (int)Math.Round(100 * (streamToTrack.Position / (double)streamToTrack.Length));
if (pos != prevPos)
{
Console.WriteLine(pos + "%");
}
prevPos = pos;
Thread.Sleep(100); //only update progress every 100ms
}
}

Related

Download arbitrary files to Android and iOS cache

I wrote the following method:
/**
* Downloads an arbitrary file to the cache asynchronously, if the current
* platform has a cache path, or to the app home; if the file was previously
* downloaded and if it's still available on the cache, it calls the
* onSuccess callback immediatly.More info:
* https://www.codenameone.com/blog/cache-sorted-properties-preferences-listener.html
*
* #param url The URL to download.
* #param extension you can leave it empty or null, however iOS cannot play
* videos without extension (https://stackoverflow.com/q/49919858)
* #param onSuccess Callback invoked on successful completion (on EDT by
* callSerially).
* #param onFail Callback invoked on failure (on EDT by callSerially).
*/
public static void downloadFileToCache(String url, String extension, SuccessCallback<String> onSuccess, Runnable onFail) {
FileSystemStorage fs = FileSystemStorage.getInstance();
if (extension == null) {
extension = "";
}
if (extension.startsWith(".")) {
extension = extension.substring(1);
}
String name = "cache_" + HashUtilities.sha256hash(url);
if (!extension.isEmpty()) {
name += "." + extension;
}
String filePath;
if (fs.hasCachesDir()) {
// this is supported by Android, iPhone and Javascript
filePath = fs.getCachesDir() + fs.getFileSystemSeparator() + name;
} else {
// The current platform doesn't have a cache path (for example the Simulator)
String homePath = fs.getAppHomePath();
filePath = homePath + fs.getFileSystemSeparator() + name;
}
// Was the file previously downloaded?
if (fs.exists(filePath)) {
CN.callSerially(() -> onSuccess.onSucess(filePath));
} else {
Util.downloadUrlToFileSystemInBackground(url, filePath, (evt) -> {
if (fs.exists(filePath)) {
CN.callSerially(() -> onSuccess.onSucess(filePath));
} else {
CN.callSerially(onFail);
}
});
}
}
It works. It's similar to some methods provided by the Util class, but with two main differences: the first is that the Util class provides methods only to download images to the cache, while I want to download arbitrary files; the second is that I can assume that the same url always returns the same file, so I don't need to download it again if it's still in the cache (while the Util methods always download the files when invoked).
However, I have some doubts.
My first question is about how caching works: currently I'm using this method to download images and videos to cache (in a chatting app), assuming that I don't need to care about when the files will be not more necessary, because the OS will delete them automatically. Is it so, right? Is it possible that the OS deletes files while I'm using them (for example immediately after storing them to the cache), or Android and iOS delete only older files?
I wrote this method to store arbitrary files. Is there any reasonable limit in MB to the file size that we can store in the cache?
Finally, I have a doubt about the callSerially that I used in the method. Previously I didn't use that, but I got odd results: my callbacks do UI manipulations and frequently (but not always) something went wrong. I solved all my callbacks problems adding the callSerially, so callSerially is the solution. But... why? The odd fact is that the ActionListener of Util.downloadUrlToFileSystemInBackground is called under the hood by the addResponseListener(callback) of a ConnectionRequest instance, so the callback is already invoked in the EDT (according to the javadoc). To be sure, I tested CN.isEdt() in the callbacks without adding the callSerially, and it returned true, so in theory callSerially is not necessary, but in practice it is. What's wrong in my reasoning?
Thank you for the explanations.
As far as I know the cache directory is just a directory that the OS is allowed to delete if it needs space. I don't think it will delete it for an active foreground application but that might vary.
There are no limits other than storage. But you still need to consider that the OS won't just clean that directory for you. It will only flush it when storage is very low and even then not always. So you still need to store data responsibly.
I think only the first callSeially has an impact. It defers the result to the next EDT loop instead of continuing in the existing thread.

Codename One: show a (secret) Image from an URL without storing and without caching

I have the following code:
EncodedImage placeholder = EncodedImage.createFromImage(FontImage.createImage(size, size, ColorUtil.GRAY), true);
String url = "...";
Date date = new Date();
URLImage qrCode = URLImage.createToStorage(placeholder, date.getTime() + ".png", url, URLImage.RESIZE_SCALE);
qrCode.fetch();
qrCodeLabel.setIcon(qrCode);
The qrCode image contains a secret that should not be saved on the Storage / FileSystem and that should not be cached in any way. It should be shown to the user only one time.
Because these requirements, of course my code doesn't work as I need, because the image is saved and cached. I prefer that the execution of the code stops until the image is downloaded, instead this code firstly shows the placeholder, then shows the image.
So, my question is which code can I use to show an image in a Label, downloading it from an url with these requirements:
no cache;
no storing;
block of the execution until the image is ready (I have a loading Dialog that I want to dispose when the image is ready).
URLImage was designed for caching. You can obviously delete the storage file but it goes a bit against the core purpose of the class.
Just use something like:
ConnectionRequest q = new ConnectionRequest(imageUrl, false) {
public void postResponse() {
EncodedImage qr = EncodedImage.create(getResponseData());
labelForQr.setIcon(qr);
parentForm.revalidate();
}
};
addToQueue(q);

Codename One: dealing with server/connection unavailability when loading images

It seems that if at the time when URLImage.createCachedImage is called to load and the image is not available (no connection, etc.), it won't be called again to reload when the connection comes back.
Description:
When the connection is unavailable, it shows a blank image. And it still shows the same blank image when the Display.getInstance().callSerially(...) is called again. It does not seem like it is invoking the connection to try to load the image but the blank image has apparently become the cache.
I'm not sure if I'm describing my problem properly, but here's the simplified question: how to deal with loading of image and handle events when the connection/server is unavailable? (I thought URLImage.createCachedImage knows if the image is not loaded and will try again.)
I have this piece of code to load Image:
protected static final Image loadImage(String imageAccessLocation) {
int filenameIndex = imageAccessLocation.lastIndexOf("/");
String filename = imageAccessLocation.substring(filenameIndex + 1);
String imageAccessBaseLocation = Application.getInstance().getImagesAccessBaseLocation();
String imageAccessURL = imageAccessBaseLocation + imageAccessLocation;
int displayWidth = Display.getInstance().getDisplayWidth();
EncodedImage imagePlaceholder = EncodedImage.createFromImage(Image.createImage(displayWidth, displayWidth / 5, 0xffff0000), true);
Image image = URLImage.createCachedImage(filename, imageAccessURL, imagePlaceholder, FLAG_RESIZE_SCALE);
return image;
}
protected static final Container prepareImageContainer(Item item) {
Image image = item.getImage();
if(image == null) {
return null;
}
Image scaledImage = image.scaled(50, 50);
Container imageContainer = new Container();
imageContainer.add(scaledImage);
return imageContainer;
}
private final void prepare() {
Container imageContainer = prepareImageContainer(lookslike);
Container textContainer = prepareTextDescription(lookslike);
add(imageContainer);
add(textContainer);
}
You're right. If there is a network problem, URLImage will just silently fail.
If the URLImage hasn't finished downloading the image for whatever reason, its isAnimation() method will return true. This is a way to detect if it hasn't finished downloading. It doesn't tell you whether the download failed or just hasn't completed, but you could combine that with some sort of timeout to check if the image is downloaded, and, if not, replace it with a new URLImage for the same URL.
Not ideal, I know. You can file an RFE in the issue tracker and we'll evaluate the request.

Tombstoning the last visited page the user was on

I want to tombstone the last page that the user was on and to retrieve it when the user comes back to the app. All of the tombstoning examples on the Internet deal with saving some data or the state of the page that the user has edited somehow (i.e inputed text in the textbox). In my app I don't have anything for the user to modify/edit so I just want to save the last visited page that the user was on. I tried to use some online examples that used PhoneApplicationService.Current.State but with no success.
Thank you to anyone who would like to help me out resolving this issue!
To locally store persistent data (data that should remain even when the user closes the app), you can use Isolated Storage.
So, in your app's Deactivated event, you can write the page's name to Isolated Storage like this:
//You get the Isolated Storage for your app (other apps can't access it)
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
//if the file already exists, delete it (since we're going to write a new one)
if (isf.FileExists("lastpage.txt")) isf.DeleteFile("lastpage.txt");
using (var isoFileStream = new IsolatedStorageFileStream("lastpage.txt", FileMode.OpenOrCreate, isf))
{
//open a StreamWriter to write the file
using (var sw = new StreamWriter(isoFileStream))
{
//NavigationService.CurrentSource returns the current page
//we can write this to the file
sw.WriteLine((Application.Current.RootVisual as PhoneApplicationFrame).CurrentSource.ToString());
}
}
This will write the current page's name to the Isolated Storage. Then, in your OnNavigatedto method of your main page (the page that first opens normally) you can read the file name and navigate to it:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
string lastpage = string.Empty;
if (isf.FileExists("lastpage.txt"))
{
using (var isoFileStream = new IsolatedStorageFileStream("lastpage.txt", FileMode.Open, isf))
{
//read the file using a StreamReader
using (var sr = new StreamReader(isoFileStream))
{
//get the uri we wrote and then convert it from a String to a Uri
lastpage = sr.ReadLine().Replace("file:///", "");
}
}
(Application.Current.RootVisual as PhoneApplicationFrame).Navigate(new Uri(lastpage, UriKind.Relative));
}
base.OnNavigatedTo(e);
}
This should read in the file you saved and then convert the string into an actual URI that you can pass to the NavigationService.
You can then delete the text file after it's been read so that it doesn't always keep jumping to that page.
In addition you can use this for getting the page name ,string PageName = (Application.Current.RootVisual as PhoneApplicationPage).Name; for getting the current page name
While I agree all the above options are possible, they are not really the correct way of doing something within a WP7.
It's better to construct a navigation page at the start of your app for properly controlling navigation, it also helps with managing back key events while using the app and prevents hiccups.
See Here for one example of implementing this:
Properly Exiting Silverlight-based WP7
With this done use the advice on storing values in isolated storage / application settings to then just store the "Current Page" state value (e.g. value of Page.GamePage) then the app navigation will direct you accordingly.
BUT beware when just storing the current page itself as you also need to save the correct state of any values or User entered data on that page when it tombstones as well, this the above advice should lead you in the right direction.
Hope this helps

Managing cookies in a WPF WebBrowser control?

Is there a way to read/write the cookies that a WebBrowser control uses?
I am doing something like this...
string resultHtml;
HttpWebRequest request = CreateMyHttpWebRequest(); // fills http headers and stuff
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
resultHtml = sr.ReadToEnd();
}
WebBrowser browser = new WebBrowser();
browser.CookieContainer = request.CookieContainer; // i wish i could do this :(
browser.NavigateToString(resultHtml);
One of the potentially confusing things about the WebBrowser control and cookies is that at a first glance, it often looks like your app gets a separate cookie store. For example, if you log into a site that stores a persistent cookie to identify you, then whether you appear to be logged in for that site from inside an app hosting the control will be independent of whether you seem to be logged in via Internet Explorer.
In fact, you can even be logged in with different identities.
However, although it might be natural to draw the conclusion that each app hosting the WebBrowser therefore gets its own cookies, in fact that's not true. There are merely two sets of cookies: the ones used in 'low integrity' mode (which is what IE runs in by default), and the other set, which is what you'll get in a normal app that hosts the WebBrowser and also what you'll get if you run IE elevated.
the webbrowser control uses WinInet for networking, specifically use the InternetSetCookie(Ex) and InternetGetCookie(Ex) functions for Cookie management. There isn't a WinInet wrapper in .Net, but you can p-invoke.
Yes you are right, InternetGetCookieEx is the only way to retrieve HttpOnly cookies and it is the preferred way to grab cookie from WebBrowser control.
I posted a complete example here
You can use Application.GetCookie and Application.SetCookie methods.
Although Application is more or less related to WPF, you can use these methods in any desktop .NET code. In fact, they are wrappers on InternetGetCookieEx and InternetSetCookieEx Windows APIs.
I faced the same issue few days ago.
Besides the examples of the previous answers, here is a Win32 wrapper for the WebBrowser control. The advantage of this implementation is that it exposes more options that the default WebBrowser control.
Unfortunately if It's not WPF native, so you will have to create a wrapper if you're planning to use it in WPF.
http://code.google.com/p/csexwb2/
Here is sample from [link][1]
> public static class WinInetHelper
{
public static bool SupressCookiePersist()
{
// 3 = INTERNET_SUPPRESS_COOKIE_PERSIST
// 81 = INTERNET_OPTION_SUPPRESS_BEHAVIOR
return SetOption(81, 3);
}
public static bool EndBrowserSession()
{
// 42 = INTERNET_OPTION_END_BROWSER_SESSION
return SetOption(42, null);
}
static bool SetOption(int settingCode, int? option)
{
IntPtr optionPtr = IntPtr.Zero;
int size = 0;
if (option.HasValue)
{
size = sizeof(int);
optionPtr = Marshal.AllocCoTaskMem(size);
Marshal.WriteInt32(optionPtr, option.Value);
}
bool success = InternetSetOption(0, settingCode, optionPtr, size);
if (optionPtr != IntPtr.Zero) Marshal.Release(optionPtr);
return success;
}
[System.Runtime.InteropServices.DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool InternetSetOption(
int hInternet,
int dwOption,
IntPtr lpBuffer,
int dwBufferLength
);
}

Resources