Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Windows 8–When Saving Isn’t Saving and Opening Isn’t Opening :-)

Blogs

Mike Taulty's Blog

Elsewhere

Archives

In a Windows 8 Store app, it’s pretty easy to put a dialog on the screen that allows the user of your app to save or open a file from/to somewhere. For example, here’s a simple piece of code which attempts to ask the user for a zip file and then will try and save that file somewhere else.

  async void OnButtonPress(object sender, RoutedEventArgs e)
        {
          FileOpenPicker openPicker = new FileOpenPicker();
          openPicker.FileTypeFilter.Add(".zip");
          openPicker.SuggestedStartLocation = PickerLocationId.Desktop;
          openPicker.ViewMode = PickerViewMode.List;
          StorageFile inFile = await openPicker.PickSingleFileAsync();
 
          if (inFile != null)
          {
            FileSavePicker savePicker = new FileSavePicker();
            savePicker.DefaultFileExtension = ".zip";
            savePicker.SuggestedStartLocation = PickerLocationId.Desktop;
            savePicker.FileTypeChoices.Add(
              "Zip Files", new string[] { ".zip" });

            StorageFile outFile = await savePicker.PickSaveFileAsync();

            if (outFile != null)
            {
              await inFile.CopyAndReplaceAsync(outFile);
            }
          }
        }

Saving to Another App

If I navigate through the 2 dialogs that the system puts in front of me, I get one file copied to another.

image

image

That’s all fine and works great but one of the strengths of the system is that the file dialog is extensible. You can read the docs about how it is extensible but one of the things that I find interesting about this is that it exposes my app to a scenario that it can’t directly code against and I can demonstrate that by using this dialog to save my file to SkyDrive.

Lsay that in the Save dialog above I don’t choose a local file as the destination, I choose a SkyDrive file;

image

Now, as it happens the zip file that I’m dealing with is 26MB in size. So what happens?

Surprisingly, I see my application code complete pretty quickly and what I suspect has happened is that the SkyDrive app has offered my app a file to write into which is local inside the SkyDrive app’s own folder structure.

I can kind of confirm this by poking around in the SkyDrive app’s local disk folder where I seem to find my file;

image

and if I repeat the process with a second file called test2.zip I see that appear here too in its own sub-folder;

image

If I run up resource monitor then I can also see;

image

and so that leads me to assume that the SkyDrive app is using the background transfer API to move these files up to SkyDrive having let my app write into them initially.

I wrote about background transfers before and one of the things that I noted was that background transfers are stopped if you close the application that started them.

Now, in this particular instance this is a little strange because ( from a user’s perspective ) I haven’t opened SkyDrive and nor do I see it running.

However, if I go and run SkyDrive and then attempt to close it I see;

image

so, it’s clear that the system seems to think that the SkyDrive app has background transfers working on my behalf and I’m at risk of losing them.

If I do go ahead and close the SkyDrive app then it seems that the background transfers stop as I would expect. However, if I run the SkyDrive app again it looks like the app has the smarts to realise that its background transfers have been stopped by the system and that they need restarting and that seems to happen and so if I wait around a while then in the end the file does indeed seem to show up on the SkyDrive in all of its glory.

So, in saving to a remote file in this way the user has saved the file from your app to SkyDrive. Job Done.

From the developer’s perspective, the code has run and the file has been written out. Job Done.

However, there is potentially a lot more work going on before the file actually shows up on the user’s SkyDrive and that’s largely opaque to the user and also to the developer of the app because the background transfers being undertaken are now owned by another app and (as far as I know) there’s no way for the original app to let the user know “your data didn’t actually get saved yet, it’s still happening”.

What about opening files via another app?

Opening from Another App?

If I turn the scenario around and take a big file from SkyDrive and attempt to use my same code above to copy it to my desktop?

image

It’s “interesting” because if I do this with a file like test above which I just uploaded to SkyDrive via my code then I see SkyDrive cleverly hand my code the local copy it already has of this file rather than do any kind of cloud download.

However, if I delete the local cached folders that the SkyDrive app seems to be using and then try again;

image

Then what’s the SkyDrive app to do? It’s got 26MB of data across the network that it’s got to return back into my app code here as a StorageFile for my app code to open.

What seems to happen is that the SkyDrive selection dialog seems to create a local file ( in the folder structure that I showed earlier ) as soon as your selection is made from the dialog and then start a background download from the cloud into that new file.

Consequently, the code that I’ve got which does CopyAndReplaceAsync can take quite a long time as the data is coming in over the network.

I changed my code a little to try and have a look at this with a bit of tracing;

  using (Stream outStream = await outFile.OpenStreamForWriteAsync())
                {
                  DateTime now = DateTime.Now;
                  byte[] buffer = new byte[4096];
                  int bytesRead = 0;
                  int totalBytes = 0;
                  int mbMarker = 1;

                  while ((bytesRead = await inStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                  {
                    await outStream.WriteAsync(buffer, 0, bytesRead);

                    totalBytes += bytesRead;

                    if ((totalBytes / (1024 * 1024)) > mbMarker)
                    {
                      Debug.WriteLine("Took {0}ms to get to {1}MB",
                        (DateTime.Now - now).TotalMilliseconds,
                        mbMarker);

                      mbMarker++;
                    }
                  }
                }

and the output showed me;

Took 11815.8769ms to get to 1MB
Took 11899.8895ms to get to 2MB
Took 11942.8997ms to get to 3MB
Took 11988.8971ms to get to 4MB


Took 17252.88ms to get to 78MB

So, it looks like what’s happening here is that the save picker hands my code File A while the SkyDrive app goes off and does a background transfer into File B and then, presumably, streams File B into File A when that background transfer is complete and my (pending) asynchronous reads then start to read that data back as it shows up in File A.

What that means is that even though the file open dialog has long since left the screen, the time taken to open the data that you’ve been handed could be many seconds or minutes and so you really need to think about having progress indicators on the screen while that’s loading.

Suspend/Resume/Terminate

I wondered about the implications of this for the suspend/resume/terminate cycle of my app.

In the case where my app is saving data to a location like SkyDrive, it would seem that my app’s code gets out of the way pretty quickly even if the saved file is in the cloud and might not show up there for quite some time. It seems unlikely that my app is going to get suspended while it’s actually saving out data.

In the case where my app is loading data, it’s more than possible that a scenario of;

  1. Offer the user a file open dialog.
  2. Start to read the data which (if it is coming from SkyDrive) could take a long time.
  3. Suspended.
  4. Either;
    1. Resumed – in which case, the data reading/writing should carry on.
    2. Terminated.

It’s hard to know what the right experience is here around 4.2. The app presented the user with a dialog and they selected a file to ‘read’. Then this took a long time so the user went away and did something else and the app got terminated having never read the file.

When the app next runs, it’s hard to know what the right thing to do is. It might be to flag to the user that this happened and let them ‘read’ again. Alternatively, it might be right to attempt to open the file again (this would require having asked for future access to it when it was presented from the save picker) and attempt to read the data automatically in the background with some kind of “completing the previous file operation you started” message to the user.

I guess it’s app-specific but it seems to me that it’s a potentially thorny area arising from a simple thing like showing a “file” selection dialog and reading/writing the file that the user chooses and it certainly needs a bit of thinking about.


Posted Fri, Mar 29 2013 3:25 PM by mtaulty

Comments

Vitor Canova wrote re: Windows 8–When Saving Isn’t Saving and Opening Isn’t Opening :-)
on Sat, Mar 30 2013 1:19 AM

Awesome. Never realized all those things happen when using SkyDrive.