Статьи

Написание интерфейса Calibre для Windows8 / WinRT с использованием SQLite для WinRT

Разрабатывая приложение для Магазина Windows для магазина электронных книг Caliber, я столкнулся с несколькими трудностями. В этом посте я объясню некоторые нюансы и то, как я пытался их устранить.

В основном их можно обобщить следующим образом:

  • Как получить доступ к базе данных Sqlite в приложении WinRT
  • Обходное ограничение доступа к файлам SQLIte для WinRT
  • Загрузить файлы обложек каждой книги
  • Создание инкрементальной загрузки Gridview с помощью ISupportIncrementalLoading

Calibre — отличный открытый исходный код и бесплатный для использования ebookmanager. Это позволяет мне управлять своей постоянно растущей библиотекой электронных книг и поддерживает множество типов файлов электронных книг, включая возможность преобразования между типами.

Моя цель — написать простое приложение для Магазина Windows, которое будет использоваться в качестве интерфейса для базы данных Caliber. Он покажет мою библиотеку в визуально привлекательной форме (он же TDPFAM, «Принцип дизайна, ранее известный как Metro») и позволит пользователю быстро запрашивать свою базу данных из любой точки окна. По крайней мере, это идея. Посмотрим, где мы окажемся (посмотрите здесь небольшое видео, демонстрирующее приложение, которое я создаю).

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

Ранняя альфа-версия Calibre, просто демонстрация того, что можно сделать (посмотрите фильм на YouTube ). Обратите внимание на макет, основанный на одной из существующих настроек проекта VS2012 WinRT.

Оригинальная программа Caliber

В этом посте я надеюсь, что у других появится триггер для создания своего собственного интерфейса Caliber, потому что, зная себя, мне очень надоест проект, как только мне придется заняться UI / UX… что мне не нравится.

Получение SQLite для Windows Runtime

Программа Caliber хранит всю свою информацию в файле «metadata.db», расположенном в корневой папке библиотеки книг. его файл представляет собой простую базу данных SQLite, поэтому нам нужно получить расширение «SQLIte для Windows RT». Я расскажу о структуре базы данных и папки позже, когда нам действительно понадобится доступ к ней.

В этом посте четко объясняется, как установить расширения «SQLIte для среды выполнения Windows», необходимые для доступа к базе данных Sqlite.

Подводя итог, эти шаги для выполнения:

  • Перейти в Инструменты => Расширения и обновления
  • Поиск Sqlite (онлайн конечно!)
  • Выберите и загрузите «Sqlite для среды выполнения Windows»
  • Добавьте ссылку на «Пакет времени выполнения MS Visual C ++» и «SqlLite для среды выполнения Windows»
  • Измените цель платформы на x86, arm или x64 (Project => Свойства проекта)

Sqlite-сеть

Sqlite-net позволяет более удобный для программистов способ доступа к базе данных Sqlite, включая возможность запрашивать вашу базу данных с помощью linq. Его можно найти на github здесь . Тем не менее, он также доступен через NuGet. Если вы устанавливаете sqlite-net через NuGet, в ваш проект будут добавлены два дополнительных файла (SQLite.cs и SQLiteAsync.cs).

Получить права на чтение в папке базы данных калибровочных книг и загрузить metadata.db

Доступ к файлам для приложений WinRT довольно ограничен. По сути, ваше приложение имеет полный доступ только к локальной папке приложения. Если вам нужен доступ в другие места, вам потребуется разрешение от пользователя. Поскольку нам нужен доступ к папке базы данных Caliber и ко всем подпапкам, которые могут находиться в любом месте компьютера и / или сети, мы будем использовать FolderPicker и немедленно сохранить выбранную папку в StorageApplicationPermissions.FutureAccessList. Это позволяет нашему приложению получать доступ к этой папке в будущем без необходимости нового разрешения от пользователя.

var folderPicker = new FolderPicker();
folderPicker.SuggestedStartLocation = PickerLocationId.Desktop;
folderPicker.FileTypeFilter.Add(".db");

StorageFolder folder = await folderPicker.PickSingleFolderAsync();
if (folder != null)
{
    // Application now has read/write access to all contents in the picked folder (including other sub-folder contents)
    StorageApplicationPermissions.FutureAccessList.Clear();
    StorageApplicationPermissions.FutureAccessList.AddOrReplace("dbfolder", folder);

    var file = await folder.GetFileAsync("metadata.db");
    if (file != null)
        await file.CopyAsync(
            Windows.Storage.ApplicationData.Current.LocalFolder,
            "metadata.db",
            NameCollisionOption.ReplaceExisting);
}

В целях этой демонстрации я очищаю FutureAccessList (строка 09). Однако, если вы хотите, чтобы ваше приложение позже использовало папку db, должно быть ясно, что очистка этого списка на самом деле не помогает улучшить удобство использования вашего приложения.

Калибр позволяет быстро переключаться между 2 или более библиотеками (например, у меня есть одна библиотека литературы и одна техническая библиотека). Разумеется, FutureAccessList может содержать все эти местоположения библиотеки, если пользователь добавил папки с помощью средства выбора папок.

Обновление (12/12/12) (спасибо Филипу Колмеру за напоминание об этом)
Также обратите внимание на строку 14, где я копирую файл metadata.db в локальную папку приложения. Это необходимо сделать, поскольку текущая версия SQLite для WinRT может работать только в нескольких местах. (Причина, заявленная здесь, заключается в том, что «SQLite использует API CreateFile2, который не является API-интерфейсом брокера WinRT. Это означает, что он ограничен определенными областями AppContainer».). Это означает, что мое приложение будет иметь дополнительный код для частой проверки исходного файла metadata.db, и, если это так, скопировать эту новую версию в локальную папку приложения.
В качестве бонуса это также обойдет любые исключения доступа к файлам, которые могут возникнуть, если и сам Caliber, и мое приложение попытаются получить доступ к файлу metadata.db.

Загрузить информацию о книге из базы данных

Библиотека Calibre имеет идеальный простой макет. По сути, это корневая папка, в которой находится база данных metadata.db. Эта база данных sqlite содержит все данные о книгах (мета), в том числе путь, в котором находится настоящая книга в файловой системе. Этот путь всегда является подпапкой папки, в которой находится сам файл metadata.db. Caliber создает папку для каждого автора, и в каждой папке для авторов есть одна или несколько папок с заголовками, содержащих одну книгу (но, возможно, несколько форматов, таких как pdf, epub, mobi и т. Д.)). Например:

  • [MyLib]

    • «Metadata.db»
    • [Стив Джобс]

      • [Его первая книга]

        • «Hisfirstbook.pdf»
        • «Histfirstbook.mobi»
        • «Cover.jpg»
      • [Другая книга]

        • HowICreatedAnApple.epub
        • «Cover.jpg»
    • [Билл Гейтс]

      • ….

Сам metadata.db довольно прост. Так как я не смог найти какую-либо документацию по базе данных (хотя я не очень тщательно искал ), я использовал SQLiteSpy, чтобы обнаружить структуру базы данных. Все начинается со стола книг.

Использование SQLiteSpy для обнаружения макета базы данных калибра

Поэтому, когда мы знаем структуру базы данных и организацию папок, возникает вопрос использования SQLite для WinRT (и sqlite-net) для выгрузки файла metadata.db в приложение, как показано здесь:

var resultlist = new List();
var fileex = await ApplicationData.Current.LocalFolder.GetFileAsync(filename);
var folder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("dbfolder");
if (fileex != null)
{
    using (var db = new SQLite.SQLiteConnection(
        Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, filename),
        SQLite.SQLiteOpenFlags.ReadOnly))
        {
        var allbooks = db.Table();

Загрузить изображения обложки

Приятной особенностью FutureAccessList является то, что мы можем получить доступ не только к самой папке, но и ко всем папкам под ней. Поскольку каждое изображение обложки книги (если оно есть) находится в той же папке, что и книжные файлы, мы можем просто получить к ним доступ без лишних хлопот.

Путь к обложке — это просто путь к самому книжному файлу (который можно найти в таблице книг) и всегда называется «cover.jpg». В таблице книг в metadata.db также есть поле «has_cover», которое мы можем использовать, чтобы сделать наш код менее подверженным ошибкам, предотвращая открытие несуществующих файлов обложки.

Следующий код кратко показывает, как мы могли бы создать новое изображение, содержащее обложку книги.

var folder =
    await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("dbfolder");
StorageFile file =
    await StorageFile.GetFileFromPathAsync(
    Path.Combine(folder.Path, book.path.Replace('/', '\\'),
    "cover.jpg"));
IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
BitmapImage img = new BitmapImage();
img.SetSource(stream);
Image cover = new Image() { Source = img };

Инкрементная нагрузка (экспериментальная)

Просто для забавы и экспериментов я посмотрел, как новый WinRT GridView поддерживает добавочную загрузку. Поскольку большинство библиотек Caliber содержат несколько сотен или тысяч книг, было бы излишним (и большим расходом энергии), если бы мы попытались загрузить информацию о книге, особенно обложки, сразу. Если источник элементов GridView реализует ISupportIncrementalLoading, GridView сможет загружать новые данные, когда это необходимо.

Твердые примеры и дополнительная информация о том, как реализовать этот интерфейс, не были легко найдены (посмотрите этот и этот, чтобы начать). Я должен признать, что у реализации, которую я сейчас имею, есть несколько глюков, и я почти уверен, что я не делаю это правильно … однако, это работает … и это то, что имеет значение сейчас, я думаю. Показанные здесь являются наиболее важными частями класс.

public class DalBooksIncrementSource :
    ObservableCollection<object>, ISupportIncrementalLoading
{
    public DalBooksIncrementSource()
    {
        CalibreDal.LoadSource();
    }

public bool HasMoreItems
{
    get
    {
        return (this.Count < CalibreDal.BookSource.Count<CalibreBook>());
    }
}

    private int AlreadyLoaded = 0;
    public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(
        uint count)
    {


        CoreDispatcher dispatcher = Window.Current.Dispatcher;
        return Task.Run<LoadMoreItemsResult>(
            () =>
            {

                List<CalibreBook> books = new List<CalibreBook>();
                for (int i = (int)AlreadyLoaded;
                    i < AlreadyLoaded + count && i < CalibreDal.BookSource.Count;
                    i++)
                {
                    books.Add(CalibreDal.BookSource[i]);
                }
                dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    () =>
                    {
                        foreach (var item in books)
                        {
                            this.Add(item);
                        }
                    });
                AlreadyLoaded += (int)count;
                return new LoadMoreItemsResult()
                    {
                        Count = (uint)books.Count
                    };

            }).AsAsyncOperation<LoadMoreItemsResult>();

    }
}

Вывод

Хорошо, пока это все. В основном этот пост представлял собой большой кусок кода, который мне понадобится, как только я напишу полноценное приложение для Магазина Windows. В то же время я собираюсь продолжать экспериментировать с этим материалом.

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