Эта топик результат выполнения тестового задания, со сроком выполнения 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.

Комментарии (6)


  1. justCxx
    17.08.2015 14:44
    +1

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

    Ну слишком же… причешите.


  1. Door
    17.08.2015 15:11
    +1

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

    Можно поподробней тут? Фразу совсем не понял


  1. bohdan4ik
    17.08.2015 15:31
    +1

    Похвально, но можно было сделать и немного по-другому, чтобы слишком много велосипедов не писать.
    К примеру, использовать FUSE: github.com/s3fs-fuse/s3fs-fuse :)


    1. crackedmind
      17.08.2015 16:05

      Осталось всего ничего, сделать так чтоб fuse работал в macos & windows


      1. bohdan4ik
        17.08.2015 16:07

        У автора используется Линукс в качестве ядра. Я думал, что это он писал для себя. «Опровержение» написал ниже :)


    1. bohdan4ik
      17.08.2015 16:06

      Оу, невежа. Это же тестовое задание. Прошу прощения, через слово читал топик :(