Эта топик результат выполнения тестового задания, со сроком выполнения 1 неделя. Поэтому код на уровне прототипа.
С моей колокольни(пользователя REST API) S3-это хранилище типа key-value, при этом допускается создавать ключи с пустым значением. При таком подъходе струткура хранилища плоская, никакого привычного нам дерева каталогов и файлов, которое мы привыкли видеть в файловой системе.
Как водится среди key-value хранилишь, если нужно что-то отличное от хеш-таблицы,
до говариваемся о представление ключа, а все остальные хотелки выночим на уровень приложения.
В данном случае будут исользоватся следующие соглашения:
В зависимости от того какое приложение вы создаете, то выбираете удобное соглашение формирования ключей.
Здесь нет никаких токенов. Тут нужно подписвать каждый запрос. В 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.
Далее идет таблица используемых запросов
Небольшой комментарий по второму запросу, получающему список объектов. Ключи в ответе упорядочены в лексикографическом порядке. По умолчанию запрос возвращает первую 1000 ключей. Если запрос обрезается,
то выставляется флаг IsTruncated:true, в этом случае для получения следующей партии ключей передается marker указывающий на последний полученный ключ. Значение 1000 можно изменить параметром max-keys.
По хорошему, для уменьшения потребления памяти, дерево хорошо бы хранить на диске. Но т.к. времени было в обрез, сделал традиционно в памяти:
В узлах дерева хранятся сегменты пути(в UI они отображаются каталогами), в листьях позиция объекта в списке objects. Алгоритм вставки простой: разбиваем ключ на сегменты, идем по дереву и вставляем новые узлы.
Примечательно что из-за того, что ключи упорядочены, а вставка не нарушает порядок, получается дерево поиска.
Когда есть готовая древовидная структура легко пишется враппер под интерфейс QAbstractItemModel. Все валидные модельные индексы, которые подаются в методы класса QAbstractItemModel будут создаваться методом:
Третьим параметром передается указатель на элемент дерева TreeNode.
В дальнейшем этот указатель будет возвращаться методом индекса:
Далее прокомментирую какой смысл имеют методы QAbstractItemModel при разработке дерева.
В моем случае существенными параметрами является row и parent. Данный метод создает модельный индекс для потомка узла parent, который стоит на позиции row.
В моем случае это позиция узла в списке TreeNode.children.
Количество потомков у узла parent. Внутри себя вызывает TreeNode.children.size()
Получить модельный индекс родительского узла. Может потребовать до двух переходов по TreeNode.parent, что бы определить правильную позицию узла и передать её в createIndex
Правильно реализовав эти три метода, у вас будет дерево.
На листьях моего дерева метаданные возвращаемые AWS, то среди них нет «имени для отображения».
Значит это имя надо извлечь из ключа. Из соображений, что в ListView и TaleView удобно отображать ключи, а в TreeView отображаемых елементов меньше чем в TableView, было принято решение для TreeView сделать делегат. Такое решение вполне соответствует Model/View Programming. Например если строка слишком большая и не вмещается в отображаемую область,
то делегат может обрезать её и дописать многоточие.
Отнаследовавшись от QItemDelegate переопределил:
Результат:
О юзабилити вообще не думал, как следствие приходится вручную определять регион, хотя его можно вытащить из хоста.
А так же неочевидность назначения кнопки на тулбаре.
Есть код с примерами использования который можно задействовать,
если вам чем то не устраивает Qt Cloud Services.
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.
Комментарии (6)
Door
17.08.2015 15:11+1По хорошему, для уменьшения потребления памяти, дерево хорошо бы хранить на диске. Но т.к. времени было в обрез, сделал традиционно в памяти:
Можно поподробней тут? Фразу совсем не понял
bohdan4ik
17.08.2015 15:31+1Похвально, но можно было сделать и немного по-другому, чтобы слишком много велосипедов не писать.
К примеру, использовать FUSE: github.com/s3fs-fuse/s3fs-fuse :)crackedmind
17.08.2015 16:05Осталось всего ничего, сделать так чтоб fuse работал в macos & windows
bohdan4ik
17.08.2015 16:07У автора используется Линукс в качестве ядра. Я думал, что это он писал для себя. «Опровержение» написал ниже :)
bohdan4ik
17.08.2015 16:06Оу, невежа. Это же тестовое задание. Прошу прощения, через слово читал топик :(
justCxx
Ну слишком же… причешите.