From 8e2125ee72af7c4e7c13ad526122a6c175c5e661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Marseault?= Date: Sat, 7 Jun 2025 17:02:48 +0200 Subject: [PATCH] WebAPI: Add metadata in /app/getDirectoryContent response ## Description Send file/folder metadata instead of just the name of a filesystem entry. Currently the endpoint only sends a list of string, containing the path of each entry, without specifying its type (file or folder). The optional `withMetadata` flag has been added to provide metadata and to prevent breaking changes with older versions. If `true`, JSON response will be an array of objects instead of an array of strings. This object contains: - `name`: the name of the file system entry (without path) - `type`: Whether the file system entry is a "file" or a "dir" - `creation_date`: file system entry's creation date - `last_access_date`: file system entry's last access date - `last_modification_date`: file system entry's last modification date If the entry is a file, a `size` field is present with the file size in bytes. ## Objective Build a server file browser inside WebUIs, feature is currently being developed for VueTorrent. It will include file metadata, filtering and sorting on the different fields. PR #22813. --- WebAPI_Changelog.md | 25 ++++++++++++++++++++-- src/webui/api/appcontroller.cpp | 38 ++++++++++++++++++++++++++++++++- src/webui/webapplication.h | 2 +- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index d05d66524..c2164f65a 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -1,5 +1,26 @@ -# 2.11.6 -* https://github.com/qbittorrent/qBittorrent/pull/22460 +# WebAPI Changelog + +## 2.11.8 + +* [#21349](https://github.com/qbittorrent/qBittorrent/pull/21349) + * Handle sending `204 No Content` status code when response contains no data + * Some endpoints still return `200 OK` to ensure smooth transition +* [#22750](https://github.com/qbittorrent/qBittorrent/pull/22750) + * `torrents/info` allows an optional parameter `includeFiles` that defaults to `false` + * Each torrent will contain a new key `files` which will list all files similar to the `torrents/files` endpoint +* [#22813](https://github.com/qbittorrent/qBittorrent/pull/22813) + * `app/getDirectoryContent` allows an optional parameter `withMetadata` to send file metadata + * Fields are `name`, `type`, `size`, `creation_date`, `last_access_date`, `last_modification_date` + * See PR for TypeScript types + +## 2.11.7 + +* [#22166](https://github.com/qbittorrent/qBittorrent/pull/22166) + * `sync/maindata` returns 3 new torrent fields: `has_tracker_warning`, `has_tracker_error`, `has_other_announce_error` + +## 2.11.6 + +* [#22460](https://github.com/qbittorrent/qBittorrent/pull/22460) * `app/setPreferences` allows only one of `max_ratio_enabled`, `max_ratio` to be present * `app/setPreferences` allows only one of `max_seeding_time_enabled`, `max_seeding_time` to be present * `app/setPreferences` allows only one of `max_inactive_seeding_time_enabled`, `max_inactive_seeding_time` to be present diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 398f08086..41d9b560c 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -77,6 +77,12 @@ const QString KEY_COOKIE_DOMAIN = u"domain"_s; const QString KEY_COOKIE_PATH = u"path"_s; const QString KEY_COOKIE_VALUE = u"value"_s; const QString KEY_COOKIE_EXPIRATION_DATE = u"expirationDate"_s; +const QString KEY_FILE_METADATA_NAME = u"name"_s; +const QString KEY_FILE_METADATA_TYPE = u"type"_s; +const QString KEY_FILE_METADATA_SIZE = u"size"_s; +const QString KEY_FILE_METADATA_CREATION_DATE = u"creation_date"_s; +const QString KEY_FILE_METADATA_LAST_ACCESS_DATE = u"last_access_date"_s; +const QString KEY_FILE_METADATA_LAST_MODIFICATION_DATE = u"last_modification_date"_s; void AppController::webapiVersionAction() { @@ -1210,10 +1216,40 @@ void AppController::getDirectoryContentAction() throw APIError(APIErrorType::BadParams, tr("Invalid mode, allowed values: %1").arg(u"all, dirs, files"_s)); }; + const bool withMetadata {Utils::String::parseBool(params()[u"withMetadata"_s]).value_or(false)}; + QJsonArray ret; QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))}; while (it.hasNext()) - ret.append(it.next()); + { + if (withMetadata) + { + const QFileInfo fileInfo = it.nextFileInfo(); + QJsonObject fileObject + { + {KEY_FILE_METADATA_NAME, fileInfo.fileName()}, + {KEY_FILE_METADATA_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(fileInfo.birthTime())}, + {KEY_FILE_METADATA_LAST_ACCESS_DATE, Utils::DateTime::toSecsSinceEpoch(fileInfo.lastRead())}, + {KEY_FILE_METADATA_LAST_MODIFICATION_DATE, Utils::DateTime::toSecsSinceEpoch(fileInfo.lastModified())}, + }; + + if (fileInfo.isDir()) + { + fileObject.insert(KEY_FILE_METADATA_TYPE, u"dir"_s); + } + else if (fileInfo.isFile()) + { + fileObject.insert(KEY_FILE_METADATA_TYPE, u"file"_s); + fileObject.insert(KEY_FILE_METADATA_SIZE, fileInfo.size()); + } + + ret.append(fileObject); + } + else + { + ret.append(it.next()); + } + } setResult(ret); } diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index a55b3a7de..e39140eff 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -53,7 +53,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 11, 7}; +inline const Utils::Version<3, 2> API_VERSION {2, 11, 8}; class APIController; class AuthController;