Sunday, December 22, 2013

Working with files across the local PC and SkyDrive

Tablet devices, now omnipresent, have enabled new levels of mobility and content creation on the go. Today, consumers want more from their tablets: the ability to access files from any device, and the affordance to do so without running out of disk space on these low-storage devices. As a result, many consumers and developers have turned to cloud storage services to store files and deliver a continuous experience across devices.

With Windows 8.1, SkyDrive is built at the core of Windows, which gives both consumers and developers unprecedented flexibility for achieving these goals. Consumers can easily save and open files on SkyDrive directly from their apps, by using the file picker or adding content to their libraries. Libraries seamlessly aggregate files from the local PC and SkyDrive, which gives apps a common entry point to access all of the user’s content.

This post highlights key guidelines and best practices for apps to make the best use of SkyDrive files they interact with. A SkyDrive-aware app reacts to network connectivity changes and highlights files that are unavailable as it displays the contents of a library. It’s also optimized to be fast, even when it accesses files on the cloud, by taking advantage of file information cached locally. For instance, it leverages cached and streamed thumbnails that avoid large downloads when displaying pictures.

In this post, I’ll show how you can update your app to be SkyDrive-aware quickly, without adding complexity to your existing code. More specifically I’ll walk you through:

  1. Basics of interacting with the user’s files in Windows 8.1.
  2. Overview of SkyDrive integration into the OS and the WinRT data model.
  3. Making sure your app works great both online and offline.
  4. Using thumbnails when displaying images for best performance.

A few reminders

If you’ve read the post I wrote on Creating beautiful views for local files or made apps for Windows 8, you might already be familiar with the methods and guidelines for accessing your users’ files with WinRT APIs. As a brief reminder, there are two main paths to acquire these files within your app: capabilities and the file picker.

  • Library capabilities are used for apps whose core competency is file collections (for instance a photo gallery or music playback app).
  • The file picker (and to some extent the file activation extension) is used for point-in-time interactions with the user’s content.
    • Starting a file flow, like editing an existing file.
    • Exporting content created by your app into the user’s file space.
      • The file save picker can be used for a one-off save.
      • The folder picker can be used to streamline this process if exporting is a common action and you want to preserve access to a specific folder to programmatically export multiple files over time.

The Xbox Music app uses the Music Library capability

Figure 1: The Xbox Music app uses the Music Library capability

The Reader app uses the file picker to open or save files

Figure 2: The Reader app uses the file picker to open or save files

In Windows 8.1, apps that have a Library capability can extend the scope of content they have access to by using the StorageLibrary management APIs. These brokered APIs enable the same functionality as the Library properties dialog in the File Explorer, where users can add and remove folders from their libraries.

Library properties dialog where users can add and remove folders from their libraries

Figure 3: Library properties dialog

This new functionality enables users to point their media collection apps to the folders that contain their photos and music directly within the context of the app. A great example of how to use these APIs is shown in the Music app.

The Music app provides an entry point for users to add more music to their collection

Figure 4: The Music app provides an entry point for users to add more music to their collection

Clicking the "Add" button invokes the library location picker

Figure 5: Clicking the "Add" button invokes the library location picker

This experience is very simple to create using the RequestAddFolderAsync API. This API invokes a location picker in which the user can select a folder to add to the library. The location picker directly surfaces any errors, so your app only needs to handle cancellation. This can all be done with a few lines of JavaScript:

  // Displays the location picker to request the user select a folder to be
// added to the Music library.
function addFolder() {
Windows.Storage.StorageLibrary.getLibraryAsync(Windows.Storage.KnownLibraryId.music).then(function (library) {
return library.requestAddFolderAsync();
}).done(function (folderAdded) {
if (folderAdded !== null) {
// a folder was added to the Music library
} else {
// the operation was canceled
}
});
}

You can also do this in C#:
  private async void AddFolder() 
{
StorageLibrary picturesLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Music);
StorageFolder folderAdded = await musicLibrary.RequestAddFolderAsync();
if (folderAdded != null)
{
// a folder was added to the Music library
}
else
{
// the operation was canceled
}
}
It’s also possible for your app to use files without interacting with the user’s own files and folders – for instance, if your app itself creates files to keep track of content created by the user within the app. These could be standard images, audio files or custom formats for project-like management (using XML for instance). For any such content that exists mostly in the context of your app and wouldn’t be useful to other apps, use the ApplicationData folders instead.

An example of this pattern is the Sound Recorder app. Because a user’s recordings make the most sense in context of the app that recorded them, the app keeps the files within the app data location. This provides a better experience than saving files to the Music library, which exposes them in apps like Xbox Music, where they wouldn’t be expected. However, users might want to share these files with teammates or edit these files in an audio editing app, which are more point-in-time interactions. For these scenarios, use the export mechanisms described above.

The Sound Recorder app keeps all recordings in ApplicationData

Figure 6: The Sound Recorder app keeps all recordings in ApplicationData.

File access and SkyDrive

A major addition to Windows 8.1 is the deep integration of SkyDrive directly in the OS. Users can now store files on SkyDrive just like in any file system folder, and manage their files regardless of network connectivity. The system takes care of syncing the files in the background and keeps the experience completely transparent to users.

SkyDrive is present throughout the system:

  • The SkyDrive app lets users browse and manage files both online and offline.
  • SkyDrive is now a built-in file picker scope so users can import SkyDrive files in your apps and export them back to SkyDrive.
  • File Explorer lets users interact with SkyDrive like any other folder.

    Managing files in the SkyDrive app

Figure 7: Managing files in the SkyDrive app.

SkyDrive in the file picker

Figure 8: SkyDrive in the file picker.

SkyDrive in File Explorer

Figure 9: SkyDrive in File Explorer.

As a developer, think of SkyDrive files as “just files” – they use the same StorageFile interface as every other file on the system and can be manipulated in much the same way. Under the hood, however, there can be some differences between local files and SkyDrive files. In Windows 8.1, we introduced the concept of smart files. A smart file is a file that appears on the system like a regular file but only contains metadata and thumbnails, not full file contents. The contents for the file are located on SkyDrive and can be downloaded as needed. Smart files help reduce the amount of storage needed to display files from SkyDrive on your device.

This might sound familiar if you’ve used the Windows 8 file picker contract. In this contract, users can open the file picker from App A and select a file from App B. App A receives a StorageFile, but under the cover, this file might be a stream-backed file whose contents are downloaded on the fly from the location where the actual file is stored. Smart files on SkyDrive function in a similar way, where their contents are downloaded transparently when a file is opened. What this means is that you won’t have different code paths for local files and SkyDrive files. However, some file operations such as editing metadata or copying might take a bit longer to perform because they require a download to occur rather than simply accessing bits on disk.

Tip: Desktop apps can also access smart files, as long as they use the APIs that support them. Smart files can be identified by the LocallyIncomplete attribute, which reflects that content is not all available locally. Desktop apps can detect this attribute and should use the IStream interface to manipulate these files. Because many Win32 apps use other APIs that don’t abstract the concept of smart file and risk encountering failures when using these files, the system marks them with the “system hidden” attribute to avoid accidental access.

How and how much to use SkyDrive is a choice that is left to the user’s discretion: in Windows 8.1, access to files on SkyDrive is brokered so that users are always in control. For this reason, there is no purely programmatic way to access to a user’s SkyDrive folder. Rather, your app will most often encounter SkyDrive files because the user selected a file from SkyDrive in the file picker to use in your app, or because a folder from SkyDrive was added to a library that your app can access.

Great experiences online and offline

We’ve seen that in most cases a file on SkyDrive behaves the same as a local file. There are some exceptions to that rule when the user has no internet connectivity. Specifically, when the user is offline, smart files cannot be downloaded on demand, and will exhibit limited functionality. An app that is SkyDrive-aware is able to recognize these scenarios and provide clear feedback to the user in its UI, and retain as much functionality as possible while offline.

The SkyDrive app differentiates offline items but lets users move or delete them

Figure 10: The SkyDrive app differentiates offline items but lets users move or delete them.

The WinRT API provides all the tools for apps to create experiences that are as seamless as possible regardless of network connectivity. A key rule of thumb when dealing with SkyDrive is that you should only have to code one time for all kinds of files, rather than have to differentiate local files and files on SkyDrive. A typical example is file availability: all files on the system expose the IsAvailable property, which lets you know if the file contents can be accessed at the point where the file is retrieved.

Values of the IsAvailable property for different scenarios

Table 1: Values of the IsAvailable property for different scenarios.

The metered connection setting is taken into account by the API

Figure 11: The metered connection setting is taken into account by the API.

Some operations, like those listed next, can be performed for all files regardless of network connectivity. In this case, you don’t have to check the IsAvailable property before performing them:

Other operations fail if the file isn’t available. Before allowing any of these operations to be performed in your app, check the value of IsAvailable, and disable the command for any file that isn’t available. Users get a better experience if your app anticipates issues and provides early indications of that state, and prevents any action that would result in an error. Operations that require a file to be available include:

  • Opening file contents.
  • Editing a file.
  • Copying a file or moving it outside of SkyDrive.

The IsAvailable property does all the legwork of checking if a file is cached locally, if the user is connected and if the network is metered or not, so that you don’t have to worry about any of these factors when you write your code. While this is very convenient, it doesn’t mean that you should stop handling errors! IsAvailable is optimized to always be fast to retrieve rather than always up to date, which means that it’s not a 100% guarantee that a file will be available when you try to access it (for instance, if the user goes offline shortly after you check the property). For this scenario, the ERROR_NO_NETWORK error was added to the WinRT API so your app can tell if a file operation failed for a reason related to connectivity.

You can use IsAvailable as a cue to visually differentiate items that aren’t available in your UI. Use ghosting (such as the 40% opacity filter shown in the SkyDrive app screenshot above) and typography (like a “Currently unavailable” string on the file) to make items stand out. Additionally, any actions that can’t be performed on the file while offline should be shown disabled in the app bar or context menu.

When using file queries, any changes to the IsAvailable property fires the contentschanged event, which gives you an opportunity to refresh your UI with the up-to-date state of each item. Here’s how to do this in JavaScript:

  picker.pickSingleFolderAsync().done(function (folder) {
if (folder) {
// Query the folder.
var query = folder.createFileQuery();
// Make sure to refresh the view when the contentschanged event is fired
var listener = function () {
displayItems(query);
};
query.addEventListener("contentschanged", listener);
// Make sure to unregister the listener when the user is no longer viewing this page
WinJS.Navigation.addEventListener("navigated", function () {
query.removeEventListener("contentschanged", listener);
});
// Display the initial view
displayItems(query);
}
});

function displayItems(query) {
query.getFilesAsync().done(function (files) {
// Display file list with availability.
files.forEach(function (file) {
// Create an entry in the list for the item.
var list = document.getElementById("itemsList");
var listItemElement = document.createElement("li");
listItemElement.textContent = file.name + " (";

// Show if the item is available (SkyDrive items are available when
// online or when they are marked for "always available offline").
if (!file.isAvailable) {
listItemElement.textContent += "not ";
}
listItemElement.textContent += "available)";

list.appendChild(listItemElement);
});
});
}
A similar implementation in C#:
  StorageFile folder = await filePicker.PickSingleFileAsync();
if (folder != null)
{
// Query the folder.
auto query = folder.CreateFileQuery();

// Make sure to refresh the view when the contentschanged event is fired
var eventHandler = new TypedEventHandler<IStorageQueryResultBase, object>( (IStorageQueryResultBase sender, object args) =>
{
Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
displayItems();
});
});
query.ContentsChanged += eventHandler;

// Make sure to unregister the listener when the user is no longer viewing this page
thisPage.OnNavigatedFrom += () =>
{
query.ContentsChanged -= eventHandler;
}

// Display the initial view
displayItems();
}

private async void displayItems()
{
IReadOnlyList<StorageFile> files = await query.GetFilesAsync();
// Display file list with availability
foreach (StorageFile file in files)
{
string text = "File name: " + file.Name + " (";

// Show if the item is available (SkyDrive items are available when
// online or when they are marked for "always available offline")
if (!file.IsAvailable)
{
text += "not ";
}

text += "available)";

print(text);
}
}
Tip: When using a ListView with data binding to a file query, use IsAvailable in your item renderer or template to customize the appearance of offline items. You can see an example of this with the HiloJS app on CodePlex.

There are a few additional things you can do to provide the best experience in your app, regardless of where the files you handle are located:

  • When opening a file, show indeterminate progress in your UI until the operation completes.
    • This indicates that your app is still responsive, even if the file needs to be downloaded over a slow network.
  • Allow users to cancel file operations.
    • Cancellation is useful for operations like opening or copying a file on SkyDrive, in case network connectivity is slow and users change their minds. The WinRT async APIs for file access all support cancellation.

Finally, while we’ve seen earlier that it’s not generally necessary for your app to differentiate SkyDrive files and local files, there may be cases where it’s relevant for your app to differentiate files based on their origin. For instance, you might want to show a user’s cloud items separately from their local items to show that they roam, whereas local items don’t. In this case, you can use the ID of the new Provider property to identify the source of a file or folder. In Windows 8.1, this ID can take one of four values:

  • computer (files on the local PC or connected hardware like USB drives)
  • SkyDrive (files on SkyDrive)
  • network (files on the local network)
  • app (for files picked via the file picker contract)

Tip: A StorageProvider has a fixed ID and a localized DisplayName. Make sure to use the ID for programmatic operations like comparison (ordinal-based and case-insensitive) or filtering, and the DisplayName for UI. In general, also avoid hard-coding IDs because new IDs might become available in the future.

This is how to use this API in JavaScript:

  picker.pickSingleFolderAsync().done(function (folder) {
if (folder) {
// Query the folder.
var query = folder.createFileQuery();
query.getFilesAsync().done(function (files) {
// Display file list with provider.
files.forEach(function (file) {
// Create an entry in the list for the item.
var list = document.getElementById("itemsList");
var listItemElement = document.createElement("li");
listItemElement.textContent = file.name;

// Show the item's provider.
listItemElement.textContent += ": On " + file.provider.displayName;
});
});
}
});
And in C#:
  StorageFolder folder = await picker.PickSingleFolderAsync(); 
if (folder != null)
{
// Query the folder.
auto query = folder.CreateFileQuery();
IReadOnlyList<StorageFile> fileList = await query.GetFilesAsync();

// Display file list with storage provider and availability.
foreach (StorageFile file in fileList)
{
// Create an entry in the list for the item.
string line = file.Name;

// Show the item's provider (This PC, SkyDrive, Network, or Application Content).
line += ": On " + file.Provider.DisplayName;

OutputPanel.Children.Add(CreateLineItemTextBlock(line));
}
}

A new way to display images

Displaying images from the user’s content is a common way for an app to personalize an experience and provide a richer UI. In most cases, apps simply acquire a file and set it as the source of an image element on their canvas. However, this approach can prove inefficient if you only want to display the image, and your app doesn’t modify the file at all. While editing requires all the file’s data to be available, displaying usually only requires a smaller version of the image to represent on screen.

Many cameras now take photos that are much higher resolution than PC displays, so your app could pay the cost of loading several MB of data unnecessarily. These effects are compounded when accessing files over a network, and can have further negative impact on users if the network is metered. Finally, using this approach with smart files could introduce a new level of complexity in your code around when a file can or cannot be displayed.

To simplify this flow, apps should use thumbnails to display images instead of relying on the full file. Windows maintains an extensive cache of file thumbnails at multiple sizes, and has special thumbnails for smart files, including medium thumbnails that are always available and large thumbnails streamed from the cloud. This means that using a thumbnail to represent an image is a sure way to get the best and fastest available image representation of a file regardless of network connectivity.

In Windows 8.1, the maximum thumbnail size supported by the system locally and from the cloud was increased to 1600px, which is enough for a full-screen preview on many form factors. Additionally, a new API was created to return scaled images at any size, so you can use it as a one-stop-shop regardless of the size you want to display. The API leverages all available thumbnails, and falls back to the actual image if thumbnails cannot satisfy the requested size (or the largest thumbnail found if the file is not available). The returned StorageItemThumbnail object allows you to easily determine if any of these fallbacks were used.

Here’s how you can use this API in JavaScript:

  file.getScaledImageAsThumbnailAsync(thumbnailMode, requestedSize, thumbnailOptions).done(function (thumbnail) {
if (thumbnail) {
// An image was returned.
if (thumbnail.returnedSmallerCachedSize) {
// The system was unable to obtain an image at the requested
// size, and returned the largest available thumbnail.
}
// You can easily insert it in an img element.
var image = document.getElementById("myImage");
var url = URL.createObjectUrl(thumbnail, { oneTimeOnly: true });
image.src = url;
}
}, function (error) {
// There was an error returning the thumbnail
});

Similarly in C#:

  using (StorageItemThumbnail thumbnail = await file.GetScaledImageAsThumbnailAsync(thumbnailMode, size, thumbnailOptions)) 
{
if (thumbnail != null)
{
// An image was returned.
if (thumbnail.ReturnedSmallerCachedSize) {
// The system was unable to obtain an image at the requested
// size, and returned the largest available thumbnail.
}
// You can easily insert it in an image element.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(thumbnail);
image.Source = bitmapImage;
}
}
With this API, you now only have to access the original file for editing, which makes sure that your app is as fast as possible and behaves well both online and offline.

Wrapping up

You’ve seen in this post how deeply SkyDrive is integrated in Windows 8.1, and how it blends with the Windows 8 file access model for Windows Store apps. A few new properties make it really easy for your app to become SkyDrive-aware by discovering file availability and origin. Using thumbnails as a proxy for images can also make your app more resilient to changes in connectivity, and enable you to display images faster. Most apps accessing user files can be updated with just a few lines of code, without creating new code paths for files on SkyDrive.

--Marc Wautier, Program Manager, Windows User Experience



via The Windows Blog http://blogs.windows.com/b/