Простой обозреватель для Amazon S3 на Qt

от автора

Эта топик результат выполнения тестового задания, со сроком выполнения 1 неделя. Поэтому код на уровне прототипа.

Amazon Simple Storage Service(S3)

С моей колокольни(пользователя REST API) S3-это хранилище типа key-value, при этом допускается создавать ключи с пустым значением. При таком подъходе струткура хранилища плоская, никакого привычного нам дерева каталогов и файлов, которое мы привыкли видеть в файловой системе.

Как водится среди key-value хранилишь, если нужно что-то отличное от хеш-таблицы,
до говариваемся о представление ключа, а все остальные хотелки выночим на уровень приложения.
В данном случае будут исользоватся следующие соглашения:

  • foo/bar/-это каталог foo, c подкаталогом bar. Используется для создания пустых каталогов;
  • foo/bar-это каталог foo, с файлом bar;
  • на этом наборе данных в качестве разделителя местами использовался символ ‘:’.

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

Аутентификация в S3

Здесь нет никаких токенов. Тут нужно подписвать каждый запрос. В Signing AWS Requests By Using Signature Version 4 расписано по шагам и есть примеры для разных языков программирования. Тут отмечу, что там используется HMAC-SHA256, для его вычисления можно использовать класс QMessageAuthenticationCode появившийся в Qt5.1.

Запросы

В каждом запросе кроме заголовка Authorization передаю еще следующие: Date, Host, x-amz-date, x-amz-content-sha256. x-amz-content-sha256 — это хеш тела запроса подсчитанный методом SHA256. Этот хеш так же добавляется в «канонический запрос» при вычислении сигнатуры запроса. Да же если идет GET запрос у которого отсутствует тело, этот заголово все равно передается. Вычисляется стандартным классом QCryptographicHash.

Далее идет таблица используемых запросов

GET / HTTP/1.1
Host s3.amazon.com
Получить список бакетов
GET / HTTP/1.1
Host BucketName.s3.amazon.com
Получить список объектов в бакете BucketName
GET /ObjectName HTTP/1.1
Host BucketName.s3.amazon.com
Скачать файл из бакета BucketName. Целиком или часть.
PUT /ObjectName HTTP/1.1
Host BucketName.s3.amazon.com
Загрузить файл на S3, в бакет BucketName

Небольшой комментарий по второму запросу, получающему список объектов. Ключи в ответе упорядочены в лексикографическом порядке. По умолчанию запрос возвращает первую 1000 ключей. Если запрос обрезается,
то выставляется флаг IsTruncated:true, в этом случае для получения следующей партии ключей передается marker указывающий на последний полученный ключ. Значение 1000 можно изменить параметром max-keys.

Строим дерево


По хорошему, для уменьшения потребления памяти, дерево хорошо бы хранить на диске. Но т.к. времени было в обрез, сделал традиционно в памяти:

struct ObjectTree {     TreeNode* root;     ASSS::ObjectInfoList objects; }; struct TreeNode {     typedef QList<TreeNode*> NodeList;     enum Type     {         Undef,         Leaf,         Node     };      Type type;     TreeNode* parent;     NodeList children;     int objectPos;     QString segment;     bool isRoot; }; 

В узлах дерева хранятся сегменты пути(в UI они отображаются каталогами), в листьях позиция объекта в списке objects. Алгоритм вставки простой: разбиваем ключ на сегменты, идем по дереву и вставляем новые узлы.
Примечательно что из-за того, что ключи упорядочены, а вставка не нарушает порядок, получается дерево поиска.

Немного «Model/View Programming»

Модель дерева

Когда есть готовая древовидная структура легко пишется враппер под интерфейс QAbstractItemModel. Все валидные модельные индексы, которые подаются в методы класса QAbstractItemModel будут создаваться методом:

QModelIndex QAbstractItemModel::createIndex(int row, int column, void * ptr = 0) const 

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

void * QModelIndex::internalPointer() const 

Далее прокомментирую какой смысл имеют методы QAbstractItemModel при разработке дерева.

virtual QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; 

В моем случае существенными параметрами является row и parent. Данный метод создает модельный индекс для потомка узла parent, который стоит на позиции row.
В моем случае это позиция узла в списке TreeNode.children.

virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; 

Количество потомков у узла parent. Внутри себя вызывает TreeNode.children.size()

virtual QModelIndex parent(const QModelIndex & index) const; 

Получить модельный индекс родительского узла. Может потребовать до двух переходов по TreeNode.parent, что бы определить правильную позицию узла и передать её в createIndex

Правильно реализовав эти три метода, у вас будет дерево.

Делегат

На листьях моего дерева метаданные возвращаемые AWS, то среди них нет «имени для отображения».

Значит это имя надо извлечь из ключа. Из соображений, что в ListView и TaleView удобно отображать ключи, а в TreeView отображаемых елементов меньше чем в TableView, было принято решение для TreeView сделать делегат. Такое решение вполне соответствует Model/View Programming. Например если строка слишком большая и не вмещается в отображаемую область,
то делегат может обрезать её и дописать многоточие.

Отнаследовавшись от QItemDelegate переопределил:

void drawDisplay(QPainter * painter, const QStyleOptionViewItem & option, const QRect & rect, const QString & text) const 

Результат:

Недочеты в юзабилити

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

А так же неочевидность назначения кнопки на тулбаре.

Что в остатке?

Есть код с примерами использования который можно задействовать,
если вам чем то не устраивает Qt Cloud Services.

ссылка на оригинал статьи http://habrahabr.ru/post/264715/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *