One of the things that I’m really happy to see in Windows 8.1 is a more complete inclusion of SkyDrive. Windows 8.0 had SkyDrive – there was a built-in Store app but in order to do something on the desktop you needed to go and download a separate client app just like on Windows 7 or on OS X.
As far as I know, in 8.0 there’s was a difference between the client app and the Store app. The Store app is accessing the SkyDrive RESTful services over HTTP to show you exactly what’s on your SkyDrive (which means it needs network connectivity and speed is limited by that connectivity) whereas the client app was more like a traditional sync client (like Groove, Live Mesh etc) in that the files were physically present on the various disks of your various devices and were sync’d via the cloud.
I found that duality a bit odd – you could get 2 different views of “SkyDrive” and I tended to stick to the view of the Store app and avoid the desktop app which was odd because I’d generally always been quite a heavy user of the desktop app but once presented with these 2 views I couldn’t quite reconcile them so preferred the immediacy of the Store app.
As I started writing this post (a week or two ago), a blog post appeared on the Windows blog that talked about exactly this area so I’d strongly recommend taking a look at that post;
(it seems fair to say that there’s a whole bunch of comments on this blog post around the ins-and-outs of how SkyDrive sync is working out for people and also about the way in which it makes use of a Microsoft Account – given that the comments are directly on the Windows blog itself I’m not going to attempt to jump into any of that discussion – I’d expect that the product folks are reading, hearing and acting on those comments).
because it details these 2 different approaches and talks about the pros/cons and trade-offs of both and it also leads off to the post on “Smart Files”;
which is a simple enough idea – a small metadata stub in the file system (“smart file”) backed by the “smarts” that allow the system to request more details from whatever service (in this case SkyDrive) is backing that “smart file”.
“More details” might include a thumbnail picture that can be produced server-side for the file at a particular resolution or “more details” might mean getting hold of the whole file itself when it’s needed.
Naturally, the “problem” with that “just in time” kind of access would be that when you were offline, you’d only be able to work on files that you’d previously opened and the system had cached on the device so SkyDrive supports a more active approach in that you can mark files/folders as “available offline”;
and that can be done either in the explorer view as pictured above or in the Store app and then, as a user, you know that certain files will always be on your device if you want them (or at least until you remove that “available offline” option in the future) and the whole thing is a kind of “pay for play” scenario in that of course storing the full files is a lot more expensive than storing just a “smart file” metadata stub.
To play with this, I took this big, fat PowerPoint file which is about 5MB and dropped it into a SkyDrive folder via Explorer;
and I can see SkyDrive doing “something” with it and then if I ensure that I have “online only” selected for that file (or its folder);
then I can see that this file is only taking 8KB of space on the disk but if I double-click it then it’s brought down from SkyDrive on-demand so I can (in an online scenario) have the file without paying the price for it on my disk or I can just use the “available offline” option if I need the file when I’m disconnected and then it takes its full quota of disk space.
This seemed novel enough to me to contemplate sticking all my photos and music into SkyDrive and then on specific devices I can play around with the online/offline nature of folders of photos and music according to how much disk space I have available and to what extent I use that device for photos/music in online/offline situations.
With that in mind, I was particularly pleased to see that explorer embraces the idea of these SkyDrive folders being added to libraries.
I tried that out with a couple of albums and copied a bunch of music (around 150MB) into a SkyDrive folder called Music, made sure it was only available online and then added it to the Music library on the machine and then wandered into the Music app on Windows 8 and all seemed to work pretty well in that my music showed up and was playable.
If I took the machine offline I can still see all my tracks inside of the Music app but they don’t play. Strangely, the music app just didn’t seem to “do anything” when I tried to play a track that would only be available online. It didn’t error and nor did it hang. It just didn’t do anything but this might be a “preview” thing.
When I looked at those files/folders in the SkyDrive app, it was more direct. It told me that I was offline and it told me that only the “offline” files would be available to me.
I found that interesting enough to try an experiment.
- While offline, I went to my SkyDrive view as presented by explorer.exe and created a new file called foo.txt.
- I went into the SkyDrive Store app and had a look to see if foo.txt showed up.
It did show up which is exactly what I’d want but it made me wonder how this is being done.
Clearly, this can’t involve using the Live SDK to go and make RESTful calls to SkyDrive when the device is offline. This must be happening locally and then being sync’d to the cloud.
That left me wonder about;
- How does a Store app in 8.1 best access SkyDrive files/folders? Does it use the Live SDK and talk directly over HTTP to the cloud or does it use another mechanism like the built-in SkyDrive app seems to?
- How do those new “smart files” work in a traditional desktop app? How does an app from (say) Windows 7 work when it encounters this new kind of file?
I thought I’d experiment a little and started with the second area – desktop apps that work with files and, in Windows 8.1, are going to perhaps encounter “smart files” for the first time.
Before dropping into those experiments, it’s worth saying that there’s a video up on Channel 9 from BUILD 2013 with a bunch of information in this area;
that’s definitely worth watching – meanwhile, back to my experiments…
Desktop Apps
I made a blank WPF 4.5.1 application and wrote a bit of code to let the user select a file and then to attempt to read that file synchronously – this code;
OpenFileDialog d = new OpenFileDialog(); if ((bool)d.ShowDialog()) { using (Stream s = d.OpenFile()) { byte[] bits = new byte[1024]; while (s.Read(bits, 0, bits.Length) > 0) { Debug.WriteLine("Read 1K"); } } } else { Debug.WriteLine("Didn't get a file"); }
I first ran this code when I was on a pretty poor network connection which let me see the dialogs involved pop up on screen. I made sure that I was picking a file that was “online only” and that I hadn’t opened before (to avoid being served out of some cache) and I fed that file into the code above via the dialog that it raises;
The file dialog stays on screen while the “downloading” dialog appears over the top of it and because I was on such a poor network this took way too long and I hit the cancel button.
That launched up the cancelling dialog below which also stayed on the screen for a very long time;
but in the end the cancellation request seemed to get heard and the “cancelling…” dialog went away and, as a user, I got returned to the file open dialog.
I wondered to what extent this might be related to my attempting to read the file synchronously so I tried to read it asynchronously but that didn’t seem to make any difference.
I also wondered what happened if the user picked an “online only” file when the machine was offline so I disconnected my network altogether and tried my code again;
I must admit that I was a bit surprised that the machine seemed to spend time trying to go off to the network and displayed the “downloading” dialog even though Windows was telling me that it wasn’t on a network.
After a while I got this dialog;
which didn’t seem so intuitive in the “Interrupted Action” title and the text itself because from my perspective as a user I’m opening a file rather than copying a file but this dialog tells me it had trouble copying a file.
I tried to repeat this with PowerPoint itself rather than using my own code.
I took my machine off the network and I used the file open dialog in PowerPoint to open a big file that is on my SkyDrive but is set for “online only” access. I waited a little while and then got the same “Interrupted Action” dialog.
It’s possible that there are differences here between what I get on the preview 8.1 OS and what happens in the RTM 8.1 OS so I’ll re-investigate when I’ve moved a machine to 8.1 RTM and will update the post if it’s different.
Not all apps open all files via a file dialog of course.
Many applications keep hold of file paths and simply read/write files on a user’s behalf using those file paths. A typical example would be when Excel presents a file dialog for the user to select and open a file. Excel will then store that file path so that it can offer it to the user on a “Most Recently Used” list in the future without the user having to go through a dialog to get to the file again. It’s a simple convenience that’s been around for decades and it’s just one example.
I wondered how that might work for the “smart file” situation – what if a user gives an app a file which is actually a “smart file” stub? I tried this code on a file from my SkyDrive that I hadn’t touched in a long time;
string filePath = @"C:\Users\mtaulty\SkyDrive\Shared\RTMContosoCookbook\RTM_ContosoCookbook_CS.zip"; try { using (Stream s = File.OpenRead(filePath)) { byte[] bits = new byte[1024]; while (s.Read(bits, 0, bits.Length) > 0) { Debug.WriteLine("Read 1K"); } } } catch (Exception ex) { }
what was “interesting” to me about that file was that I couldn’t read it. I got back an exception (IOException with an HRESULT in it of 0x80070780).
Initially, that was a bit of a surprise but I think it makes sense if this “smart file” work is being done at the shell level of Windows rather than (say) somewhere deeper down in the operating system. For a long time, the shell has presented a view of various parts of the system and has been extensible so that developers can plug items into it. If the shell is doing this work then maybe asking the shell to retrieve the bytes of this file for me is going to work better (although it’s likely to be a “lot less fun” from .NET code). I made a fairly flimsy attempt;
IShellItem shellItem = null; Guid BHID_Stream = new Guid("1CEBB3AB-7C10-499a-A417-92CA16C4CB83"); SHCreateItemFromParsingName( @"C:\Users\mtaulty\SkyDrive\Shared\RTMContosoCookbook\RTM_ContosoCookbook_CS.zip", IntPtr.Zero, typeof(IShellItem).GUID, out shellItem); if (shellItem != null) { IntPtr pIStream; shellItem.BindToHandler(IntPtr.Zero, BHID_Stream, typeof(IStream).GUID, out pIStream); IStream iStream = (IStream)Marshal.GetObjectForIUnknown(pIStream); byte[] buffer = new byte[1024]; // TODO: Not sure UInt32 is 100% right here... Int32 totalBytesRead = 0; IntPtr pBytesRead = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(UInt32))); iStream.Read(buffer, buffer.Length, pBytesRead); while (Marshal.ReadInt32(pBytesRead) > 0) { totalBytesRead += Marshal.ReadInt32(pBytesRead); iStream.Read(buffer, buffer.Length, pBytesRead); } Marshal.FreeHGlobal(pBytesRead); Marshal.ReleaseComObject(iStream); Debug.WriteLine(string.Format("Read a total of {0} bytes", totalBytesRead)); Marshal.ReleaseComObject(shellItem); }
and that code relied on a few interop bits and pieces mostly borrowed from PInvoke.net;
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void SHCreateItemFromParsingName( [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath, [In] IntPtr pbc, [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid, [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem ppv);
along with;
[ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] public interface IShellItem { void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)]Guid bhid, [MarshalAs(UnmanagedType.LPStruct)]Guid riid, out IntPtr ppv); void GetParent(out IShellItem ppsi); void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); }; public enum SIGDN : uint { NORMALDISPLAY = 0, PARENTRELATIVEPARSING = 0x80018001, PARENTRELATIVEFORADDRESSBAR = 0x8001c001, DESKTOPABSOLUTEPARSING = 0x80028000, PARENTRELATIVEEDITING = 0x80031001, DESKTOPABSOLUTEEDITING = 0x8004c000, FILESYSPATH = 0x80058000, URL = 0x80068000 }
and, sure enough, that code seemed to mostly hold together and I could debug it to see my loop spinning and I could watch my network card activity as it pulled the file over SkyDrive and because I hadn’t put this work onto some kind of background thread it blocked the UI for a very long time indeed before finally coming up with the right number of bytes for the ZIP file in question.
Of course, running the code for a second time gave me an instant result because the ZIP file is now down on the local hard-drive and there’s no SkyDrive work to do.
So…it looks like reading from one of these files is possible but it’s perhaps got to be done at the shell level (i.e. IShellItem and friends) rather than at the operating system (i.e. CreateFile/ReadFile/WriteFile and friends) functions that .NET would usually use.
I’m not sure whether there’s a great library out there that makes this sort of shell programming much easier so forgive me if I could have just used a library for this with all the interop code already baked into it but I know that for a very long time the Windows Shell and .NET didn’t mix too well together so that library might not even be out there today.
Store Apps
What about a Windows Store app? How does a Store app interact with these new SkyDrive files? As far as I know, there are a few ways in which a SkyDrive file/folder might be given to a Store app;
- Via a file/folder picker. The user gets presented with a dialog and they give a file/folder to the app.
- Via a library. The app has access to one or more of the user’s libraries (e.g. music) and that library happens to contains files from SkyDrive.
- Via share. It’s possible for one app to share a file/folder with another app so I guess it’s possible that the source app could share a file that originated on SkyDrive.
Whereas the built-in SkyDrive app has a complete view of the user’s SkyDrive as represented on the disk, a custom app can’t get that complete picture in the same way. There’s no API that serves that purpose. A Windows 8.1 app can do the same thing as an 8.0 app in that it could go across the web over HTTP to SkyDrive and try and build a complete picture of the user’s SkyDrive that way but that only works online and runs the risk of being out of sync with what’s happening on the local machine as in my previous example of creating the “foo.txt” file while offline.
It’s perhaps fair to say though that most apps won’t need complete access to all of a user’s SkyDrive content – that’s perhaps a job that’s mostly handled by the SkyDrive app itself.
It’s also worth remembering that a Windows 8 Store app has a very different security model and lifecycle model to a desktop app. For instance, any app that wants to ask the user for access to a particular file/folder has to take care to avoid a scenario such as;
- App asks user for access to a file.
- User says “yes”, hands the app a file.
- App stores the path of the file for later use.
- App is suspended, terminated.
- User runs app again, starts new process which uses saved state to revert back to its previous position.
- App attempts to use file that was previously granted to it by the user but no longer has access.
There are ways of handling this (see MSDN http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.accesscache.storageapplicationpermissions.futureaccesslist.aspx) but it’s something that always has to be factored into handling files in a Store app.
The other thing that has to be factored into a Windows Store app is the asynchronous nature of the file/folder APIs which, no doubt, helps when the content of the file might first need bringing across the network from SkyDrive and so becomes much an asynchronous operation that takes a lot longer than just reading a file from a solid state drive.
So, what happens if a Store app pops up a dialog, accepts a file and then finds it’s got a “smart file” metadata stub rather than a full file? Code like this;
FileOpenPicker picker = new FileOpenPicker(); picker.FileTypeFilter.Add(".zip"); StorageFile file = await picker.PickSingleFileAsync(); try { // See if the file can be got hold of - i.e. is local, is cached local // or can be downloaded. if ((file != null) && file.IsAvailable) { // the availability might change any time so things could // fail and I'm doing nothing with that here. using (var stream = (await file.OpenReadAsync()).AsStreamForRead()) { byte[] bits = new byte[1024]; int readCount = 0; int totalRead = 0; while ((readCount = await stream.ReadAsync(bits, 0, bits.Length)) > 0) { totalRead += readCount; } Debug.WriteLine(string.Format("Read a total of {0} bytes", totalRead)); } } } catch (Exception ex) { Debugger.Break(); }
Running this code without a network connection still allowed me to choose “SkyDrive” as an option for file open as below;
and told me that the file that I was picking was not available offline which is nice;
but I was surprised when the debugger hit line 8 of that code above and the StorageFile.IsAvailable flag returned True which suggested that the file was available. That seems wrong to me so I wonder if it’s a “preview thing” but it’s definitely what I saw and I would then find that my call on line 12 to OpenAsync would return ok but the first read call on line 18 to ReadAsync would timeout after quite a bit of time and leave me with an IOException wrapped around a timeout.
That was a bit of a surprise – I was hoping the IsAvailable flag would stop this happening but it’s maybe that I’m seeing some freak result.
If I switched the network to be back on then the file would load just fine.
I wondered how this worked for folders – that is, if the app is given a whole folder to play with from SkyDrive, can it enumerate those files and see the difference in online/offline availability? That’s supposed to be how things work so I figured I’d try it out;
async void OnEnumerateFolder(object sender, RoutedEventArgs e) { var picker = new FolderPicker(); picker.FileTypeFilter.Add("*"); var folder = await picker.PickSingleFolderAsync(); if (folder != null) { var files = await folder.GetFilesAsync(); foreach (var file in files) { Debug.WriteLine( string.Format("File {0} is reported as {1}", file.Name, file.IsAvailable)); } Debug.WriteLine( string.Format("Current network status is reported as {0}", (NetworkInformation.GetInternetConnectionProfile().GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess) ? "online" : "offline")); } }
for a situation where my machine was offline and the call to NetworkInformation was returning “offline” I found that even if I used the dialog to pick a folder which contained “online only” files – i.e. files which should not be available offline I was still getting the IsFileAvailable flag returned as true which leads me to think that this is perhaps either a “preview thing” or a “my machine is busted” thing so I’ll definitely revisit on the RTM version when I finally get around to upgrading my machine.
Assuming the IsFileAvailable flag is just a glitch, I wanted to see what would happen if I asked for thumbnails in both online/offline scenarios and I thought photos would be a good choice for that so I added a simple FlipView onto a UI and fed it some thumbnails from a photos folder on SkyDrive.
The first thing I tried to do was execute code like this;
var folderPath = @"c:\users\mtaulty\skydrive\skydrive camera roll"; var folder = await StorageFolder.GetFolderFromPathAsync(folderPath);
which taught me a lesson as I got denied access to that folder attempting to get access to it this way. I went back to using a FolderPicker to get hold of a SkyDrive folder;
async void OnFeedFlipView(object sender, RoutedEventArgs e) { FileThumbnailWrapper.Dispatcher = this.Dispatcher; var picker = new FolderPicker(); picker.FileTypeFilter.Add("*"); var folder = await picker.PickSingleFolderAsync(); var queryOptions = new QueryOptions(CommonFileQuery.OrderByName, new string[] { ".jpg", ".png" }); var query = folder.CreateFileQueryWithOptions(queryOptions); FileInformationFactory factory = new FileInformationFactory(query, ThumbnailMode.SingleItem, 640, ThumbnailOptions.ResizeThumbnail, false); var files = await factory.GetFilesAsync(); this.flipView.ItemsSource = files.Select(f => new FileThumbnailWrapper(f)); }
and so this code is trying to query all jpg/png files in a particular SkyDrive folder and put them into a FlipView which is simply set up as;
<FlipView x:Name="flipView" HorizontalAlignment="Left" Height="534" Margin="447,74,0,0" VerticalAlignment="Top" Width="827"> <FlipView.ItemTemplate> <DataTemplate> <Image Source="{Binding Thumbnail}" /> </DataTemplate> </FlipView.ItemTemplate>
It’s a bit “brute force” and I had to introduce a FileThumbnailWrapper class for 2 reasons – one is that I can’t bind a XAML Image to a StorageItemThumbnail and the other is that as far as I know whether you pass a value of True/False into that final parameter to the constructor for FileInformationFactory you can still get a NULL StorageItemThumbnail returned by the system and you have to wait for a ThumbnailUpdated event (or, from my experiments here possibly more than one of those events) before the thumbnail actually arrives. Here’s that wrapper class (it’s a lot hacky in its use of a dispatcher);
class FileThumbnailWrapper : INotifyPropertyChanged { BitmapImage bitmapImage; public static CoreDispatcher Dispatcher; public FileThumbnailWrapper(FileInformation fileInformation) { fileInformation.ThumbnailUpdated += OnThumbnailUpdated; this.CreateImage(fileInformation.Thumbnail); } void CreateImage(StorageItemThumbnail thumbnail) { if (thumbnail != null) { this.bitmapImage = new BitmapImage(); this.bitmapImage.SetSource(thumbnail); } } public ImageSource Thumbnail { get { return (this.bitmapImage); } } void OnThumbnailUpdated(IStorageItemInformation sender, object args) { Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { if (sender.Thumbnail != null) { this.CreateImage(sender.Thumbnail); if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs("Thumbnail")); } } }); } public event PropertyChangedEventHandler PropertyChanged; }
If I exercised this code with the network disconnected then I could quickly get to the point where the system didn’t have a real thumbnail for an image stored on an unavailable SkyDrive folder that I was trying to enumerator and so would return;
which kind of makes sense and in the situation where the network was available and I was requesting a thumbnail that I hadn’t given the system any chance to figure out previously then I would generally see this image load as the first thumbnail my app was presented with and then the real thumbnail would arrive as an asynchronous update to what was on the screen.
That all seemed to work pretty well. What I don’t know (because I didn’t yet manage to get any kind of trace on it) is whether the system was using some cleverness around requesting these thumbnails – that is, for a 640 thumbnail of a big image is it doing the resizing work on the server-side and only bringing across the network the small image required or is it bringing across the whole image and then resizing it locally.
The other part of this is libaries. I can easily put together a library that mixes SkyDrive items with local file system items and so I tried to make my pictures library a combination of 3 things;
- a local photos folder
- a folder served off a NAS box in my house
- a folder served off SkyDrive
I wasn’t surprised but I did find it slightly odd that I can add a folder from SkyDrive into my pictures library but I still can’t add a folder served off a NAS box as a network share into that library. I know that there are ways around this with one of them being to create symbolic links in the file system but it surprised me that there’s not a better, built-in way of dealing with what might be a fairly common scenario.
Regardless, once I’d got a pictures library with both local and SkyDrive content into it I made sure that my test app had the capability to talk to the pictures library and then effectively re-tried the code that I’d been running above;
async void OnReadPicturesLibrary(object sender, RoutedEventArgs e) { // nasty hack so that this class has a dispatcher to play with. FileThumbnailWrapper.Dispatcher = this.Dispatcher; var queryOptions = new QueryOptions(CommonFileQuery.OrderByName, new string[] { ".jpg", ".png" }); var query = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions); FileInformationFactory factory = new FileInformationFactory(query, ThumbnailMode.SingleItem, 640, ThumbnailOptions.ResizeThumbnail, false); var files = await factory.GetFilesAsync(); this.flipView.ItemsSource = files.Select(f => new FileThumbnailWrapper(f)); }
and that seemed to work as expected – i.e. when offline I get thumbnails for the files from the library that are in my local file system plus any thumbnails that the system has already built for files that are on SkyDrive. For files that are on SkyDrive where the system hasn’t already built/cached thumbnails I just get placeholder thumbnails. Once online, of course, I get thumbnails for everything.
Once the app had access to a SkyDrive file/folder, I was curious to know whether it could get access to that file again in the case of a suspend/terminate and so I tried that out via invoking these OnPickFile/OnReadFile methods from the UI across different instantiations of my app’s process;
async void OnPickFile(object sender, RoutedEventArgs e) { FileOpenPicker picker = new FileOpenPicker(); picker.FileTypeFilter.Add(".zip"); StorageFile file = await picker.PickSingleFileAsync(); if (file != null) { StorageApplicationPermissions.FutureAccessList.AddOrReplace("myToken", file); } } async void OnReadFile(object sender, RoutedEventArgs e) { if (StorageApplicationPermissions.FutureAccessList.Entries.Count > 0) { var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync("myToken"); ReadFile(file); } } static async void ReadFile(StorageFile file) { try { // See if the file can be got hold of - i.e. is local, is cached local // or can be downloaded. if ((file != null) && file.IsAvailable) { // the availability might change any time so things could // fail and I'm doing nothing with that here. using (var stream = (await file.OpenReadAsync()).AsStreamForRead()) { byte[] bits = new byte[1024]; int readCount = 0; int totalRead = 0; while ((readCount = await stream.ReadAsync(bits, 0, bits.Length)) > 0) { totalRead += readCount; } Debug.WriteLine(string.Format("Read a total of {0} bytes", totalRead)); } } } catch (Exception ex) { Debugger.Break(); } }
and that seemed to work fine.
Everything that I’ve tried so far relates to reading files. I guess that’s the more obvious side of things to explore because of the tantalising way in which SkyDrive offers “smart files” to an application that may or may not actually be available based on their “online only/offline available” status and the presence of a network connection.
But what about writing files? The “nice” thing about this new model for dealing with SkyDrive files is that the system will present the user with their SkyDrive “structure” whether or not they are online and an app can write into a file and then let the system deal with sync’ing it to the cloud at a later point. That, presumably, makes the business of writing a lot easier than reading although that leaves to one side the obvious issues around concurrent writes to the same file on different devices and how the synchronisation process deals with that.
As an example, here’s a UI using a WebView, a TextBox and a Button to simulate “a web browser”. It takes about 3 minutes to cook that up in Visual Studio 2013;
I can easily (again via Visual Studio) add an application bar and use a CommandBar to add a “take screenshot” button which would allow the user to snap whatever they are viewing in the browser and write it to an image file where the webView member variable in the code below is, as you’d expect, an instance of a WebView control;
async void OnScreenshot(object sender, RoutedEventArgs e) { FileSavePicker picker = new FileSavePicker(); picker.DefaultFileExtension = ".jpg"; picker.FileTypeChoices.Add("jpeg files", new string[] { ".jpg" }); var file = await picker.PickSaveFileAsync(); if (file != null) { using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite)) { RenderTargetBitmap bitmap = new RenderTargetBitmap(); await bitmap.RenderAsync(this.webView); IBuffer pixels = await bitmap.GetPixelsAsync(); var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream); encoder.SetPixelData( BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, DisplayInformation.GetForCurrentView().LogicalDpi, DisplayInformation.GetForCurrentView().LogicalDpi, pixels.ToArray()); await encoder.FlushAsync(); } } }
and that seems to work quite nicely and quickly and unlike in the read-case I don’t really have to worry about “SkyDrive not being there” because, from the point of view of writing SkyDrive is always there (AFAIK!).
Summing Up
There’s some impact here for a non Windows Store application like a desktop application – it’s possible that a user could present you with a path to a file that doesn’t necessarily behave quite like a regular file any more and you’d have to be a bit careful with that. It’s similar to a user presenting you with a networked file path but not exactly the same.
For a Store application, the impact seems smaller. The user presents the application with “files” via one of a number of mechanisms including pickers, libraries and so on and then the app works with those files as though they were local files even if they are, in reality, stored elsewhere.
The StorageFile.IsAvailable flag feels like it becomes really useful and I’ll update this post as/when I’ve worked with it on the RTM version of the OS as I think that’s key to an app being able to figure out what files are/aren’t present on the device but, naturally, that availability is always subject to the race condition of losing a network connection at any time in your code.
I think the main impact for a Store app is then one that should already be encoded into every Store app which is treating files as truly asynchronous operations.
Now, the WinRT APIs already force you to code asynchronously against files so why do I say “truly asynchronous operations”?
What I mean by that that is that an app has to think about files potentially coming from anywhere, not necessarily being available and even when they are available taking a long time to bring across a network. I’ll admit that I’ve built Windows Store apps already which handle IO asynchronously but don’t factor in the idea that file IO might take a long time and that a file which seemed like it was available might not actually be available – that’s something that I would rework if I visited those apps again or in creating new apps in the future for Windows 8.1.