The Story of State, Part 2: User Data, Files, and OneDrive - Microsoft Press Programming Windows Store Apps with HTML, CSS and JavaScript (2014)

Microsoft Press Programming Windows Store Apps with HTML, CSS and JavaScript (2014)

Chapter 11 The Story of State, Part 2: User Data, Files, and OneDrive

Every week I receive an email advertisement from a well-known electronics retailer (I did opt in) that typically highlights items across a variety of categories, like PCs, tablets, TVs, audio equipment, external hard drives, software, home security, and so forth. I’ve found it interesting over the couple of years I’ve been receiving these emails to observe how large a hard drive (or now SSD) you can get for around US$80. This is easy as the retailer seems to highlight items around that price point. I’ve watched how the same US$80 that bought about 320 gigabytes of storage two years ago will now acquire on the order of 2 terabytes or more (and may increase yet further by the time you read this).

What we call user data, a term we defined in Chapter 10, “The Story of State, Part 1,” is the driving force behind the ever-growing need for storage. (Just hand a video camera to a six-year-old and—presto!—you have another gig of video files that you just can’t bring yourself to delete.) The vast majority of what populates storage devices nowadays is all the stuff that we’ll likely haul around with us across app changes, operating system changes, and device changes—or simply keep it away from all such concerns in the cloud. By its nature, user data is generally independent from the apps that create it. Any number of apps can manipulate that data (and associate themselves with the file types in question), and those apps can come and go while the data remains.

At the same time, the more user data expands the more we need great apps to efficiently manage it and present it in meaningful ways. There are many creative ways to present and interact with a user’s pictures, videos, and music, regardless of where those files are stored. The same is true for all other types of data (like designs, drawings, and other documents), because you can easily query a folder to retrieve those files that match all sorts of different criteria, thereby helping the user sort through all their data more easily.

One of the key characteristics of the Windows platform is that the “file system” as it’s presented to the user isn’t merely a local phenomenon: it seamlessly integrates local, removable, network-based, and cloud-based locations, and it can even represent file-like or folder-like entities that other apps generate dynamically. The same is true for apps: the WinRT StorageFolder and StorageFile classes, which we met in Chapter 10 and will explore fully here, insulate you from the details of where such entities are physically located, how they are referenced, and how they are accessed. Two of the most important properties they provide are an availability flag and thumbnail representations, which are the fundamental building blocks of most types of browsing UI.

Speaking of cloud-based storage, we’ll get to know OneDrive (formerly SkyDrive) much more in this chapter. Users with a Microsoft account get cloud storage on OneDrive for free, and the service is deeply integrated into the fiber of Windows as a whole. OneDrive is what hosts all the user’s roaming data (from both apps and the system). Users also automatically see a OneDrive folder on their local system that is transparently synchronized with their cloud storage, and they can elect to maintain offline copies of whatever files and folders they choose (hence the matter of availability).

And speaking of properties, we’ll see how the StorageFile class makes all sorts of additional properties and metadata available for whatever files you’re working with and how the StorageFolder class makes it possible to query against that metadata.

But let’s not get ahead of ourselves by rushing into details any more than you need to rush out with your US$80 to buy more storage. Let’s instead take a step back and see how the different aspects of files, folders, and user data relate.

The Big Picture of User Data

To look at the broad scope of user data, we can ask a few questions of it similar to those we asked of app data in Chapter 10:

• Where does user data live?

• How does an app get to user data?

• What affects and modifies user data?

• How do apps associate themselves with specific user data formats?

Let’s be clear again that user data, by definition, has no dependency on the existence of particular apps and is never part of any app’s state. Lists of files and folders can certainly part of such state, such as recently used files, favorite folder locations, and so forth, but the data in those files is not app state. (Apps use the Windows.Storage.AccessCache to maintain such lists, because it preserves programmatic access permissions for StorageFolder and StorageFile objects across app sessions. We’ll see the details later in this chapter.)

This makes it easy to answer the first question: from an app’s point of view—which is what we care about in a book about building apps!—user data lives anywhere and everywhere outside your app data folders and your package folder. That outside realm again stretches from other parts of the local file system all the way out to the cloud, as illustrated in Figure 11-1.

images

FIGURE 11-1 User data lives anywhere outside the app’s package and app data folders. Access to user data locations happens through APIs that produce StorageFile, StorageFolder, and related objects, which represent files and folders regardless of location. Access to some locations like libraries, local networks, and removable storage are determined through manifest capabilities; the rest are typically accessed through the File Picker API. OneDrive is integrated as part of the local file system, and other apps can provide access to other cloud back-ends.

Windows makes it easy—seamless, really—for users to navigate across all these locations to select the files and folders they care about. This includes items served up by other provider apps (the Sound Recorder is an example), as well as built-in integration with OneDrive.

The right way to think about OneDrive integration is that it’s simply a folder on the local file system with automatic synchronization with the cloud that’s also aware of considerations like connection cost on metered networks. You use it like a local folder, meaning that you can programmatically enumerate the contents of OneDrive folders and get file metadata like thumbnails through the StorageFile object. All this works because Windows automatically maintains local placeholder or “smart” files that contain the metadata but not the file contents. TheStorageFile.isAvailable property tells you whether a file’s contents exist locally, which is helpful for visually distinguishing which files in a gallery view are available offline (a local copy exists) and which are online-only.

Regardless of availability, you can still attempt to open a file and read its contents. Windows will automatically download a local copy of the file as part of the process (if it has connectivity, of course, and if current cost policy on a metered network allows it). This doesn’t complicate the programming model, mind you, because you always have to handle errors even for local files. Anyway, the bottom line is that it’s super-easy to work with OneDrive in an app—other than using availability to style items in your UI, you work with the StorageFolder and StorageFile APIs as you would with any other location. Again, those objects insulate you from having to worry about the underlying details of whatever is providing the item in question—truly convenient!

Using OneDrivedirectly Although OneDrive is directly integrated with Windows, you can always use the service directly through its REST API (as you would do with other cloud storage providers). Details can be found on the OneDrive reference, on the OneDrive core concepts(which includes a list of supported file types), and in the PhotoSky sample. A backgrounder on this and other Windows Live services can also be found on the Building Windows 8 blog post entitled Extending "Windows 8" apps to the cloud with SkyDrive.

This brings us to our second question: how does an app get to an arbitrary user data location in the first place? That is, how does it acquire a StorageFolder or StorageFile object for stuff outside of its app data locations? Where user data is concerned, this happens in only three ways:

• Let the user choose a file or folder through the File Picker UI, invoked through one of three classes inWindows.Storage.Pickers: FolderPicker, FileOpenPicker, and FileSavePicker, each of which is tailored for its particular purpose.

• Acquire a StorageFolder for a known library, folder, or removable storage, or acquire a StorageFile from one of these.

• The user launches a file for which the app has declared an association in its manifest.

Let me be very clear up front that the first option—using the File Picker UI—should always be your first choice if you need to access only a single file at a time. Accessing libraries (and thus declaring the necessary capabilities) is necessary only if you need to enumerate the contents of a library to create a gallery/browsing experience directly in the app. Otherwise the File Pickers do a fabulous job of browsing content across all available locations and don’t require you to declare any capabilities. You can also instruct the pickers to use a particular library as its default location, making the whole process even more seamless for your users.

The reason for channeling access through the pickers is that accessing arbitrary locations requires user consent. That consent is implicit in having the user specifically navigate to a file or folder through a picker UI, and doing it that way is much more natural for most users than showing a long pathname in a message dialog! Furthermore, some files and folders—especially those that aren’t on the file system and those that are generated dynamically—might not even have user-readable names. The pickers, which we’ll see visually in “The File Picker UI” later, provide a friendly, graphical means to this end that apps can also extend through picker providers.

Again, the Windows.Storage.AccessCache API is how you save a StorageFolder or StorageFile object along with the user consent implied by a picker. This is essential to remember. I’ve seen many developers slip into thinking of files and folders in terms of pathnames and just save those strings in their app state. This never preserves access, however, so always think in terms of the StorageFolder and StorageFile abstractions and APIs like the AccessCache that work with them.

When it is appropriate to work with a library directly, the options are quite specific. First you have the Windows.Storage.KnownFoldersobject, which contains StorageFolder objects for the Pictures, Music, and Videos libraries, as well as Removable Storage. Access to each of these requires the declaration of the appropriate capability in your manifest, as shown in Figure 11-2, without which the attempt to retrieve a folder will throw an Access Denied exception. With Removable Storage you must also declare a file type association, also shown in Figure 11-2. (The static methods StorageFolder.-getFolderFromPathAsync and StorageFile.getFileFromPathAsync can access locations with string pathnames if the app has programmatic access through manifest capabilities.)

images

images

FIGURE 11-2 Capabilities related to user data in the manifest editor (left) and the file type association editor (right). The red X on the Declarations tab indicates the minimal required fields for an association.

Where is the Documents library? If you look at the KnownFolders object, you’ll also see that there’s a documentsLibrary property, but there is no Documents capability in the manifest editor. I call this out because the capability was visible in Windows 8 (that is, Visual Studio 2012), and declaring that capability required one or more file type associations as with Removable Storage. The capability still exists in Windows 8.1 but must be added manually by editing the manifest XML. Declaring it will trigger a more extensive (and time-consuming) process when you submit the app to the Windows Store, which includes verifying that you have a company account (not an individual account), verifying an Extended Validation Certificate, and reviewing your written justifications for using the library. In the end, few apps used the capability in Windows 8, and most of those that did were better off using the file pickers in the first place, hence the stringent requirements.

Note also that you call tell the File Picker API to use the Documents library as the default location, in which case it maps to the user’s OneDrive root. A user can also still navigate to their local Documents folder through the pickers if they want, which is the recommended approach for most apps.

Another way to access libraries is through the StorageLibrary objects obtained through the static method Windows.Storage.StorageLibrary.getLibraryAsync.84 The StorageLibrary object is meant for apps that provide a UI through which a user can manage one of their media libraries. It contains a folders property (a vector of StorageFolder objects) and two methods: requestAdd-FolderAsync and requestRemoveFolderAsync. Note that the StorageLibrary object does not give you a StorageFolder for the library’s root, because you can obtain that through KnownFoldersalready.

The other known location is represented by the Windows.Storage.DownloadsFolder object, whose only methods, createFolderAsync and createFileAsync, allow you to create folders and files, respectively (but not open or enumerate existing content). The DownloadsFolder object—having only these two methods—is useful only for apps that download user data files and wouldn’t otherwise prompt the user for a target location. This is all we’ll say of the object in this chapter.

Now that we know how to get to files and folders, we can answer the third question we posed at the beginning of this section: “What affects and modifies user data?” This is something of an open question because user data isn’t tied to any particular app and can thus always be modified independently of those apps. The user can rename, copy, move, and delete files and folders, of course, and can use any number of tools to modify file contents (including old-time methods like copy con from the command prompt!). We’re primarily interested in the answer to this question from an app’s point of view, and here the answer is simple: access to and modification of user data starts with the StorageFolder and StorageFile objects. Through these you can modify metadata, for one, as well as open files to get to their data streams. For the most part, we’ve already seen in Chapter 10 how to get to the data through methods like StorageFile.openAsync and the kinds of things we can do with the resulting streams, buffers, and blobs.

In this chapter, we’ll review a few of those basics and we’ll complete the story with all the other features of these objects. This includes extended properties for media files, enumerating and filtering folder contents with file queries (using the Windows.Storage.Search API), and the additional capabilities offered by the StorageLibrary object, as noted earlier. Of special interest is how to use file metadata like thumbnails to create gallery experiences, which avoids the expensive overhead (in time and memory) of opening files and reading their contents for that purpose.

This brings us to the last question—“How do apps associate themselves with specific user data formats?”—and also the third way an app gets a StorageFile object: through a file association. In this case the answer is again simple: apps declare such associations in their manifests. By doing so, those apps appear in the Windows app selector UI when an otherwise unassigned file is launched, and an app will always be there as an option if the user wants to change the default association through PC Settings > Search and Apps > Defaults > Choose Default Apps by File Type.

Such launching happens through the Windows Explorer on the desktop or programmatically through WinRT APIs in Windows.System.Launcher. In either case, the associated app gets activated with an activation kind of file, and the activation event args will contain the appropriateStorageFile objects. We’ll see the details toward the end of this chapter.

Sidebar: Enterprise File Protection

The Windows.Security.EnterpriseData.FileRevocationManager APIs are an additional set of capabilities with the file system, but they are not covered in this book. These help you manage copy protection for any StorageItem with what is called selective wipe (described in the Security documentation). This accommodates enterprise users who bring their own mobile devices to work and use them to access corporate data. IT departments, of course, want to make sure that such data doesn’t get leaked outside the enterprise environment. Access to files and folders can be granted in a protected manner through FileRevocation-Manager.protectAsync such that they can be revoked remotely through a server-issued command. Revoked files are completely inaccessible even though they still technically exist on the file system.

For use of the API, refer to the File Revocation Manager sample.

Using the File Picker and Access Cache

Although the File Picker doesn’t sound all that glamorous, it’s actually, to my mind, one of the coolest features in Windows. “Wait a minute!” you say, “How can a UI to pick a file or folder be, well, cool!” The reason is that this is the place where the users can browse and select from their entire world of data. That world—as I’ve said several times already—includes locations well beyond what we normally think of as the local file system (local drives, removable drives, and the local network). Those added locations are made available by what are called file picker providers:apps that specifically take a library of data that’s otherwise buried behind a web service, within an app’s own database, or even generated on the fly and make it appear as if it’s part of the local file system.

Think about this for a moment (as I invited you to do way back in Chapter 1, “The Life Story of a Windows Store App”). When you want to work with an image from a photo service like Flickr or Picasa, for example, what do you typically have to do? The first step is to download that file to the local file system within some app that gives you an interface to that service (which might be a web app). Then you can make whatever edits and modifications you want, after which you typically need to upload the file back to the service. Well, that’s not so bad, except that it’s time consuming, it forces you to switch between multiple apps, and eventually it litters your system with a bunch of temporary files, the relationship of which to your online service is quickly forgotten.

Having a file picker provider that can surface such data directly, both for reading and writing, eliminates all those intermediate steps and eliminates the need to switch apps. This means that a provider for a photo service makes it possible for other apps to load, edit, and save online content as if it all existed on the local file system. Consuming apps don’t need to know anything about those other services, and they automatically have access to more services as more provider apps are installed. What’s more, providers can also make data that isn’t normally stored as files appear as though they are. For example, the Sound Recorder app that’s built into Windows is a file picker provider that lets you record a new audio file and return it just as if it had already been present on the file system. All of this gives users a very natural means to flow in and out of data no matter where it’s stored. Like I said, I think this is a very cool feature!

In this section, we’ll first look at the File Picker UI so that we know what’s going to appear when we use the File Picker API in Windows.Storage.Pickers. Then we’ll see the Windows.Storage.Access-Cache API, because it’s in the context of the file picker that you’ll typically be saving file permissions for later sessions.

We’ll look more at the question of providers in Appendix D, “Contract Providers.” Our more immediate concern is how to use these file pickers to obtain a StorageFile or StorageFolder object.

The File Picker UI

When a file picker is invoked, you’ll see a full-screen view like that in Figure 11-3, depending on whether you want single or multiple selection, whether you’re picking files or folders (or a save location), and whether you want only specific file types. In the case of Figure 11-3, the picker is invoked to choose a single image with a thumbnail view (which provides a rich tooltip control when you hover over an item). In a way, the file picker itself is like an app that’s invoked for this purpose, and it’s designed (with a dark gray background) to give full attention to the contents of the files. The pickers also provide semantic zoom capabilities, as you’d expect, and can be invoked in any sized view even down to the 320px minimum, as shown on the right of the figure.

images

FIGURE 11-3 A single-selection file picker on the Pictures library in thumbnail view mode, with a hover tooltip showing for one of the items (the head of the Sphinx) and the selection frame showing on another (the Taj Mahal). The overlay on the right shows the file picker in a narrow 320px view. Bonus points if you can identify the location of the other ruins in the main view on the left! (And if you’re wondering, these are all my own photos.)

In Figure 11-3, the Pictures heading shows the current location of the picker. The Sort By Name drop-down lets you choose other sorting criteria, and the This PC header is also a drop-down list that lets you navigate to different locations, as shown in Figure 11-4, including other areas of the file system (though never protected areas like the Windows folder or Program Files), network locations, and other provider apps. When choosing a picture (left side), notice how the list of apps is filtered to show just those that can provide pictures. When the picker is invoked to choose general files or other types like music (right side), additional apps like the Sound Recorder can appear.

images

FIGURE 11-4 Selecting other picker locations; notice that OneDrive and apps are listed along with file system locations. The picker on the left is invoked to select pictures, so only picture-providing apps appear; the picker on the right is invoked to select any type of file, so additional providers appear.

Choosing another file system or network location navigates there, of course, from which you can browse into other folders. As OneDrive is built into Windows, it’s treated like another network location, as shown in Figure 11-5, and it’s also the default location for the file save picker (controlled through PC Settings > OneDrive > File Storage > Save Documents to OneDrive by Default).

images

FIGURE 11-5 When picking files from OneDrive, cloud storage appears like any other local or network location.

Selecting an app, on the other hand, launches that app through the file picker provider contract. In this case it appears within a system-provided—but app-branded—UI like that shown in Figure 11-6 and Figure 11-7. In these cases the heading reflects the name of the app but also provides the drop-down list that lets you navigate to other picker locations (which is important for multiple selections); the Open and Cancel buttons act as they do for other picker selections. In short, a provider app is just an extension to the File Picker UI, but it’s a very powerful one. And ultimately such an app just returns an appropriate StorageFile object that makes its way back to the original app. There’s quite a lot happening with just a single call to the File Picker API!

images

FIGURE 11-6 The Windows Phone app invoked through the file picker provider contract to select a single image.

images

FIGURE 11-7 The Sound Recorder app invoked through the file picker provider contract. This is what appears after a sound has been recorded, and because the picker is invoked to select multiple files, the user can create and return multiple recordings at one time.

In Figure 11-3, Figure 11-5, and Figure 11-6, the picker is invoked to select a single file. In Figure 11-7, on the other hand, it is invoked to select multiple files, which can again come from the file system, network or cloud locations, or other apps—it doesn’t matter! With multiple selection, selected items are placed into what’s called the basket on the bottom of the screen. You can see this in Figure 11-7 and also in Figure 11-8 (where the picker is using list view mode rather than thumbnails). The purpose of the basket is to let you select items from one location, navigate to a new location through the header drop-down, select a few more, and then navigate to still other locations. In short, the basket holds whatever items you select from whatever locations. The basket in Figure 11-8 contains the sound recording from Figure 11-7, a picture from my phone, a picture from OneDrive, an MP3 file from my Music folder, and a couple videos from the current folder.

images

FIGURE 11-8 The file picker in multiselect mode with the selection basket at the bottom. The picker’s layout here is a “list” view mode (not thumbnails) that’s set independently from the selection mode.

The picker can also be used to select a folder, as shown in Figure 11-9 (provider apps aren’t shown from the heading drop-down in this case), or a save location and filename, as shown in Figure 11-10.

images

FIGURE 11-9 The file picker used to select a folder—notice that the button text changed and the picker shows the contents of the folder.

images

FIGURE 11-10 The file picker used to select a save location (defaulting to OneDrive) and filename (at the bottom). Files that match the specified save type are also shown alongside folders.

The File Picker API

Now that we’ve seen the visual results of the file picker, let’s see how we invoke it from our app code through the API in Windows.Storage.Pickers(assume this namespace unless indicated). All the images we just saw came from the File picker sample, so we’ll use that as the source of our code.

For starters, scenario 1 of the sample, in its pickSinglePhoto function (js/scenario1.js), uses the picker to obtain a single StorageFile for opening (reading and writing):

function pickSinglePhoto() {
   // Create the picker object and set options
   var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
   openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;
   openPicker.suggestedStartLocation =
      Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
 
   // Users expect to have a filtered view of their folders depending on the scenario.
   openPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
 
   // Open the picker for the user to pick a file
   openPicker.pickSingleFileAsync().done(function (file) {
      if (file) {
         // Application now has read/write access to the picked file
      } else {
         // The picker was dismissed with no selected file
      }
   });
}

To invoke the picker, we create an instance of the FileOpenPicker class, configure it, and call its pickSingleFileAsync method. The result of pickSingleFileAsync as delivered to the completed handler is a StorageFile object, which will be nullif the user canceled the picker. Alwayscheck that the picker’s result is not null before taking further action on the file!

With the configuration, here we’re setting the picker’s viewMode to thumbnail (from the PickerViewMode enumeration), resulting in the view in Figure 11-3. The only other possibility here is list, the view shown in Figure 11-8.

We also set the suggestedStartLocation to the picturesLibrary, which is a value from the PickerLocationId enumeration; other possibilities are documentsLibrary (OneDrive or This PC > Documents), computerFolder (meaning This PC on Windows 8.1), desktop, downloads, homeGroup,musicLibrary, and videosLibrary.

No capabilities needed! It’s very important to note that picker locations do not require you to declare any capabilities in your manifest. By using the picker, the user is giving consent for you to access whatever location he or she chooses. If you check the manifest in the File pickers sample, in fact, you’ll see that no capabilities are declared whatsoever and yet you can still navigate anywhere other than protected system folders, including network locations.

OneDrive or Documents folder? If the user has PC Settings > OneDrive > File Storage > Save to OneDrive by Default turned on, the file picker will show OneDrive when you specify the documentsLibrary location (as in Figure 11-10). If the user turns this off, that location will bring up the user’s local Documents folder instead.

The one other property we set is the fileTypeFilter (a vector/array of strings) to indicate the type of files we’re interested in (PNG and JPEG). Beyond that, the FileOpenPicker also has a commitButtonText property, which sets the label of the primary button in the UI (the one that’s not Cancel), and settingsIdentifier, a means to essentially remember different contexts of the file picker. For example, an app might use one identifier for selecting pictures, where the starting location is set to the pictures library and the view mode to thumbnails, and another id for selecting documents with a different location and perhaps a list view mode.

This sample, as you can also see, doesn’t actually do anything with the file once it’s obtained, but it’s quite easy to imagine what we might do. We can, for instance, simply pass the StorageFile to URL.createObjectURL and assign the result to an img.src property for display. The same thing could be done with audio and video, possibilities that are all demonstrated in scenario 1 of the Using a blob to save and load content sample I mentioned in Chapter 10. That sample also shows reading the file contents through the HTML FileReader API alongside the other WinRT and WinJS APIs we’ve seen. You could also transcode an image (or other media) in the StorageFile to another format (as we’ll see in Chapter 13, “Media”), retrieve thumbnails as shown in the File and folder thumbnail sample, or use the StorageFile methods to make a copy in another location, rename the file, and so forth. But from the file picker’s point of view, its particular job was well done!

Returning now to the File pickers sample, picking multiple files is pretty much the same story as shown in the pickMultipleFiles function of scenario 2 (js/scenario2.js). Here we’re using the list view mode and starting off in the documentsLibrary (which goes to either OneDrive or the local Documents folder depending on the user’s choice in PC Settings). Again, these start locations do not require capability declarations in the manifest, which is fortunate here because the Documents library capability has many restrictions on its use!

function pickMultipleFiles() {
   // Create the picker object and set options
   var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
   openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.list;
   openPicker.suggestedStartLocation =
      Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
   openPicker.fileTypeFilter.replaceAll(["*"]);
 
   // Open the picker for the user to pick a file
   openPicker.pickMultipleFilesAsync().done(function (files) {
      if (files.size > 0) {
         // Application now has read/write access to the picked file(s)
      } else {
         // The picker was dismissed with no selected file
      }
   });
}

When picking multiple files, the result of pickMultipleFilesAsync is an array (technically a vector view) of StorageFile objects.

Scenario 3 of the sample shows a call to pickSingleFolderAsync defaulting to the desktop, where the result of the operation is a StorageFolder. Here you must indicate a fileTypeFilter that helps users pick an appropriate location where some files of that type exist or create a new location (js/scenario3.js):

function pickFolder() {
   // Create the picker object and set options
   var folderPicker = new Windows.Storage.Pickers.FolderPicker;
   folderPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.desktop;
   folderPicker.fileTypeFilter.replaceAll([".docx", ".xlsx", ".pptx"]);
 
   folderPicker.pickSingleFolderAsync().then(function (folder) {
      if (folder) {
         // Cache folder so the contents can be accessed at a later time
         Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList
            .addOrReplace("PickedFolderToken", folder);
      } else {
         // The picker was dismissed with no selected file
      }
   });
}

You can see here that we save the folder in the access cache, which we’ll come back to shortly. First let’s look at the final file picker use case in scenario 4, where we use a FileSavePicker object and its pickSaveFileAsync method (js/scenario4.js), resulting in the UI of Figure 11-10(assuming OneDrive is the default save location; I’ve also added the myData variable to illustrate what’s being saved, even though it isn’t in the sample):

function saveFile(myData) {
   // Create the picker object and set options
   var savePicker = new Windows.Storage.Pickers.FileSavePicker();
   savePicker.suggestedStartLocation =
      Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
   // Drop-down of file types the user can save the file as
   savePicker.fileTypeChoices.insert("Plain Text", [".txt"]);
   // Default file name if the user does not type one in or select a file to replace
   savePicker.suggestedFileName = "New Document";
 
   savePicker.pickSaveFileAsync().done(function (file) {
      if (file) {
         // Prevent updates to the remote version of the file until we finish making
         // changes and call CompleteUpdatesAsync.
         Windows.Storage.CachedFileManager.deferUpdates(file);
 
         // write to file
         Windows.Storage.FileIO.writeTextAsync(file, myData).done(function () {
            // Let Windows know that we're finished changing the file so the other app
            // can update the remote version of the file (see Appendix D).
            // Completing updates might require Windows to ask for user input.
            Windows.Storage.CachedFileManager.completeUpdatesAsync(file)
               .done(function (updateStatus) {
                  if (updateStatus ===
                     Windows.Storage.Provider.FileUpdateStatus.complete) {
                  } else {
                     // ...
                  }
               }
            });
         });
      } else {
         // The picker was dismissed
      }
   });
}

The FileSavePicker has many of the same properties as the FileOpenPicker, but it replaces fileTypeFilter with fileTypeChoices (to populate the drop-down list) and includes suggested-FileName (a string), suggestedSaveFile (a StorageFile), and defaultFileExtension (a string).

What’s interesting (and important!) in the code above are the interactions with the Windows.-Storage.CachedFileManager API. This object helps file picker providers know when they should synchronize local and remote files, which is often necessary when a file consumer saves new content. Technically speaking, use of the CachedFileManager API isn’t required—you can just write to the file and be done with it, especially for files you know are local and also for a single write as shown here. However, if you’re doing multiple writes, placing your I/O within calls todeferUpdates and complete-UpdatesAsync methods will make the process more efficient. For more details on the caching mechanism, refer to Appendix D.

Access Cache

As we’ve now seen, the File Picker UI allows users to navigate to and select files and folders in many different locations, and through the File Picker APIs an app gets back the appropriate StorageFile and StorageFolder objects for those selections. By virtue of the user having selected those files and folders, an app has full programmatic access through the StorageFile and StorageFolder APIs as it does for its appdata folders and those libraries declared in its manifest.

This is all well and good within any given app session. But how does an app preserve that same level of access across sessions (that is, when the app is closed and restarted later on, or across reboots)? Furthermore, how does an app save references to such files and folders in its state? Remember that the StorageFile and StorageFolder objects are essentially rich abstractions for pathnames, and they hide the fact that some entities cannot even be represented by a path to begin with because they use URIs with custom schema or some other provider-specific naming convention altogether.

These needs are met by the Windows.Storage.AccessCache API, which saves StorageFile and StorageFolder objects along with their permissions such that you can retrieve those same objects and permissions in subsequent sessions. Simply said, unless you know for certain that your app already has programmatic access to a given item and can definitely be represented by a pathname (which basically means appdata locations), always use the AccessCache API to save file/folder references instead of saving path strings. Otherwise you’ll see Access Denied errors when you try to open the item again.

When you add a storage item to the cache—and I’ll refer now to files and folders as items for convenience unless the distinction is important—what you get back is a string token. You save that token in your app state if you want to get back to a specific item later on, but you can also enumerate the contents of the cache at any time. What’s also very powerful is that for the local file system, at least, the token will continue to provide access to its associated item even if that item is independently moved or renamed. That is, the access cache does its best to keep the tokens connected to their underlying files, but it’s not an exact science. For this reason, if you find an invalid token in the cache, you’ll want to remove it.

The access cache maintains two per-app lists for storage items: a future access list and a recently used list. You get to these through the AccessCache.StorageApplicationPermissions class and its futureAccessList and mostRecentlyUsedList properties (it has no others):

var futureList = Window.Storage.AccessCache.StorageApplicationPermissions.futureAccessList;
var mruList = Window.Storage.AccessCache.StorageApplicationPermissions.mostRecentlyUsedList;

Technically speaking, the two are almost identical; their methods and properties come from the same interface (see table below). You could, in fact, maintain your own recently used list by adding items to the futureAccessList and saving the returned tokens in some collection of your own. But because a recently used list is a common app scenario, the API’s designers decided to save you the trouble. The mostRecentlyUsedList is thus limited to 25 items and automatically removes the oldest items when a new one is added that would exceed that limit. ThemostRecentlyUsedList fires its itemremoved event in this case, which you can use to enumerate the current contents of the list and update your UI as necessary.

The futureAccessList, on the other hand, is there for any and all other items for which you want to preserve access. It has an upper limit of 1000 items and will throw an exception when it’s full, so you have to remove items yourself.

The methods and properties of both lists (which come from the IStorageItemAccessList interface), as are follows:

images

The update to the Here My Am! app we made in Chapter 10 shows some basic use of the access cache. First, here’s how we save the current image’s StorageFile in the futureAccessList and save its token in the app’s sessionState (pages/home/home.js):

var list = Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList;
 
if (app.sessionState.fileToken) {
   list.addOrReplace(app.sessionState.fileToken, newFile);
} else {
   app.sessionState.fileToken = list.add(newFile);
}

Notice how we use the list’s addOrReplace method if we already have a token from a previous session; otherwise we add the item anew and save that token. I will say that when I first wrote this code, I didn’t realize that addOrReplace does not return the same token you pass in, and I was assigning undefined to my fileToken variable. Such an assignment is unnecessary because I already have that token in hand.

Anyway, if the app is suspended and then terminated, we check for the token during activation and attempt to retrieve its StorageFile, which we use to rehydrate the image element (also in pages/home/home.js):

if (app.sessionState.fileToken) {
   var list = Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList;
 
   list.getFileAsync(app.sessionState.fileToken).done(function (file) {
      if (file != null) {
         lastCapture = file;
         var uri = URL.createObjectURL(file);
 
         var img = document.getElementById("photoImg");
         img.src = uri;
         scaleImageToFit(img, document.getElementById("photo"), file);
      }
  });
}

Scenario 7 of the File access sample has additional demonstrations, letting you choose which list to work with. It shows that you can enumerate the entries collection of either list. As above, entries is an AccessListEntryView with a size property and first, getAt, getMany, and indexOfmethods (basically a derivative of a vector view, which we saw in Chapter 6, “Data Binding, Templates, and Collections”). This type is projected into JavaScript as an array, so you can also use the [ ] operator and methods like forEach as scenario 7 of the sample shows (js/scenario7.hs):

var mruEntries =
   Windows.Storage.AccessCache.StorageApplicationPermissions.mostRecentlyUsedList.entries;
if (mruEntries.size > 0) {
   var mruOutputText = "The MRU list contains the following item(s):<br /><br />";
   mruEntries.forEach(function (entry) {
      mruOutputText += entry.metadata + "<br />";
   });
   outputDiv.innerHTML = mruOutputText;
}

Each entry in the AccessListEntryView is a simple AccessListEntry object that contains the item’s token and a metadata property with the string you can include when adding an item to the list.

StorageFile Properties and Metadata

In Chapter 10 (in “Folders, Files, and Streams”) we began looking at the many methods and properties of the StorageFile object, limiting ourselves to the basics because many of its features apply primarily to user data files rather than those you’ll create for your app state. Now we’re ready to delve into all its details, a topic that will take us quite deep down a few rabbit holes!

Note Many of the properties and methods described here for StorageFile also apply to StorageFolder object, but for convenience we’ll focus on StorageFile.

Let’s just assume that you’ve obtained a StorageFile object of interest through some means, be it a file picker, a media library, a file activation, one of the static StorageFile methods like getFile-FromPathAsync or replaceWithStreamedFileAsync, and so on. As we’ve seen in Chapter 10, you can open the file (obtaining a stream) through openAsync, openReadAsync, openSequentialReadAsync, and openTransactedWriteAsync. You can also manage the file on the file system (as you would through Windows Explorer) with copyAsync, copyAndReplaceAsync, deleteAsync, moveAsync,moveAndReplaceAsync, and renameAsync. And the purpose of many other properties and methods, listed below, should be quite apparent from their descriptions, so we won’t cover them here. Refer to the StorageFile reference and the File access sample for all that.

images

What’s left are just two properties and three methods that are among the most important to understand:

• The isAvailable property (described next in “Availability”)

• The getThumbnailAsync and getScaledImageAsThumbnailAsync methods (described in “Thumbnails”)

• The StorageFile.properties property and the getBasicPropertiesAsync method (described in “File Properties”)

Availability

First is the isAvailable property that I mentioned earlier. When working with files that might originate in the cloud, it’s generally unimportant for an app to know where it came from, how it might be downloaded, and so forth, because the provider that’s behind the StorageFile can take care of all that transparently like Windows does for OneDrive. What you primarily need to know in an app is whether you can expect to open that file and get to its contents. The simple isAvailable flag (a Boolean) tells you that and relieves you from the burden of having to check network connectivity yourself. This is a big reason why placeholder files are often referred to as “smart” files!

The following table (thanks to Marc Wautier) indicates the different conditions that will set this flag to true or false:

images

For a metered network, the user settings are found in PC Settings > OneDrive > Metered Connections, as shown below. These are simple on/off settings regardless of file size.

images

Thumbnails

Next we have the getThumbnailAsync and getScaledImageAsThumbnailAsync methods. What’s very important about these is that they help you more efficiently create gallery or browsing experiences in apps without having to manually open files and generate your own image from its contents. For one thing, this is somewhat difficult to do if the file itself is not already an image. In addition, it’s horribly inefficient to open an image file as a whole just to generate a thumbnail.

For example, let’s say you just want to show the images in the user’s Pictures library. You can easily obtain its StorageFolder, enumerate all the StorageFile objects therein, pass each one to URL.createObjectURL, and store the result in a WinJS.Binding.List that you then provide to a ListView. Simple, yes! But—ouch!—take a look at the app in a memory profiler when your Pictures library contains a few hundred multi-megabyte images and you’ll be wishing there was another way to do it. Run the app through other performance analyzers and you’ll see that most of the time in your app is spent chewing on a bunch of potentially large images just to create small representations on the order of 150x150 pixels. This is one case where the obvious approach definitely does not yield the best results!

By using thumbnails, on the other hand, you take advantage of pre-cached image metadata, which also works for nonimage files (and folders) automatically, so you never need to make the distinction. Furthermore, thumbnails generally work for files even when isAvailable is false(unless there’s simply no cached data), whereas the URL.createObjectURL approach—which opens the files and reads its contents!—will necessitate a download and thus also fail outright when isAvailable isn’t set, regardless of existing caches.

Think also about your own pictures library and the typical images you probably have from your camera or phone. What are the native image resolutions? Many of mine are in the 2048x1536 or 3700x2400 range, and that’s because I’m not too concerned with really great image quality. If you’re a pixel junkie and have one of those 16+ megapixel cameras, your files are likely much larger. Now compare those sizes to your display resolutions—I have two 24” monitors in front of me right now, whose resolution is only 1920x1200. What this means is that most of the time, the images you display in an app—even when full screen—are essentially thumbnails of the original!

For the sake of memory efficiency and performance, then, the only time you should ever be loading up a full image file is when you need to display the raw pixels in a 1:1 mapping on the display, as when you’re editing the image. Otherwise, essentially for all consumption scenarios, use a thumbnail. (Remember that we did this to Here My Am! way back in Chapter 2, “Quickstart,” after we initially used URL.createObjectURL. The Hilo sample app from Microsoft’s Patterns & Practices group also used thumbnails exclusively.)

The two thumbnail methods, getThumbnailAsync and getScaledImageAsThumbnailAsync, both accomplish the same ends (a StorageItemThumbnail object). The difference between them is that the first always draws from a thumbnail cache, whereas the latter will go to the full file image as a fallback. For this reason, getScaledImageAsThumbnailAsync is primarily used for obtaining a large thumbnail—even one that’s full screen—although you can also set it to use only cached images as well.

Both methods actually have three variants to accommodate some optional arguments. In all cases the required argument is a value from the ThumbnailMode enumeration that indicates the type of thumbnail you want (based on intended use) and the default size:

images

For in-depth discussion of these options with many examples, refer to Guidelinesfor thumbnails in the documentation.

The second optional argument is called requestedSize, an integer that indicates the pixel length of the thumbnail’s longest edge (width for images that are more wide than tall, height for those that are more tall than wide). By default, this does not guarantee that the returned image will be exactly this size. Instead, the APIs use this to determine how best to use existing cached thumbnails, so you might get back an image that is larger or smaller.

Generally speaking, it’s best to set requestedSize to one of the sizes that the system already caches. For the square aspect views in the table above, these sizes are 16, 32, 48, 96, 256, 1024, and 1600. (I don’t honestly know why the defaults are 40x40, but there you are.) For wide aspects, the sizes are 190, 266, 342, 532, and 1026. You’ll find these described on the topic Accessing the file system efficiently, whose first section is on thumbnails.

The third optional argument is one or more ThumbnailOptions values combined with the bitwise OR operator (|). These options affect the speed of the request and the resulting image quality:

none Use default behavior.

resizeThumbnail Scale the thumbnail to match your requestedSize.

useCurrentScale Increases requestedSize based on the pixel density of the display (that is, the scaling factor; see Chapter 8, “Layout and Views”).

returnOnlyIfCached Forces the API to fail if a thumbnail does not exist in the cache nor in the file itself. This prevents opening the full image file and potential downloads.

You can see some of these variations in action through the File and folder thumbnail sample. Most of the scenarios (1–5) use getThumbnailAsync for different libraries and ThumbnailMode settings and other options. In scenario 1, for example, you can choose from the picturesView,listView, and singleItem view mode (which is in the modes[modeselected] variable in the code below), toggle whether returnOnlyIfCached is set, and then choose an image for which to generate a ~200px thumbnail (js/scenario1.js):

var requestedSize = 200,
   thumbnailMode = modes[modeSelected],
   thumbnailOptions = Windows.Storage.FileProperties.ThumbnailOptions.useCurrentScale;
 
if (isFastSelected) {
   thumbnailOptions |= Windows.Storage.FileProperties.ThumbnailOptions.returnOnlyIfCached;
}
 
// File picker code omitted--choice is returned in 'file'
// Can also use getScaledImageAsThumbnailAsync here
file.getThumbnailAsync(thumbnailMode, requestedSize,
    thumbnailOptions).done(function (thumbnail) {
   if (thumbnail) {
      outputResult(file, thumbnail, modeNames[modeSelected], requestedSize);
   }
 
   // Error handling code omitted
});
 
function outputResult(item, thumbnailImage, thumbnailMode, requestedSize) {
   document.getElementById("picture-thumb-imageHolder").src =
      URL.createObjectURL(thumbnailImage, { oneTimeOnly: true });
 
   // Close the thumbnail stream once the image is loaded
   document.getElementById("picture-thumb-imageHolder").onload = function () {
      thumbnailImage.close();
   };
   document.getElementById("picture-thumb-modeName").innerText = thumbnailMode;
   document.getElementById("picture-thumb-fileName").innerText = "File used: " + item.name;
   document.getElementById("picture-thumb-requestedSize").innerText =
      "Requested size: " + requestedSize;
   document.getElementById("picture-thumb-returnedSize").innerText = "Returned size: "
      + thumbnailImage.originalWidth + "x" + thumbnailImage.originalHeight;
}

The output for each of the three modes (using the same original image) is as follows:

images

You can see how the picturesView and listView modes automatically crop the image to maintain the aspect ratio implied by that mode. The singleItem mode, on the other hand, uses the original aspect ratio, so we see a full representation of the original (portrait) image.

Scenarios 2–5 are all variations on this same theme, showing, for example, how you get an icon thumbnail for something like an Excel document when using the documentsView mode. The one other bit to show is the use of getScaledImageAsThumbnailAsync in scenario 6 (js/scenario6.js), which effectively amounts to replacing getThumbnailAsync with getScaledImageAsThumbnailAsync in the code above. In fact, you can make this change throughout the sample and you’ll see the same results for the most part—you just might get those results back more quickly, which is very helpful when retrieving thumbnails for a ListView.

What we do need to look at a bit more closely is the StorageItemThumbnail object we get as the result of these operations (in the Windows.Storage.FileProperties namespace). This is a rather rich object that contains quite a few methods and properties, because it’s actually a derivative of IRandomAccessStream. The benefit of this is that you can toss this object to our old friend URL.createObjectURL, as shown in the code above, and it works as you expect. Otherwise, the members in which you’re usually most interested (the thumbnail-specific ones) are:

close Always call this when you’re done using the thumbnail. Notice how the sample code calls this once the img element we’re loading with the thumbnail has finished its work.

originalHeight, originalWidth The nonscaled pixel dimensions of the thumbnail.

type A value from ThumbnailType indicating whether it contains a thumbnail image or an icon representation.

returnedSmallerCachedSize A Boolean indicating whether the returned thumbnail came from a cache with a size smaller than the requestedSize.

For the rest, refer to the StorageItemThumbnail documentation.

Tip As we saw in Chapter 7, “Collection Controls,” the WinJS.UI.StorageDataSource object simplifies many of the details of setting up file queries over various libraries for use with a ListView control. Its loadThumbnail method also encapsulates many of the details we’ve seen here to help you easily load up a thumbnail for items in the collection. Refer to “A FlipView Using the Pictures Library” in Chapter 7 for more.

File Properties

Last but certainly not least is the StorageFile.properties property (say that ten times fast!), along with the getBasicPropertiesAsync method. To put it mildly, these are just the first of many doors that open up all kinds of deep information about files and media file in particular, as illustrated in Figure 11-11, along with the other direct properties and thumbnails. As you can see, alongside the direct properties of StorageFile, some extended properties are retrieved through getBasicPropertiesAsync and the media-specific methods of the properties object.

images

FIGURE 11-1 Relationships between the StorageFile object and metadata objects.

First, all of classes in Figure 11-11 come from the Windows.Storage.FileProperties namespace (except for StorageFile itself), so assume that is our context unless otherwise noted.

BasicProperties is the first one of interest, as we’ve already seen thumbnails in the previous section. This is what we get from StorageFile.getBasicPropertiesAsync, and it provides just three properties: dateModified is the last modified date to complement StorageFile.dateCreated, sizeis the size of the file, and itemDate contains the system’s best attempt to find a relevant date for the file’s contents based on other properties. For example, the relevant date for a picture or video is when it was taken; for music it’s the release date. This is actually quite convenient because it relieves you from having to implement similar heuristics of your own and helps promote consistency across apps.

“But still,” you might be saying to yourself, “all this a snoozer! An async call just to get an object with three properties?” Yes, it looks that way until you see that little retrievePropertiesAsync method—but let’s come back to that in a moment because it shows up all over the place (as you can see in Figure 11-11) and is accompanied by another ubiquitous method, savePropertiesAsync (not shown in the figure).

StorageFile.properties contains a StorageItemContentProperties object that interestingly enough contains no direct properties! It only contains six methods—four of these retrieve media-specific properties, which will be described soon and which are especially helpful when creating gallery experiences over the user’s media libraries or some other arbitrary folder. The other two methods are our friends retrievePropertiesAsync and savePropertiesAsync (through which you can access the same properties as the media-specific methods).

Every retrievePropertiesAsync method is capable of retrieving an array of name-value pairs for all kinds of other metadata related to files. The only argument you provide is an array of the property names you want where each name is a string that comes from a very extensive list ofWindows Properties, such as System.FileOwner and System.FileAttributes. (Be aware that many of the listed properties don’t apply to file system entities like System.Devices.BatteryLife.)

Tip To access the most common property names as strings, use the properties of the Windows.Storage.StorageProperties object, which has the benefit of providing auto-complete within Visual Studio.

An example of this is found in scenario 6 of the File access sample, which employs both getBasicPropertiesAsync and retrievePropertiesAsync to show the basic properties along with the last access date and the file owner (js/scenario6.js, where file is StorageFile):

var dateAccessedProperty = "System.DateAccessed";
var fileOwnerProperty    = "System.FileOwner";
 
file.getBasicPropertiesAsync().then(function (basicProperties) {
   outputDiv.innerHTML += "Size: " + basicProperties.size + " bytes<br />";
   outputDiv.innerHTML += "Date modified: " + basicProperties.dateModified + "<br />";
 
   // Get extra properties
   returnfile.properties.retrievePropertiesAsync([fileOwnerProperty, dateAccessedProperty]);
}).done(function (extraProperties) {
   var propValue = extraProperties[dateAccessedProperty];
   if (propValue !== null) {
       outputDiv.innerHTML += "Date accessed: " + propValue + "<br />";
   }
   propValue = extraProperties[fileOwnerProperty];
   if (propValue !== null) {
       outputDiv.innerHTML += "File owner: " + propValue;
   }
}

The result of retrievePropertiesAsync, in the extraProperties variable in the code above, is a simple Map collection. We met maps back in Chapter 6, in “Maps and Property Sets.” A key point about a map is that you can access its members with the [ ] operator, as we see above, but a map is not an array and does not have array methods. If you want to iterate over a map, the best way is to pass the map to Object.keys and loop over that array. For example, the specific lookup code above could be replaced with the following:

Object.keys(extraProperties).forEach(function (key) {
   outputDiv.innerHTML += key + ": " + extraProperties[key] + "<br/>";
});

where you could use a separate map object to look up UI labels for the property name in key, of course.

Note If a requested property is not available on the file, retrievePropertiesAsync will not include an entry for it in the map but the map’s size property will still reflect the size of the original input array. For this reason, use Object.keys(<map>).length to determine the actual number of returned properties.

What’s very useful about this is that the map from retrievePropertiesAsync is directly connected to the property story of the underlying file. This means you can modify its contents and add new entries, call savePropertiesAsync (with no arguments), and voila! You’ve just updated those properties on the file. This assumes, of course, that those properties are writeable and supported for the file type in question. If they’re supported but read-only, my experience shows that savePropertiesAsync will ignore them. If they’re not supported, on the other hand, the method will throw an exception with the message “the parameter is incorrect.”

For example, the sample.dat file that is created in the File access sample doesn’t support any writeable properties, so it’s not a good test case. If you use an image file instead (like a JPEG), you can write properties such as System.Keywords or System.Author. (A good way to check what’s writable is to right-click a file of some type in Windows Explorer, select Properties, and look at the Details tab to see what properties can be edited and which ones appear as read-only.)

To show a bit of code, assume that file here points to a JPEG, extraProperties came from a retrievePropertiesAsync call, and we want to add some keywords:

extraProperties.insert("System.Keywords", "sample keyword");
file.properties.savePropertiesAsync().done(function () {
   console.log("success");
});

If we’d made any other changes within extraProperties, those too would be saved. Alternately, we can pass savePropertiesAsync a Windows.Foundation.Collections.PropertySet object with those specific properties we want to set, in this case System.Author:

var propsToAdd = new Windows.Foundation.Collections.PropertySet()
propsToAdd.insert("System.Author", currentAuthor);
file.properties.savePropertiesAsync(propsToAdd).done(function () {
   console.log("success");
});

Here’s another example showing how to make a file read-only through the System.FileAttributes property, where we OR in the value of 1 (FILE_ATTRIBUTE_READONLY in the Win32 file API):

var key = "System.FileAttributes";
var FILE_ATTRIBUTES_READONLY = 1;  //From the Win32 API
var file;
 
//Assign some StorageFile to the file variable
 
file.properties.retrievePropertiesAsync([key]).then(function (props) {
   if (props) {
      props[key] |= FILE_ATTRIBUTES_READONLY;
   } else {
      props = new Windows.Foundation.Collections.PropertySet();
      props.insert(key, FILE_ATTRIBUTES_READONLY);
   }
 
   return file.properties.savePropertiesAsync(props);
}).done(function () {
   //Any other action
});

We’ll see a third example in the next section with an image file and the ImageProperties object.

Tip Avoid calling savePropertiesAsync when another save is still outstanding, such as running the second snippet above while the first has not yet completed. Doing so will throw an exception with the message “A method was called at an unexpected time.” Instead, consolidate your property changes into a single call, or use a promise chain to run the async operations sequentially.

Media-Specific Properties

Alongside the BasicProperties class in Windows.Storage.FileProperties we also have those returned by the StorageFile.properties.get*PropertiesAsync methods: ImageProperties, VideoProperties, MusicProperties, and DocumentProperties. Though we’ve had to dig deep to find these, they each contain deeper treasure troves of information—and I do mean deep! The tables below summarize each of these in turn, and each object contains retrievePropertiesAsync and savePropertiesAsync methods, as we’ve seen, so that you can work with additional properties that aren’t directly surfaced in the media-specific object.

Note that the links at the top of the table identify the most relevant groups of Windows properties.

images

images

images

images

Note The latitude and longitude properties for images and video are double types but contain degrees, minutes, seconds, and a directional reference. The Simple imaging sample (in js/default.js) contains a helper function to extract the components of these values and convert them into a string:

    "convertLatLongToString": function (latLong, isLatitude) {
       var reference;
 
       if (isLatitude) {
          reference = (latLong >= 0) ? "N" : "S";
       } else {
          reference = (latLong >= 0) ? "E" : "W";
       }
 
       latLong = Math.abs(latLong);
       var degrees = Math.floor(latLong);
       var minutes = Math.floor((latLong - degrees) * 60);
       var seconds = ((latLong - degrees - minutes / 60) * 3600).toFixed(2);
 
       return degrees + "°" + minutes + "\'" + seconds + "\"" + reference;
    }

To summarize, the sign of the value indicates direction. A positive value for latitude means North, negative means South; for longitude, positive means East, negative means West. The whole number portion of the value provides the degrees, and the fractional part contains the number of minutes expressed in base 60. Multiplying this value by 60 gives the whole minutes, with the remainder then containing the seconds. Although this floating-point value isn’t all that convenient for UI output, as we see here, it’s what you typically want for coordinate math and talking with various web services.

Speaking of the Simple imaging sample, it’s one of a few samples in the Windows SDK that demonstrate working with these properties. Scenario 1 (js/scenario1.js) provides the most complete demonstration because you can choose an image file and it will load and display various properties, as shown in Figure 11-12. I can verify that the date, camera make/model, and exposure information are all accurate.

images

FIGURE 11-12 Image file properties in the Simple imaging sample, which is panned down to show the whole image and more of the property fields.

The sample’s openHandler method is what retrieves these properties from the file using StorageFile.properties.getImagePropertiesAsync and ImageProperties.retrieve-PropertiesAsync for a couple of additional properties not already in ImageProperties. ThengetImagePropertiesForDisplay coalesces these into a single object used by the sample’s UI. Some lines are omitted in the code shown here:

var ImageProperties = {};
 
function openHandler() {
   // Keep data in-scope across multiple asynchronous methods.
   var file = {};
 
   Helpers.getFileFromOpenPickerAsync().then(function (_file) {
      file = _file;
      return file.properties.getImagePropertiesAsync();
   }).then(function (imageProps) {
      ImageProperties = imageProps;
 
      var requests = [
          "System.Photo.ExposureTime",       // In seconds
          "System.Photo.FNumber"            // F-stop values defined by EXIF spec
      ];
 
      return ImageProperties.retrievePropertiesAsync(requests);
   }).done(function (retrievedProps) {
      // Format the properties into text to display in the UI.
      displayImageUI(file, getImagePropertiesForDisplay(retrievedProps));
   });
}
 
function getImagePropertiesForDisplay(retrievedProps) {
   // If the specified property doesn't exist, its value will be null.
   var orientationText = Helpers.getOrientationString(ImageProperties.orientation);
 
   var exposureText = retrievedProps.lookup("System.Photo.ExposureTime") ?
      retrievedProps.lookup("System.Photo.ExposureTime") * 1000 + " ms" : "";
 
   var fNumberText = retrievedProps.lookup("System.Photo.FNumber") ?
      retrievedProps.lookup("System.Photo.FNumber").toFixed(1) : "";
 
   // Omitted: Code to convert ImageProperties.latitude and ImageProperties.longitude to
   // degrees, minutes, seconds, and direction
 
   return {
      "title": ImageProperties.title,
      "keywords": ImageProperties.keywords, // array of strings
      "rating": ImageProperties.rating, // number
      "dateTaken": ImageProperties.dateTaken,
      "make": ImageProperties.cameraManufacturer,
      "model": ImageProperties.cameraModel,
      "orientation": orientationText,
      // Omitted: lat/long properties
      "exposure": exposureText,
      "fNumber": fNumberText
   };
}

Most of the displayImageUI function to which these properties are passed just copies the data into various controls. It’s good to note again, though, that displaying the picture itself is easily accomplished with our pal, URL.createObjectURL, but as we learned earlier in this chapter, it would be better to avoid loading the whole image file and instead use a thumbnail from StorageFile.-getScaledImageThumbnailAsync. That is, change this line of code in displayImageUI (js/scenario1.js):

id(“outputImage”).src = window.URL.createObjectURL(file, { oneTimeOnly: true });

to the following:

var mode = Windows.Storage.FileProperties.ThumbnailMode.singleItem;
file.getScaledImageAsThumbnailAsync(mode, 500).done(function (thumb) {
   var img = id(“outputImage”);
   img.src = URL.createObjectURL(thumb, { oneTimeOnly: true });
   img.onload = function () {
      thumb.close();
   }
});

A bit more code to write, but definitely more efficient!

For MusicProperties a small example can be found in the Playlist sample and another in the Configure keys for media sample, both of which we’ll see in Chapter 13. The latter especially shows how to use the music properties to obtain album art. As for VideoProperties and Document-Properties, the SDK doesn’t have samples for these, but working with them follows the same pattern as shown above for ImageProperties.

As for saving properties, the Simple Imaging sample delivers there as well, also in scenario 1. As the fields shown earlier in Figure 11-12 are editable, the sample provides an Apply button (panned off the top of the screen) that invokes the applyHandler function below to write them back to the file (js/scenario1.js):

function applyHandler() {
   ImageProperties.title = id("propertiesTitle").value;
 
   // Keywords are stored as an array of strings. Split the textarea text by newlines.
   ImageProperties.keywords.clear();
   if (id("propertiesKeywords").value !== "") {
      var keywordsArray = id("propertiesKeywords").value.split("\n");
 
      keywordsArray.forEach(function (keyword) {
         ImageProperties.keywords.append(keyword);
      });
   }
 
   var properties = new Windows.Foundation.Collections.PropertySet();
 
   // When writing the rating, use the "System.Rating" property key.
   // ImageProperties.rating does not handle setting the value to 0 (no stars/unrated).
   properties.insert("System.Rating", Helpers.convertStarsToSystemRating(
      id("propertiesRatingControl").winControl.userRating
      ));
 
   // Code omitted: convert discrete latitude/longitude values from the UI into the
   // appropriate forms needed for the properties, and do some validation; the end result
   // is to store these in the properties list
   properties.insert("System.GPS.LatitudeRef", latitudeRef);
   properties.insert("System.GPS.LongitudeRef", longitudeRef);
   properties.insert("System.GPS.LatitudeNumerator", latNum);
   properties.insert("System.GPS.LongitudeNumerator", longNum);
   properties.insert("System.GPS.LatitudeDenominator", latDen);
   properties.insert("System.GPS.LongitudeDenominator", longDen);
 
   // Write the properties array to the file
   ImageProperties.savePropertiesAsync(properties).done(function () {
      // ...
   }, function (error) {
      // Some error handling as some properties may not be supported by all image formats.
   });
}

A few noteworthy features of this code include the following:

• It separates keywords in the UI control and separately appends each to the keywords vector.

• It creates a new Windows.Foundation.Collections.PropertySet, which is the expected input to savePropertiesAsync. Remember from Chapter 6 that the PropertySet is the only WinRT collection class that you can instantiate directly, as we must do here.

• The Helpers.convertStarsToSystemRating method (also in js/default.js) converts between 1–5 stars, as used in the WinJS.UI.Rating control, to the System.Rating value that uses a 1–99 range. The documentation for System.Rating specifically indicates this mapping.

In general, all the detailed information you want for any particular Windows property can be found on the reference page for that property. Again start at Windows Properties and drill down from there.

Folders and Folder Queries

Now that we’ve seen everything we can do with a StorageFile and file properties, it’s time to turn our attention to the folders and libraries in which those files live and the StorageFolder and StorageLibrary objects that represent them.

As with StorageFile, we’ve already peeked at some of the basic StorageFolder methods in Chapter 10, such as createFileAsync, createFolderAsync, getFileAsync, getFolderAsync, getItemAsync, getFolderFromPathAsync, deleteAsync, and renameAsync. It also has many of the same properties as a file, including name, path, displayName, displayType, attributes, folderRelativeId, dateCreated, provider, and properties. It also shares a number of identical methods: isEqual, isOfType, getThumbnailAsync, getScaledImageAsThumbnailAsync, and getBasicPropertiesAsync. Everything about these is the same as for files, so please refer back to “StorageFile Properties and Metadata” for the details.

Be mindful, though, that the Windows properties you can retrieve and modify on a folder differ from those supported by files. For example, you can use StorageFolder.properties.retrieve-PropertiesAsync for the System.FreeSpace property and you’ll actually get the free space on the drive where the folder lives. Pretty cool, eh?

And here are the unique aspects of StorageFolder:

tryGetItemAsync Identical to getItemAsync (to retrieve a contained item) except that it results in null (to your completed handler) for the most common errors instead of calling your error handler. This can simplify app logic. getItemAsync, on the other hand, will succeed if the item exists invokes your error handler for all error cases. For a simple demonstration, see scenario 11 of the File access sample.

getFilesAsync, getFoldersAsync, and getItemsAsync These are the basic methods to enumerate the immediate contents of a folder depending on whether you want files only, folders only, or both, respectively. Each method results in a vector of the appropriate object type—StorageFile,StorageFolder, or StorageItem—as discussed in “Simple Enumeration and Common Queries” below.

• Any *Query method or property All of these members deal with file queries, which is how you do deep enumerations of folder contents based on Windows properties, drawing on the power of the system indexer. This also includes several overloads of getFilesAsync and getFolders-Async. We’ll talk of all this under both “Simple Enumeration and Common Queries” and “Custom Queries.”

Before that, however, let’s spend a few minutes on the known folders and the StorageLibrary object, as well as working with removable storage—these are special cases of handling containers for files and folders. (The special Windows.Storage.DownloadsFolder mentioned at the beginning of this chapter in “The Big Picture of User Data” doesn’t need any more explanation.)

Sidebar: Indexing App Content

The query capabilities that we’ll be learning about here draw on the system indexer, which works automatically on file system content. Beyond this, the API in Windows.Storage.Search also gives you the ability to easily add Windows properties to files along with the means to index app content that isn’t stored in files at all. Both topics are covered in Chapter 15, “Contracts,” in the discussion on Search, specifically in “Indexing and Searching Content.” For now, just check out the Indexer sample where these features are demonstrated.

KnownFolders and the StorageLibrary Object

As you already know, Windows.Storage.KnownFoldersgives you direct access to the StorageFolder objects for various user data locations. The picturesLibrary, musicLibrary, and videosLibrary are the obvious ones, but we haven’t yet mentioned a number of others, some of which depend on the same capabilities in your manifest. All of them are summarized in the following table:

images

Again, only request specific library access if you’re going to work within any of these libraries outside of the file picker—for example, when you want to create your own UI to show folder contents (which the file pickers do very well already).

As you will rightly expect, some of these folders do not support the creation of new files or folders within them—specifically, homeGroup, mediaServerDevices, and removableDevices—though in the latter you can generally create subfolders within the folder for any one device. As for the rest, they behave just like other local folders. The cameraRoll and savedPictures folders, for their part, are alternate routes into the Pictures library that come from the ongoing work to bring the Windows and Windows Phone platforms together. They don’t otherwise have any special meaning.

With the three primary media libraries (and documents), you also have the ability to programmatically include other folders in those libraries. This is different, mind you, from creating a subfolder in that library directly—a media folder as Windows sees it is a list of any number of other folders on the file system that are then treated as a single entity. You see this in Windows Explorer under Libraries (ignore the legacy Podcasts artifact):

images

In Windows Explorer, if you right-click a folder and select Include In Library, you’ll see a popup menu with these same choices:

images

The Windows.Storage.StorageLibrary object gives you access to these capabilities from within an app. To obtain a StorageLibrary, call the static Windows.Storage.StorageLibrary.-getLibraryAsync method with a value from Windows.Storage.KnownLibraryId enumeration, as shown here in scenario 1 of the Library management sample (js/S1_AddFolder.js):

Windows.Storage.StorageLibrary.getLibraryAsync(Windows.Storage.KnownLibraryId.pictures)
   .then(function (library) {
   // ...
   });
}

where the available options in KnownLibraryId are pictures, music, videos, and documents. Once you have a StorageLibrary object, you have these members to play with:

folders A read-only but observable vector of StorageFolder objects in the library.

saveFolder The StorageFolder of the default save location for the library (read-only).

requestAddFolderAsync Invokes the folder picker UI that automatically shows only those locations eligible to add to the library (excluding other apps, for instance, and removable storage, but it does include OneDrive given that it has a local folder with at least placeholder files). Once the user selects a folder or cancels, the API will complete with the selected StorageFolder or null, respectively.

requestRemoveFolderAsync Prompts the user to confirm removal of a given StorageFolder from the library; the Boolean result indicates whether the user consented to the action.

definitionChanged Fired when the contents of the folders collection have been changed either through Windows Explorer or another app. Uses this to update your UI as needed.

The Library management sample shows all of these features except for saveFolder. To complete scenario 1, it calls requestAddFolderAsync (js/S1_AddFolder.js):

Windows.Storage.StorageLibrary.getLibraryAsync(Windows.Storage.KnownLibraryId.pictures)
   .then(function (library) {
      return library.requestAddFolderAsync();
   }).done(function (folderAdded) {
   // ...
   });
}

Scenario 3 does the opposite with requestRemoveFolderAsync (js/S3_RemoveFolder.js):

var folderToRemove =
   picturesLibrary.folders[document.getElementById("foldersSelect").selectedIndex];
picturesLibrary.requestRemoveFolderAsync(folderToRemove).done(function (folderRemoved) {
   // ...
});

where the foldersSelect control is populated from the folders collection (js/S3_RemoveFolder.js):

function fillSelect() {
   var select = document.getElementById("foldersSelect");
   select.options.length = 0;
   picturesLibrary.folders.forEach(function (folder) {
      var option = document.createElement("option");
      option.textContent = folder.displayName;
      select.appendChild(option);
   });
}

Nearly identical code is found in scenario 2 (js/S2_ListFolders.js), which just outputs the list of current folders to the display.

If you add and remove folders in this sample, also run the built-in Pictures app and you can see the contents of those folders appearing and disappearing. This is because the Pictures app is using the definitionChanged event to keep itself updated. The Library management sample does the same just to refresh its drop-down list in scenario 3 and refreshes its display in scenario 2 (js/S2_ListFolders.js):

Windows.Storage.StorageLibrary.getLibraryAsync(Windows.Storage.KnownLibraryId.pictures)
   .then(function (picturesLibrary) {
      picturesLibrary.addEventListener("definitionchanged", updateListAndHeader);
      updateListAndHeader();// Refresh the display
   });

You can test this by adding and removing folders to the Pictures library in Windows Explorer while the sample is running.

Hint Remember that definitionChanged is a WinRT event, so be sure to remove its listeners appropriately. Note that the Library management sample does not do this properly.

Removable Storage

As with the media libraries, programmatic access to the user’s removable storage devices is controlled by a capability declaration plus one or more file type associations. This means that you cannot simply enumerate the contents of these folders directly or write whatever files you want therein. Put another way, going directly to Windows.Storage.KnownFolders.removableDevices is only something you do when you’re looking for a specific file type. For example, a Photo management app can look here for cameras or USB drives from which to import the image files it supports.

If you simply want to allow the user to open or save files on removable devices, the capability and the removableDevices folder isn’t what you need: use the file picker instead. What you don’t want to do is create a file type association that you don’t support, because users will attempt to launch your app through those associations and will reflect their disappointment in your Store ratings!

Tip One reason you might think about removable storage is to let the user save files to an SD card. In this case, it’s better for the user to directly configure PC Settings > PC and Devices > Devices > Default Save Locations, which is at the very bottom of the Devices page and is easy to overlook.

That said, let’s assume that the capability is appropriate for what you want to do. If you load the Removable storage sample in Visual Studio and run it, you can see the effect of it associating itself with .gif, .jpg, and .png files. As a result, it shows up in Open With lists such as the context menu of Windows Explorer and the default program selector:

images

images

How you create the appropriate declarations and handle file activation is a subject we’ll return to later in “File Activation and Association.” For now, assuming that you’ve done all that properly, your app will be able to get to the removableDevices folder. The sample (scenario 1) just uses the StorageFolder.getFoldersAsync method to list the connected devices. Scenarios 2 and 3 send an image to and retrieve an image from a selected device, where the device names themselves are obtained from APIs in the Windows.Devices namespace, as we’ll see in Chapter 17, “Devices and Printing.” You don’t have to go to that extent, however, because the removableDevices folder already gives you access to the StorageFolder objects you need for the same purposes.

Scenario 4 demonstrates handling Auto Play activation, which we’ll also return to in “File Activation and Association.” In such cases you’ll likely query the contents of the removable device in question to create a list of the files you care about, and for that we need to look at file queries.

Simple Enumeration and Common Queries

A simple or shallow enumeration of folder contents happens through the variants of getFilesAsync, getFoldersAsync, and getItemsAsync in the StorageFolder object, which take no arguments. Each method results in a vector of the appropriate item types: getFilesAsync enumerates files only and provides a vector of StorageFile objects; getFolderAsync enumerates only immediate child folders in a vector of StorageFolder objects; and getItemsAsync provides a vector of both together, each represented by a StorageItem object. Note that getItemsAsync has a variant,getItemsAsync-(<startIndex>, <maxItemsToRetrieve>), with which you can do a partial enumeration. This is especially helpful when dealing with folders that contains a few hundred items or more such that you can bring items into memory only when they come into view.

Once you receive one of the vectors, you can iterate over it as you would an array, as a vector is projected into JavaScript as such. With the StorageFile, StorageFolder, and StorageItem objects you can extract their display names and thumbnails to create a gallery view, present a more compact list to the user, or do whatever else you want. Note that the various permissions and manifest capabilities do not affect enumeration: if you were able to acquire the root StorageFolder, you have programmatic permission to enumerate its contents. (For removable storage and the documents library, however, enumeration is automatically limited to the file types you declare in the manifest.)

The Folder enumeration sample shows us these simple use cases. Scenario 1 calls getItemsAsync on the Pictures library:

picturesLibrary.getItemsAsync().done(function (items) {
   // Output all contents under the library group
   outputItems(group, items);
});

where the outputItems function just iterates the resulting list and creates some DOM elements to show their names (the group variable just the header element with the count):

images

Skipping to scenario 4, we find an example of checking the StorageFile.isAvailable flag for enumerated items. This scenario lets you select a folder through the folder picker and then iterates the results to show the file’s displayName, the provider.displayName, and the file’s availability. Doing this for one of my OneDrive folders, I get the results below left when I have connectivity whereas I see those below right when I’m offline or on a metered network, as described by the table in “Availability” earlier, because those files are marked online-only:

images

I skipped ahead to scenario 4 first because it gives us our first taste of file and folder queries, specifically the function StorageFolder.createFileQuery, which does exactly the same thing as getItemsAsync when called with no arguments:

var query = folder.createFileQuery();
query.getFilesAsync().done(function (files) {
   files.forEach(function (file) {
   // ...
});

What comes back from createFileQuery is now a different object, a StorageFileQueryResult (in the Windows.Storage.Search namespace). Two parallel methods, createFolderQuery and createItemQuery, return a StorageFolderQueryResult and a StorageItemQueryResult, respectively.

As you can see in the code above, the StorageFileQueryResult has a getFilesAsync that behaves exactly like its namesake in StorageFolder (resulting in a StorageFile vector). Storage-FolderQueryResult, for its part, has a getFoldersAsync (resulting in a StorageFolder vector) andStorageItemQueryResult has a getItemsAsync. And all these methods have variants to also return an index-based subset of results instead of the whole smash.

The three objects then have the rest of their members in common:

folder A property containing the root StorageFolder in which the query was run.

getItemCountAsync Retrieves the number of results from the query.

contentsChanged An event that’s fired when changes to the file system affect query results. This prompts an app displaying those contents to refresh itself. (If an app is suspended when this happens, all changes that take place during suspension are consolidated into a single event.)

getCurrentQueryOptions, applyNewQueryOptions Retrieves and modifies the QueryOptions object used to define the query. Calling applyNewQueryOptions fires the optionsChanged event. See “Custom Queries” later on for using query options.

findStartIndexAsync and getMatchingPropertiesWithRanges Used to identify exactly where matches in a query are located; also described in “Custom Queries.”

So now our enumerations start to become more interesting! The whole idea of a query, after all, is to retrieve a subset of items based on certain criteria. Using the full query API that we’ll see in the next section, the sky is really the limit here! But the designers of the WinRT API also understood that certain queries are the ones that most apps will care about—including deep rather than shallow queries—so they made is easy to execute them without having to ponder the fine details.

You do this through variants of the createFileQuery and createFolderQuery methods (no variant exists for items):

• createFileQuery(CommonFileQuery) Takes a value from the CommonFileQuery enumeration (in Windows.Storage.Search).

• createFolderQuery(CommonFolderQuery) Takes a value from the CommonFolderQuery enumeration (also in Windows.Storage.Search).

Both support an option called defaultQuery, which returns the same vector as getFilesAsync() and getFoldersAsync(), respectively, using a shallow enumeration. All other options, shown in the tables below, perform a deep enumeration. Furthermore, all queries can be run in any library or HomeGroup folder; CommonFileQuery.orderByName and orderBySearchRank can also be run in any folder. Refer also to Windows Properties for documentation on System.ItemNameDisplay and so forth.

images

images

Clearly, many of the common folder queries (and one common file query) apply to music specifically, but others apply generally. However, not every location supports every type of file query—those that don’t will throw a “parameter is incorrect” exception, which can be confusing if you’re not prepared for it! (Query support is related to the indexing status of the location in question; libraries are indexed by default.)

The way you check for support is through StorageFolder.isCommonFileQuerySupported and isCommonFolderQuerySupported, to which you pass the desired CommonFileQuery and Common-FolderQuery value you want to test.

To help you play with this, I’ve modified scenario 4 of the Folder enumeration sample that’s in this chapter’s companion content. I’ve added a drop-down list through which you can select which CommonFolderQuery to run; once you’ve chosen a folder, it also checks if the query is supported:

var select = document.getElementById("selectQuery");
var selectedQuery = queries[select.selectedIndex];
 
if (!folder.isCommonFileQuerySupported(selectedQuery)) {
   //Show message in output if not supported and return.
   return;
}
 
//Run query as usual

Assuming the query is supported and succeeds when run, the result is just a flat list of files (a StorageFile vector) that you can easily iterate.

Folder queries are a little more complicated because the StorageFolderQueryResult.get-FoldersAsync method gives you a StorageFolder vector, where each one is a virtual folder that’s used to group files according to the query. That is, each folder does not represent an actual folder on whatever location was queried but is simply used to organize the results and help you present them in your UI. The displayName and other properties of each StorageFolder can be used to create group headings, and the files within each folder that you enumerate withStorageFolder.getFilesAsync are that group’s contents.

Scenario 2 of the Folder enumeration sample demonstrates this with groupByMonth, groupByTag, and groupByRating queries on the Pictures library. It runs each query as follows, using the same processing code for the results (js/scenario2.js; I’ve shortened one variable name for brevity):

var pix = Windows.Storage.KnownFolders.picturesLibrary;
var query = pix.createFolderQuery(Windows.Storage.Search.CommonFolderQuery.groupByTag);
query.getFoldersAsync().done(outputFoldersAndFiles);

The query variable, to be clear, is the StorageFolderQueryResult object, and calling its getFolderAsync is what performs the actual enumeration. The outputFoldersAndFiles function—which is a completed handler—receives and iterates the resulting StorageFolder vectors, callinggetFilesAsync for each and joining the resulting promises. It then processes each folder’s results when they’re all ready to show the output (js/scenario2.js):

function outputFoldersAndFiles(folders) {
   // Add all file retrieval promises to an array of promises
   var promises = folders.map(function (folder) {
      return folder.getFilesAsync();
   });
   // Aggregate the results of multiple asynchronous operations
   // so that they are returned after all are completed. This
   // ensures that all groups are displayed in order.
   WinJS.Promise.join(promises).done(function (folderContents) {
      for (var i in folderContents) {
         // Create a group for each of the file groups
         var group = outputResultGroup(folders.getAt(i).name);
         // Add the group items in the group
         outputItems(group, folderContents[i]);
      }
   });
}

This is a marvelously concise piece of code, so let me explain what’s happening. First, folders is the StorageFolder vector, and because we can treat it as an array we can use folders.map to execute a function for each folder in the vector. The promises variable is an array of promises, one for each folder’s getFilesAsync. Passing this to WinJS.Promise.join, if you remember from Chapter 3, “App Anatomy and Performance Fundamentals,” returns a promise that is fulfilled when all the promises in the array are fulfilled, so this effectively waits for each one to complete.

The completed handler for join then receives an array that contains all the results of the individual promises. This handler then simply iterates that array, calling the sample’s outputResultGroup and outputItems methods for each set, which just build up a DOM tree for the display. The results for my pictures library with groupByTag are as follows (revealing a small extent of my travels!):

images

As for scenario 3 of the sample, that brings us into the matter of query options, which are discussed next.

Custom Queries

Now that we’ve seen the basics of enumerating folder contents with common queries and processing their results, we’re ready to look at the full query capabilities offered by WinRT wherein you construct a custom query from scratch using a QueryOptions object. (The common queries are just shortcuts for typical cases to save you the trouble.) A custom query basically gives you all the capabilities that you have through the search features of the desktop Windows Explorer, using whatever Windows Properties you require, especially those you use to index your own app content.

As with the common queries, not all locations support custom queries, so once you’ve built the QueryOptions you want to call StorageFolder.areQueryOptionsSupported with that object. It if returns true, you can proceed. If you attempt to query a folder with unsupported options, expect a “parameter is incorrect” exception.

Something else you might want to know ahead of time is whether the folder itself has been indexed, which greatly affects the potential speed of the query. You do this through StorageFolder.-getIndexedStateAsync. This results in an IndexedState value: notIndexed, partiallyIndexed,fullyIndexed, and unknown. Do note that a fully indexed folder could yet contain nonindexed folders, but generally speaking an indexed folder will produce results more quickly than the others.

The next step is to create the query by passing your query options to one of these StorageFolder methods: createFileQueryWithOptions, createFolderQueryWithOptions, and createItem-QueryWithOptions. These result in StorageFileQueryResult, StorageFolderQueryResult, andStorageItemQueryResult objects, as already discussed. With these you can enumerate the results however you need, just as you do when you use a common query.

If you remember from earlier, the getCurrentQueryOptions and applyNewQueryOptions methods of Storage[File | Folder | Item]QueryResult work with the QueryOptions that were used to create the query. The get method retrieves the QueryOptions object, obviously, and calling theapply method will change the query mid-flight, firing the query result’s optionsChanged event.

Ultimately, these queries involve what are known as Advanced Query Syntax (AQS) strings that are capable of identifying and describing as many specific criteria you desire. Each criteria is a Windows Property (again see Windows properties for the reference) such as System.ItemDate,System.Author, System.Keywords, System.Photo.LightSource, and so on.85 Each property can contain a target value such as System.Author(Patrick OR Bob) and System.ItemType: "mp3", and terms can be combined with AND (which is implicit), OR, and NOT operators, as inSystem.Keywords: “needs review” AND (System.ItemType: “.doc” OR System.ItemType: “.docx”).

Tip The AND, OR, and NOT operators must be in uppercase or else they are interpreted as a keyword. Also note that quotation marks aren’t strictly necessary.

Simply said, an AQS string is exactly what you can type into the search control of Windows Explorer. You can also try out AQS strings through the Programmatic file search sample, which we’ll see in a moment once we look at the QueryOptions object that is so central to this process (it’s where you provide AQS strings).

First, there are three QueryOptions constructors:

new QueryOptions() Creates an uninitialized object.

new QueryOptions(<CommonFolderQuery>) Creates an object pre-initialized from a CommonFolderQuery.

new QueryOptions(<CommonFileQuery>, <file_types>) Creates an object pre-initialized from a CommonFileQuery along with an array of file types—for example, ["*.jpg", "*.jpeg", "*png"]. If you specify null for <file_types>, it’s the same as ["*"].

Once constructed you then set any of the other properties you care about—all types and enumerations referred to below such as FolderDepth are found in Windows.Storage.Search:

images

QueryOptionsmethods You can save a QueryOptions for later use and then reinitialize an instance from that saved state. The QueryOptions.saveToString returns a string representation for the query that you can save in your app data—and remember also to save the StorageFolder in the access cache! Later, when you want to reload the options, use QueryOptions.loadFromString. These methods are, in short, analogs to JSON.stringify and JSON.parse.

QueryOptions has two other methods—setPropertyPrefetch and setThumbnailPrefetch—which are discussed in “Metadata Prefetching with Queries.”

Here’s a brief snippet to do a CommonFileQueryorderByTitle file query for MP3s, where we remember to call areQueryOptionsSupported:

var musicLibrary = Windows.Storage.KnownFolders.musicLibrary;
var options = new Windows.Storage.Search.QueryOptions(
   Windows.Storage.Search.CommonFileQuery.orderByTitle, [".mp3"]);
 
if (musicLibrary.areQueryOptionsSupported(options)) {
   var query = musicLibrary.createFileQueryWithOptions(options);
   showResults(query.getFilesAsync());
}

I noted in the table that the application and user filter AQS strings here are combined within the query. What this means is that you can separately manage any filter you want to apply generally for your app (applicationSearchFilter) from user-supplied search terms (userSearchFilter). This way you can enforce some search filters without requiring the user to type them in and without always having to combine strings yourself. It’s also helpful to separate locale-independent and locale-specific properties, as terms in applicationSearchFilter should always use locale-invariant property names (like System.FileName instead of filename) to make sure results come out as expected. For more on this, see Using Advanced Query Syntax Programmatically.

It’s necessary to use one or both of these properties with the CommonFileQuery.orderBySearch-Rank query because text-based searches return ranked results to which this query applies. (The query sorts by System.Search.Rank, System.DateModified, and then System.ItemDisplayName.) Scenario 1 of the Programmatic file search sample shows a bit of this, where it uses this ordering along with the userSearchFilter property, whose value is set to whatever you enter in the sample’s search box (js/scenario1.js):

var musicLibrary = Windows.Storage.KnownFolders.musicLibrary;
var options = new Windows.Storage.Search.QueryOptions(
   Windows.Storage.Search.CommonFileQuery.orderBySearchRank, ["*"]);
options.userSearchFilter = searchFilter;
var fileQuery = musicLibrary.createFileQueryWithOptions(options);

On my machine, where I have a number of songs with “Nightingale” in the title, as well as an album called “Nightingale Lullaby,” a search using the string Nightingale AND System.ItemType: mp3 in the above code gives me results that look like this in the sample (only partial results shown for brevity):86

images

This shows that the search ranking favors songs with “Nightingale” directly in the title but also includes those from an album with that name.

A query like this lets us see the purpose of the other two methods on the Storage[File | Folder | Item]QueryResult objects that we haven’t mentioned yet: findStartIndexAsync and getMatchingPropertiesWithinRanges.

findStartIndexAsync retrieves the StorageFile for the first item in the query results where the text argument you provide matches the first property used in the query. This is a bit tricky. In the sample above, which uses CommonFileQuery.orderBySearchRank, there’s actually not much to compare to. If you use orderByTitle, on the other hand, you can compare against values of the System.Title property. For instance, using this and the same AQS string as before, I get these results:

images

Calling findStartIndexAsync with “Horses” I get the result of 3 (zero-based). I can pass this to the query’s getFilesAsync(index, 1) method to retrieve the StorageFile for that item:

fileQuery.findStartIndexAsync("Horses").done(function (index) {
   console.log("First item with 'Horses' at index: " + index);
 
   if (index != 4294967295) {
      fileQuery.getFilesAsync(index, 1).done(function (files) {
         files &&console.log(files[0].displayName);
      });
   }
});

This prints “- 08 - Horses, Sleeping.mp3” to the console. Note that the return value of 4294967295 is a long integer -1, meaning “no index.”

As for getMatchingPropertiesWithRanges, this works for only one StorageFile at a time and is typically called when going through the query results from getFilesAsync(). It returns a Map of property names, where the values are arrays (vectors) of objects that describe where matches occurred in that property. Each object has startPosition and length properties that pinpoint each location.

For a demonstration, play around with scenario 3 of the Semantic text query sample. (The other two scenarios are for searching in text content, which we’ll mention in Chapter 15, “Contracts.”) It runs a query against the Music library, as we’ve been doing, using theCommonFileQuery.orderBySearch-Rank plus whatever extra AQS string you type in (js/filePropertiesMatches.js):

var musicLibrary = Windows.Storage.KnownFolders.musicLibrary;
var options = new Windows.Storage.Search.QueryOptions(
   Windows.Storage.Search.CommonFileQuery.orderBySearchRank, ["*"]);
options.userSearchFilter = searchFilter;
options.setPropertyPrefetch(
   Windows.Storage.FileProperties.PropertyPrefetchOptions.musicProperties, []);
 
var fileQuery = musicLibrary.createFileQueryWithOptions(options);
 
fileQuery.getFilesAsync().done(function (files) {
   if (files.size > 0) {
      files.forEach(function (file) {
         var searchHits = fileQuery.getMatchingPropertiesWithRanges(file);
 
         // [Output code omitted to highlight System.FileName occurrences]
      });
   }
});

To keep things simple, I just ran a search on “Horses” that results in just the one file, “- 08 - Horses, Sleeping.mp3”. The map that I got back in the searchHits variable contained the following (only one location per property):

images

Clearly, you can see that position 7 (zero-based) in the filename is where “Horses” begins. The System.Title of the track is just “Horses, Sleeping,” so its start position is appropriately 0.

In some cases, as with my “Nightingale” search before, the term might not occur in the filename or title at all. In such cases you’ll see a completely different list of properties in the map, such as System.ItemFolderNameDisplay and System.Music.AlbumTitle.

Metadata Prefetching with Queries

The QueryOptions.setPropertyPrefetch method allows you to indicate a group of file properties that you want to optimize for fast retrieval—they’re accessed through the same APIs as properties are otherwise, but they come back faster. This is very helpful when you’re displaying a collection of files in a ListView, using a custom data source with certain properties from enumerated files. In that case, you’d want to set those up for prefetch so that the control renders faster. Similarly, the setThumbnail-Prefetch method tells Windows what kinds of thumbnails you want to include in the query—again, you can ask for these without setting the prefetch, but they come back faster when you do. This helps you optimize the display of a file collection.

Examples of this can be found in two samples. In scenario 3 of the Semantic text query sample, which we saw at the end of the previous section, it made sure to prefetch the music properties it planned to search (js/filePropertiesMatches.js):

options.setPropertyPrefetch(
   Windows.Storage.FileProperties.PropertyPrefetchOptions.musicProperties, []);

The values affected are determined by the first argument to setPropertyPrefetch, whichcomes from PropertyPrefetchOptions: none, musicProperties, videoProperties, imageProperties, documentProperties, and basicProperties. If something sounds familiar here, it’s because these exactly match the objects returned through methods of the StorageFile.properties object, such as retrieveMusicPropertiesAsync, as discussed in “Media-Specific Properties” earlier.

To that group of properties you can also specify a custom list in the second argument, with each property name in quotes, such as ["System.Copyright", "System.Image.ColorSpace"]; for no custom properties, just pass [ ]. Remember that you can also use strings from theSystemProperties object in Windows.Storage.

This, in fact, is exactly what you see in an example of the API, found in scenario 3 of the Folder enumeration sample that we’ve been looking at already (js/scenario3.js):

var search = Windows.Storage.Search;
var fileProperties = Windows.Storage.FileProperties;
 
// Create query options with common query sort order and file type filter.
var fileTypeFilter = [".jpg", ".png", ".bmp", ".gif"];
var queryOptions = new search.QueryOptions(search.CommonFileQuery.orderByName, fileTypeFilter);
 
// Set up property prefetch - use the PropertyPrefetchOptions for top-level properties
// and an array for additional properties.
var imageProperties = fileProperties.PropertyPrefetchOptions.imageProperties;
var copyrightProperty = "System.Copyright";
var colorSpaceProperty = "System.Image.ColorSpace";
var additionalProperties = [copyrightProperty, colorSpaceProperty];
queryOptions.setPropertyPrefetch(imageProperties, additionalProperties);
 
// Query the Pictures library.
var query = Windows.Storage.KnownFolders.picturesLibrary.
   createFileQueryWithOptions(queryOptions);

setThumbnailPrefetch is similar, where you specify a ThumbnailMode, a requested size, and options, all of which are the same as discussed in “Thumbnails” earlier. The same scenario of the Folder enumeration sample shows a use of this, before the call to createFileQueryWithOptions(js/scenario3.js):

var thumbnailMode = fileProperties.ThumbnailMode.picturesView;
var requestedSize = 190;
var thumbnailOptions = fileProperties.ThumbnailOptions.useCurrentScale;
queryOptions.setThumbnailPrefetch(thumbnailMode, requestedSize, thumbnailOptions);

Clearly, you’d often use a thumbnail prefetch when creating a gallery experience with a ListView control, where you’d typically be using a WinJS.UI.StorageDataSource object as well. In fact, the StorageDataSource has its own options that it uses to automatically set up the appropriate prefetching, in which case you don’t use the QueryOptions directly. We’ll see this in the next section.

Creating Gallery Experiences

In Chapter 7 we learned about the three components of a collection control: a data source, a layout, and templates. One of the most common uses of a collection control—especially the ListView—is to display a gallery of entities in the file system, organized in interesting ways. This section brings together much of what we’ve learned in this chapter where implementing such an experience is concerned:

• Unless you’re working with a library for which a capability exists, you’ll need to have the user choose folders to include in your gallery through the Folder Picker.87 If that set of folders is unlikely to change often, it’s best for the app to have a page where the user manages the included folders—adding new ones from the Folder Picker, and removing existing ones. When the user selects a new folder, be sure to save it in the Windows.Storage.AccessCache so that subsequent app sessions won’t need to ask again.

• Always, always use thumbnails from the StorageFile.getThumbnailAsync and getScaledImageAsThumbnailAsync methods. The thumbnails can be passed directly to URL.createObjectURL to display in img elements in place of the full StorageFile. Using thumbnails will perform much better both in terms of speed (because thumbnails are typically drawn from existing caches) and memory (because you’re not loading the whole image file).

• Differentiate file availability based on the StorageFile.isAvailable flag. You can use this to provide a textual indicator and also “ghost” unavailable items by setting their opacity to something like 40%. Remember that availability means that network conditions are such that the file’s contents cannot be accessed, so you typically want to disable interactivity (like invocation) on unavailable items. Furthermore, some items might be available but are represented by only a local placeholder file. Be sure, then, to show an indeterminate progress control if it takes longer than a second or two to get a file’s contents, and also provide a means to cancel the operation. This gives the user control on slow networks.

• Very often, your gallery will involve some kind of query against the file system to create its data source. Depending on your needs, you can use a data source built on a WinJS.Binding.List, which you populate with the results from your queries. Alternately, you can use theWinJS.UI.StorageDataSource object, which has built-in support for queries (more on this below). The caveat with StorageDataSource is that it doesn’t support grouping.

For a full end-to-end demonstration of these and other patterns, I’ll refer you to the JavaScript Hilo app put together by Microsoft’s Patterns & Practices team. (Note: As of the current preview, the app has not yet been updated to Windows 8.1, but it shows many of these patterns nonetheless.)

With the StorageDataSource in particular, scenario 5 of the StorageDataSource and GetVirtualizedFilesVector sampleoffers a demonstration of working with queries. Back in Chapter 7 we just used one of the shortcuts like this:

myFlipView.itemDataSource = new WinJS.UI.StorageDataSource("Pictures",
   { requestedThumbnailSize: 480 });

The first argument to the constructor is actually a query object of some sort—“Pictures” here is again just a shortcut. But you can create any query you want and use it here. The sample, then, creates a QueryOptions from scratch, setting up its own sorting by System.IsFolder andSystem.ItemName. The purpose of having this query is that for some types of galleries, such as music, the folder hierarchy on the file system is irrelevant and you want to organize by metadata. This is why a query such as CommonFileQuery.orderByMusicProperties returns a flat list of files rather than a folder tree, because you’ll typically group the results by criteria other than folder.

Other options we give to the StorageDataSource set up thumbnails, which are used to call the QueryOption.setThumbnailPrefetch method on our behalf. The result is a data source over a query that should perform well by default (js/scenario2ListView.js; code slightly edited):

function loadListViewControl() {
   // Build datasource from the pictures library
   var library = Windows.Storage.KnownFolders.picturesLibrary;
   var queryOptions = new Windows.Storage.Search.QueryOptions;
 
   // Shallow query to get the file hierarchy
   queryOptions.folderDepth = Windows.Storage.Search.FolderDepth.shallow;
 
   // Order items by type so folders come first
   queryOptions.sortOrder.clear();
   queryOptions.sortOrder.append({ascendingOrder: false, propertyName: "System.IsFolder"});
   queryOptions.sortOrder.append({ascendingOrder: true, propertyName: "System.ItemName"});
   queryOptions.indexerOption =
      Windows.Storage.Search.IndexerOption.useIndexerWhenAvailable;
 
   var fileQuery = library.createItemQueryWithOptions(queryOptions);
   var dataSourceOptions = {
      mode: Windows.Storage.FileProperties.ThumbnailMode.picturesView,
      requestedThumbnailSize: 190,
      thumbnailOptions: Windows.Storage.FileProperties.ThumbnailOptions.none
   };
 
   var dataSource = new WinJS.UI.StorageDataSource(fileQuery, dataSourceOptions);
 
   // Create the ListView using dataSource...
};

Within its storageRenderer function (the item renderer), the sample uses the StorageData-Source.loadThumbnail method to load up the prefetched thumbnails and display them in their img elements.

If you’re interested, you can dig into the WinJS sources (the ui.js file) and see how StorageData-Source works with its queries and sets up an observable collection. Along the way, you’ll run into one more set of WinRT APIs: Windows.Storage.BulkAccess. This was originally created for the StorageDataSource but is now considered deprecated. If you create your own data source or collection control, just use the enumeration and prefetch APIs we’ve already discussed.

File Activation and Association

As noted a number of times already, an app typically obtains StorageFile and StorageFolder objects either from locations where it already has programmatic access or through the picker APIs. But a third option exists: when an app is associated with a particular file type, the user and/or other apps can launch a file or files, which in turn launches an associated app to handle them. In the process, the app that’s activated for this purpose receives those StorageFile objects in its activated handler and is automatically granted full programmatic access. This makes sense if you think about it: if the user activated the file(s) from Windows Explorer, they’ve implicitly given their consent in the process. And for another app to launch a file, it must first get to its StorageFile object, and that happens either through the file picker or some other means that has programmatic access.

Both sides of file activation are demonstrated in the Association launching sample. Let’s start with scenarios 1 and 2 that show the launching part, as that’s simple and straightforward.

To launch a given StorageFile, just call Windows.System.Launcher.launchFileAsync, the results of which is a Boolean indicating whether it was successful (js/launch-file.js):

// file is the StorageFile to launch
Windows.System.Launcher.launchFileAsync(file).done(
   function (success) {
      // success indicates whether the file was launched.
   }
});

Note The launcher blocks any file type that can contain executable code, such as .exe, .msi, .js, .etc.

Alternately, you can launch a URI by using the launchUriAsync method, which is typically used to launch a browser or an URI with a custom scheme (such as mailto:) that activates an app through protocol association. (We’ll return to protocols in Chapter 15.) This is demonstrated in scenario 2 (js/launch-uri.js):

var uri = new Windows.Foundation.Uri(document.getElementById("uriToLaunch").value);
 
// Launch the URI.
Windows.System.Launcher.launchUriAsync(uri).done(
   function (success) {
      // success indicates whether the URI was launched.
   }
});

Note launchUriAsync does not recognize ms-appx, ms-appx-web, or ms-appdataURIs, because these already map to the current app. The app should just display such pages directly.

With both APIs you can control some aspects of the launching process by passing a LauncherOptions object as the second argument. Its properties are as follows:

images

The Association launching sample lets you play with some of these options. Here’s how the Open With dialog appears for an .mp4 file when using displayApplicationPicker:

images

Let’s switch now to the other side of the equation: an app that can be launched through a file association must first have at least one File Type Association on the Declarations tab of the manifest editor, as shown in Figure 11-13. Each file type can have multiple specific types (notice the Add New button under Supported File Types), such as a JPEG having .jpg and .jpeg file extensions. Note again that some file types are disallowed for apps; see How to handle file activation for the complete list.

Under Properties, the Display Name is the overall name for a group of file types (this is optional; not needed if you have only one type). The Name, on the other hand, is required—it’s the internal identity for the file group and one that should remain consistent for the entire lifetime of your app across all updates. In a way, the Name/Display Name properties for the whole group of file types is like your real name, and all the individual file types are nicknames—any of them ultimately refer to the core file type and your app.

Info Tip is tooltip text for when the user hovers over a file of this type and the app is the primary association. The Logo is a little tricky; in Visual Studio here, you simply refer to a base name for an image file, like you do with other images in the manifest. In your actual project, however, you should have multiple files for the same image in different target sizes (not resolution scales): 16x16, 32x32, 48x48, and 256x256; you’ll find a place to specify all of these under the Square 30x30 Logo section of the Visual Assets tab in the manifest editor. The Association launching sample uses such images with targetsize-* suffixes in the filenames, as in smallTile-sdk.targetSize-32.png.88 These various sizes help Windows provide the best user experience across many different types of devices, and you should be sure to provide them all.

images

FIGURE 11-13 The Declarations > File Type Associations UI in the Visual Studio manifest designer.

The options under Edit Flags control whether an “Open” verb is available for a downloaded file of this type: checking Open Is Safe will enable the verb in various parts of the Windows UI; checking Always Unsafe disables the verb. Leaving both blank might enable the verb, depending on where the file is coming from and other settings within the system.

At the very bottom of this UI you can also set a discrete start page for handling activations, including a setting for the launched app’s desired view. Typically, though, as shown in js/default.js of the sample, you’ll just use your main activation handler:

function activated(e) {
   // If activated for file type or protocol, launch the appropriate scenario.
   // Otherwise navigate to either the first scenario or to the last running scenario
   // before suspension or termination.
   var url = null;
   var arg = null;
 
   if (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.file) {
      url = scenarios[2].url;
      arg = e.detail.files;
   } elseif (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.protocol) {
      url = scenarios[3].url;
      arg = e.detail.uri;
   } elseif (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
      url = WinJS.Application.sessionState.lastUrl || scenarios[0].url;
   }
 
   // ...
 
}

Here you can see some of the other possibilities in the ActivationKind enumeration. The file kind means the app was launched through file activation, in response to which it navigates to scenario 3 ([2] by array position). The protocol kind means it was activated through a custom URI scheme, as we’ll again see in Chapter 15, which navigates to scenario 4. And of course launch is the default kind, when an app is launched from a tile.

Note Apps can be activated for these purposes when they’re already running. Be sure to test that case in your own apps and verify that the appropriate app data and settings are loaded, including session state if eventArgs.detail.previousExecutionState is terminated.

With the activation kind of file, the eventArgs.detail is a WebUIFileActivatedEventArgs object. As with normal activation, this contains an activatedOperation object with a getDeferral method (in case you need to do async operations) along with previousExecutionState andsplashScreen properties.

Three members are unique to file activation. First is the files property, which contains either the single StorageFile that the launching app provided to launchFileAsync or an array of StorageFile objects if the app was launched from Windows Explorer for a multiple selection. Thedetail.verb property will be "open". The sample, for its part, doesn’t do anything with the files it receives except pass them onto the page control in js/receive-file.js that just outputs some information about those files. Your real apps, of course, will respond more intelligently by working with the files and their contents as appropriate.

Tip Because the files might have come from anywhere, the receiving app should always treat them as untrusted content. Avoid taking permanent actions based on the file contents.

The event args also contains a property called neighboringFilesQuery, which is a ready-made StorageFileQueryResult that allows you to retrieve sibling files even though they weren’t actually selected or activated. This helps in creating gallery views for a folder that contains the activated file(s), and it’s a good place to call the query’s findStartIndexAsync, especially when using a semantic zoom control where you want to zoom to that index. In this case, the argument to findStartIndexAsync is one of the StorageFile objects within eventArgs.detail.files rather than a keyword.

Caveat With neighboringFilesQuery, the app still needs programmatic access to the folder in question, such as a library capabilities, otherwise you’ll see access denied exceptions. Thus, the query is primarily useful for those libraries and not arbitrary folders.

Apps that declare the Removable Storage capability and at least one file type can also be launched with ActivationKind.device, which means the user selected to launch that app in response to an AutoPlay event when a device (like a camera) was attached to the system. An app will typically then do things like import images from that device. Scenario 4 of the Removable storage sample is activated for this launch kind, for example. The eventArgs here is WebUIDeviceActivatedEventArgs, the key property of which is eventArgs.detail.deviceInformationId that identifies the attached device. This sample passes that onto its page control in js/s4_autoplay.js (as arg) that then gets a virtual StorageFolder through Windows.Devices.Portable.StorageDevice.fromId and runs a file query to find images:

var storage = (typeof arg === "string" ?
   Windows.Devices.Portable.StorageDevice.fromId(arg) : arg);
if (storage) {
   var storageName = storage.name;
 
   // Construct the query for image files
   var queryOptions = new Windows.Storage.Search.QueryOptions(
      Windows.Storage.Search.CommonFileQuery.orderByName, [".jpg", ".png", ".gif"]);
   var imageFileQuery = storage.createFileQueryWithOptions(queryOptions);
 
   // Run the query for image files
   imageFileQuery.getFilesAsync().done(function(imageFiles) {
      // process results
   });

What We’ve Just Learned

• User data lives anywhere outside the app’s own app data folders (and package). User data locations span the local file system, removable storage devices, local networks, and the cloud, and the StorageFile and StorageFolder objects hide the details about how those locations are referenced and their access models.

• Access to user data folders, such as media libraries, documents, and removable storage, is controlled by manifest capabilities. Such capabilities need be declared only if the app needs to access the file system in some way other than using the file picker.

• The file picker is the way that users can select files from any safe location in the file system, as well as files that are provided by other apps (where those files might be remote, stored in a database, or otherwise not present as file entities on the local file system). The ability to select files directly from other apps—including files that another app might generate on demand—is one of the most convenient and powerful features of Windows Store apps.

• Apps should always use the Windows.Storage.AccessCache to preserve programmatic access to files and folders for later sessions. The cache maintains two independent lists: one for recently used items (limited to 25) and one for general purpose use (limited to 1000 items).

• OneDrive is deeply integrated into Windows such that there is always at least a local placeholder file that will automatically download its contents (if allowed by the current network) when the file is open. The StorageFile.isAvailable flag indicates whether the file’s contents are currently accessible.

• The StorageFile object provide access to rich metadata, including thumbnails and media specific properties.

• Apps should always use thumbnails to show image contents in consumption scenarios to avoid loading full image data. Loading the image is necessary only in editing and full-image panning views.

• The StorageLibrary object supplies the ability to manage which folders are included in the user’s media libraries.

StorageFolder objects provide a very rich and extensive capability to enumerate its contents and to query for items that match certain criteria. WinRT provides common file and folder queries for typical scenarios, but you can also build custom queries with Advanced Query Syntax (AQS) strings. Custom queries also provide prefetching capabilities for properties and thumbnails.

• The WinJS.UI.StorageDataSource object provides a built-in means with query support to create a gallery experience in a ListView control. A gallery should always use thumbnails for images and use the isAvailable flag to differentiate items in its UI.

• To support file activation, an app associates itself with one or more file types in its manifest and then watches for the ActivationKind.file whose event args will contain the StorageFile objects for the files that were launched, as well as a neighboringFilesQuery that provides access to other files in the folder.

• Apps that support removable storage can also associate themselves with one or more file types and be launched for Auto Play events with ActivationKind.device.

84 There is also a documents option for this API that has the same requirements as KnownFolder.documentsLibrary.

85 Contrary to any examples in the documentation, queries should always use a full property name such as System.ItemDate: rather than the user-friendly shorthand date: because the latter will not work on localized builds of Windows.

86 To reiterate the purpose of applicationSearchFilter and userSearchFilter, if my app was capable of working with only mp3 formats, I could store the constant term "System.ItemType: 'mp3'" in applicationSearchFilter and then put variable, user-provided terms like "Nightingale" in userSearchFilter.

87 It’s generally best to work with library content through one of the KnownFolder objects because these will reflect all the other folders that a user might have added to those libraries. This will not be the case if you have the user select only an individual local folder. That said, one point of user frustration (which I’ve encountered personally) is the inability to add a folder on removable storage to a library, in which case providing a way to include such folders directly is helpful.

88 Ignore, however, the sample’s use of targetsize-* naming conventions for the app’s tile images; this is an error because target sizes apply only to file and URI scheme associations.