Статьи

Резервное копирование и восстановление на Skydrive в Windows Phone 7

Я работаю над большим обновлением для своего приложения Speaker Timer и одной из функций, которые я реализую, является поддержка Skydrive, чтобы создать резервную копию данных и восстановить их в случае, если пользователь покупает новый телефон или ему нужно сбросить его , В Интернете есть много учебных пособий по этому сценарию, но я думаю, что во всех них отсутствует некоторая важная информация: некоторые учебные пособия только объясняют, как загружать файлы на Skydrive, а не как их загружать; Официальная документация объясняет, как выполнить загрузку, но упоминает такие вещи, как «идентификатор файла», не объясняя в деталях, как ее получить.

По этой причине я решил написать учебник самостоятельно: надеюсь, вы найдете его полезным для ваших приложений. Помните, что это руководство основано на Windows Phone 7: в Windows Phone 8 подход действительно похож, но Live SDK поддерживает асинхронные методы вместо использования шаблона обратного вызова.

Подготовить окружающую среду

Первое, что нужно сделать, это добавить Live SDK к вашему проекту, который является SDK, предоставленным Microsoft для взаимодействия со службами Live. Вы можете использовать его для идентификации пользователя, для получения некоторой информации о нем, для доступа к его удаленным фотографиям и так далее. Одной из поддерживаемых функций является доступ Skydrive: вы можете читать и записывать файлы из облачного хранилища пользователя. Вы можете скачать Live SDK для Windows Phone с официального сайта или просто добавить его с помощью NuGet .

Второй шаг — регистрация вашего приложения на портале Live Developer: этот шаг необходим для получения доступа к Client Id , который является уникальным идентификатором, который необходим сервисам Live для идентификации вашего приложения. После того, как вы вошли в свою учетную запись Microsoft на портале, нажмите на раздел « Мои приложения » и выберите опцию « Создать приложение» .

образ

На первом этапе вы должны указать уникальный идентификатор (в поле « Имя приложения» ) и основной язык вашего приложения. Когда вы будете готовы, нажмите кнопку Я принимаю. На втором и последнем этапе вы можете указать, что разрабатываете мобильное приложение, выбрав « Да» в опции « Мобильное клиентское приложение» . Не забудьте нажать Сохранить для подтверждения.

образ

На этой странице вы также получите идентификатор клиента , который мы скоро будем использовать для настройки нашего приложения.

Войдите с учетной записью Microsoft

Первая операция, которую необходимо выполнить в вашем приложении, — добавить поддержку входа в систему: пользователю необходимо будет войти в свою учетную запись Microsoft, прежде чем он сможет взаимодействовать со Skydrive. Эта операция действительно проста, поскольку Live SDK включает в себя встроенный элемент управления, который отвечает за весь процесс. Прежде всего, добавьте в объявление PhoneApplicationPage в XAML пространство имен, содержащее элемент управления:

XMLNS: мой =»CLR-пространства имена: Microsoft.Live.Controls; сборочный = Microsoft.Live.Controls»

Теперь вы можете добавить элемент управления на страницу, как в следующем примере:

<my:SignInButton ClientId="your_client_id"
    Scopes="wl.signin wl.skydrive_update"
    Branding="Skydrive"
    TextType="SignIn"
    SessionChanged="SignInButton_OnSessionChanged"
    VerticalAlignment="Top"
/>

Элемент управления — это просто кнопка, которая позаботится обо всех операциях, необходимых для входа в систему: когда пользователь нажимает на нее, открывается веб-представление с запросом учетных данных учетной записи Microsoft. После того, как процесс входа в систему завершен, в приложении вы получите информацию, была ли операция входа успешной или нет.

образобраз

Имя элемента управления — SignInButton, и он предлагает множество свойств для его настройки. Наиболее важным является ClientId , который содержит уникальный идентификатор приложения, которое вы получили после регистрации приложения на портале разработчика. Еще одна важная опция — Scopes , с помощью которой можно указать, какие функции платформы Live вы собираетесь использовать (вы можете найти список всех доступных областей на этой странице ): в этом случае нам просто нужен wl.signin (это основной, необходимый для поддержки аутентификации) и wl.skydrive_update(который необходим для доступа на чтение и запись к Skydrive). Если вы хотите настроить внешний вид кнопки, вы можете использовать параметры Branding и TextType : первый используется для выбора логотипа, который будет отображаться на кнопке (поскольку мы собираемся взаимодействовать со Skydrive, мы используем Skydrive опция), то второй из них выбрать тексты Логин / выхода из системы , которые будут использоваться в качестве метки (вы также можете настроить их с помощью SignInText и SignOutText свойства).

Теперь пришло время написать некоторый код: кнопка предлагает событие с именем SessionChanged, которое вызывается, когда пользователь взаимодействует с элементом управления и пытается войти в систему. Вот как управлять событием в коде:

private void SignInButton_OnSessionChanged(object sender, Microsoft.Live.Controls.LiveConnectSessionChangedEventArgs e)
{
    if (e.Status == LiveConnectSessionStatus.Connected)
    {
        client = new LiveConnectClient(e.Session);
        MessageBox.Show("Connected!");
    }
}

Параметр e содержит статус операции: с помощью свойства Status (тип LiveConnectSessonStatus ) мы можем узнать, успешно ли завершена операция входа ( LiveConnectSession.Status.Connected ). В этом случае мы можем создать новый экземпляр класса LiveConnectClient , который является базовым классом, необходимым для выполнения всех операций со службами Live. В качестве параметра необходимо передать идентификатор текущего сеанса, который хранится в свойстве Session параметров метода.

Резервное копирование файла

Теперь, когда пользователь вошел в систему и у вас есть действующий объект LiveConnectClient , вы готовы сделать резервную копию ваших файлов на Skydrive. В этом примере мы собираемся сохранить один файл в корне Skydrive:

private void OnBackupClicked(object sender, RoutedEventArgs e)
{
    client.UploadCompleted += client_UploadCompleted;
    using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
    {
        IsolatedStorageFileStream stream = storage.OpenFile("Sessions.xml", FileMode.Open);
        client.UploadAsync("me/skydrive", "SpeakerTimer.bak", stream, OverwriteOption.Overwrite);
    }
}
 
void client_UploadCompleted(object sender, LiveOperationCompletedEventArgs e)
{
    if (e.Error == null)
    {
        MessageBox.Show("Upload successfull!");
    }
}

Первое, что мы делаем, это подписываемся на событие UploadCompleted , предоставляемое классом LiveConnectClient , которое вызывается после завершения операции загрузки: это необходимо, потому что операция асинхронная. Затем, используя API хранилища, мы получаем поток файла, который мы хотим сохранить : в моем примере это файл с именем Sessions.xml, хранящийся в корне изолированного хранилища. В конце мы запускаем операцию загрузки, вызывая метод UploadAsync () клиента, который принимает:

  • Путь, где сохранить файл в хранилище Skydrive. Используя синтаксис « me / skydrive», мы собираемся сохранить файл в корне Skydrive;
  • Имя файла, который будет сохранен на Skydrive;
  • Поток файла для сохранения: это тот, который мы получили с помощью метода OpenFile () класса IsolatedStorageFile ;
  • Что делать, если файл на Skydrive уже существует: в нашем примере мы просто перезаписываем его, используя значение OverwriteOption.Overwrite .

Когда UploadCompleted событие возникает , мы просто проверить , если произошла ошибка, проверив значение ошибки свойство LiveOperationCompletedEventArgs параметра: в случае , если это нуль, мы показываем сообщение с уведомлением о том , что загрузка успешно завершена. Если мы все сделали правильно, мы найдем файл в хранилище нашего Skydrive.

Восстановление файла

Операция резервного копирования была очень простой для выполнения; вместо этого операция восстановления немного сложнее, поскольку каждый файл и папка на Skydrive идентифицируется уникальным идентификатором, и вы не можете получить доступ к загруженному нами файлу, используя его имя. Одним из способов получить его является использование параметров, возвращаемых методом UploadCompleted , которые содержат в свойстве Result словарь со всеми свойствами файла, поэтому с помощью следующего кода мы можем его получить.

void client_UploadCompleted(object sender, LiveOperationCompletedEventArgs e)
{
    if (e.Error == null)
    {
        string fileId = e.Result["id"].ToString();
        MessageBox.Show("Upload successfull!");
    }
}

The problem with this approach is that, before getting the id of the file, we need to upload it. The most common scenario, instead, is that the user has made a reset or purchased a new phone and he wants to restore the backup after reinstalling the application: in this case no upload operations have been made, so we need to find another way to get the id of the file. The solution is to use the GetAsync() method of the client, that can be used to retrieve all the Skydrive files and folders and access to their properties. We’re going to get a list of all the Skydrive files and folders and, by exploring their properties, we’re going to find the id of the file which name is SpeakerTimer.bak.

private void OnRestoreClicked(object sender, RoutedEventArgs e)
{
    string id = string.Empty;
    client.GetCompleted += (obj, args) =>
    {
        List<object> items = args.Result["data"] as List<object>;
        foreach (object item in items)
        {
            Dictionary<string, object> file = item as Dictionary<string, object>;
            if (file["name"].ToString() == "SpeakerTimer.bak")
            {
                id = file["id"].ToString();
            }
        }
    };
 
    client.GetAsync("me/skydrive/files");
}

The GetAsync() method of the client accepts as parameter the path we want to explore: by using the me/skydrive/files syntax we get the list of all the files and folders inside the root. Since the method is asynchronous, we subscribe to the GetCompleted event, which is raised when the operation is done. The Result property of the parameter contains a collection of all the available files and folders, inside the item identified by the Data collection (it’s a dictionary). Since it’s a collection, we need to do a cast to the List<object> type. Every object inside this collection is another Dictionary<string, object>, that contains all the properties of the file or folder. By using a foreach we iterate over all the files and folders and, for each one, we check the value of the name’s property: if the value is the one we’re expecting (SpeakerTimer.bak), we get the id property and we store it (it will be something like file.8c8ce076ca27823f.8C8CE076CA27823F!129).

Now we’re ready to execute the real download operation:

client.DownloadCompleted += (o, a) =>
                                {
                                    Stream stream = a.Result;
                                    using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
                                    {
                                        using (
                                            IsolatedStorageFileStream fileToSave = storage.OpenFile("Sessions.xml", FileMode.Create,
                                                                                                    FileAccess.ReadWrite))
                                        {
                                            stream.CopyTo(fileToSave);
                                            stream.Flush();
                                            stream.Close();
                                        }
                                    }
                                };
 
client.DownloadAsync(string.Format("{0}/content", id));

We use the DownloadAsync() method of the client, passing as parameter the id of the file we’ve just retrieved. It’s really important to add the /content suffix to the id: many tutorials are missing this information, but it’s crucial because, otherwise, you’ll get the JSON with all the file’s properties instead of the real content.

Since, as usual, the operation is asynchronous, we subscribe to the DownloadCompleted event, that is invoked when the download is completed and we have access to the downloaded file, which is stored, as a stream, in the Result property of the method’s parameters. By using, again, the storage APIs, we save the stream we’ve downloaded in the Isolated Storage, by creating a new file (using the OpenFile() method and passing FileMode.Create as option) and by copying the downloaded stream in the local stream.

Here is the full restore method, where the two operations are executed at the same time:

private void OnRestoreClicked(object sender, RoutedEventArgs e)
{
    string id = string.Empty;
    client.GetCompleted += (obj, args) =>
    {
        List<object> items = args.Result["data"] as List<object>;
        foreach (object item in items)
        {
            Dictionary<string, object> file = item as Dictionary<string, object>;
            if (file["name"].ToString() == "SpeakerTimer.bak")
            {
                id = file["id"].ToString();
            }
        }
 
        client.DownloadCompleted += (o, a) =>
                                        {
                                            Stream stream = a.Result;
                                            using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
                                            {
                                                using (
                                                    IsolatedStorageFileStream fileToSave = storage.OpenFile("Sessions.xml", FileMode.Create,
                                                                                                            FileAccess.ReadWrite))
                                                {
                                                    stream.CopyTo(fileToSave);
                                                    stream.Flush();
                                                    stream.Close();
                                                }
                                            }
                                        };
 
        client.DownloadAsync(string.Format("{0}/content", id));
    };
 
    client.GetAsync("me/skydrive/files");
}

Conclusion

This is a simple solution to backup and restore your data in your application: there’s room for improvement (for example, you can encrypt the file before uploading it), but this tutorial covers pretty much all of the basic steps. Have fun!