340 lines
10 KiB
C++
340 lines
10 KiB
C++
#include "ollie_kio.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QUrl>
|
|
#include <QDateTime>
|
|
#include <QMimeDatabase>
|
|
#include <QMimeType>
|
|
|
|
#include <KIO/UDSEntry>
|
|
#include <KIO/WorkerFactory>
|
|
|
|
using namespace KIO;
|
|
|
|
// Plugin factory — KIO loads this .so and calls the appropriate create method
|
|
#if QT_VERSION_MAJOR >= 6
|
|
class OllieWorkerFactory : public KIO::WorkerFactory
|
|
{
|
|
Q_OBJECT
|
|
Q_PLUGIN_METADATA(IID "org.kde.kio.worker.ollie" FILE "ollie.json")
|
|
public:
|
|
using WorkerFactory::WorkerFactory;
|
|
std::unique_ptr<WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override
|
|
{
|
|
return std::make_unique<OllieWorker>(pool, app);
|
|
}
|
|
};
|
|
#else
|
|
class OllieWorkerFactory : public KIO::RealWorkerFactory
|
|
{
|
|
Q_OBJECT
|
|
Q_PLUGIN_METADATA(IID "org.kde.kio.worker.ollie" FILE "ollie.json")
|
|
public:
|
|
using RealWorkerFactory::RealWorkerFactory;
|
|
std::unique_ptr<SlaveBase> createWorker(const QByteArray &pool, const QByteArray &app) override
|
|
{
|
|
Q_UNUSED(pool); Q_UNUSED(app);
|
|
return nullptr; // unused — KIO calls createRealWorker for RealWorkerFactory
|
|
}
|
|
std::unique_ptr<WorkerBase> createRealWorker(const QByteArray &pool, const QByteArray &app) override
|
|
{
|
|
return std::make_unique<OllieWorker>(pool, app);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
// kdemain entry point — KF5 KIO launches workers by calling this
|
|
extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv)
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
app.setApplicationName(QStringLiteral("kio_ollie"));
|
|
|
|
if (argc != 4) {
|
|
fprintf(stderr, "Usage: kio_ollie protocol pool_socket app_socket\n");
|
|
return -1;
|
|
}
|
|
|
|
OllieWorker worker(argv[2], argv[3]);
|
|
worker.dispatchLoop();
|
|
return 0;
|
|
}
|
|
|
|
OllieWorker::OllieWorker(const QByteArray &pool, const QByteArray &app)
|
|
: WorkerBase("ollie", pool, app)
|
|
{
|
|
}
|
|
|
|
OllieWorker::~OllieWorker()
|
|
{
|
|
m_client.disconnect();
|
|
}
|
|
|
|
bool OllieWorker::ensureConnected()
|
|
{
|
|
if (m_client.isConnected())
|
|
return true;
|
|
return m_client.connectDefault();
|
|
}
|
|
|
|
QString OllieWorker::ninepPath(const QUrl &url) const
|
|
{
|
|
QString path = url.path();
|
|
// Remove leading slash — 9P root is ""
|
|
if (path.startsWith(QLatin1Char('/')))
|
|
path = path.mid(1);
|
|
// Remove trailing slash
|
|
if (path.endsWith(QLatin1Char('/')) && path.length() > 1)
|
|
path.chop(1);
|
|
return path;
|
|
}
|
|
|
|
bool OllieWorker::isDirectory(const QString &mode) const
|
|
{
|
|
// 9P mode: starts with 'd' for directory,
|
|
// or numeric with Plan 9 DMDIR bit (0x80000000)
|
|
if (mode.startsWith(QLatin1Char('d')))
|
|
return true;
|
|
bool ok;
|
|
uint modeVal = mode.toUInt(&ok, 0);
|
|
if (ok && (modeVal & 0x80000000))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
mode_t OllieWorker::parseMode(const QString &modeStr) const
|
|
{
|
|
// Try numeric first
|
|
bool ok;
|
|
uint modeVal = modeStr.toUInt(&ok, 0);
|
|
if (ok) {
|
|
return static_cast<mode_t>(modeVal & 0777);
|
|
}
|
|
// Parse rwx string (e.g. "drwxr-xr-x")
|
|
QString perms = modeStr;
|
|
if (perms.startsWith(QLatin1Char('d')))
|
|
perms = perms.mid(1);
|
|
mode_t m = 0;
|
|
if (perms.length() >= 9) {
|
|
if (perms[0] == QLatin1Char('r')) m |= 0400;
|
|
if (perms[1] == QLatin1Char('w')) m |= 0200;
|
|
if (perms[2] == QLatin1Char('x')) m |= 0100;
|
|
if (perms[3] == QLatin1Char('r')) m |= 0040;
|
|
if (perms[4] == QLatin1Char('w')) m |= 0020;
|
|
if (perms[5] == QLatin1Char('x')) m |= 0010;
|
|
if (perms[6] == QLatin1Char('r')) m |= 0004;
|
|
if (perms[7] == QLatin1Char('w')) m |= 0002;
|
|
if (perms[8] == QLatin1Char('x')) m |= 0001;
|
|
} else {
|
|
m = 0644;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
KIO::UDSEntry OllieWorker::entryFromStat(const QString &path, const QString &name)
|
|
{
|
|
UDSEntry entry;
|
|
auto st = m_client.stat(path);
|
|
|
|
if (st.valid) {
|
|
bool dir = isDirectory(st.mode);
|
|
entry.fastInsert(UDSEntry::UDS_NAME, name.isEmpty() ? st.name : name);
|
|
entry.fastInsert(UDSEntry::UDS_SIZE, dir ? 0 : st.size);
|
|
entry.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, st.mtime);
|
|
entry.fastInsert(UDSEntry::UDS_ACCESS_TIME, st.mtime);
|
|
|
|
mode_t perm = parseMode(st.mode);
|
|
if (dir) {
|
|
entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
|
|
entry.fastInsert(UDSEntry::UDS_ACCESS, perm | 0111);
|
|
} else {
|
|
entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFREG);
|
|
entry.fastInsert(UDSEntry::UDS_ACCESS, perm);
|
|
}
|
|
|
|
if (!dir) {
|
|
QMimeDatabase db;
|
|
QMimeType mime = db.mimeTypeForFile(name.isEmpty() ? st.name : name,
|
|
QMimeDatabase::MatchExtension);
|
|
if (mime.isValid() && !mime.isDefault()) {
|
|
entry.fastInsert(UDSEntry::UDS_MIME_TYPE, mime.name());
|
|
} else {
|
|
entry.fastInsert(UDSEntry::UDS_MIME_TYPE,
|
|
QStringLiteral("text/plain"));
|
|
}
|
|
}
|
|
} else {
|
|
entry.fastInsert(UDSEntry::UDS_NAME, name);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
WorkerResult OllieWorker::listDir(const QUrl &url)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
QString path = ninepPath(url);
|
|
QStringList entries = m_client.ls(path);
|
|
if (entries.isEmpty() && !m_client.lastError().isEmpty()) {
|
|
return WorkerResult::fail(ERR_CANNOT_ENTER_DIRECTORY,
|
|
m_client.lastError());
|
|
}
|
|
|
|
// Emit "." entry for the directory itself
|
|
UDSEntry dot;
|
|
dot.fastInsert(UDSEntry::UDS_NAME, QStringLiteral("."));
|
|
dot.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
|
|
dot.fastInsert(UDSEntry::UDS_ACCESS, 0755);
|
|
listEntry(dot);
|
|
|
|
for (const QString &name : entries) {
|
|
QString childPath = path.isEmpty() ? name : (path + QLatin1Char('/') + name);
|
|
UDSEntry entry = entryFromStat(childPath, name);
|
|
listEntry(entry);
|
|
}
|
|
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
WorkerResult OllieWorker::stat(const QUrl &url)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
QString path = ninepPath(url);
|
|
QString name = url.fileName();
|
|
if (name.isEmpty())
|
|
name = QStringLiteral(".");
|
|
|
|
UDSEntry entry = entryFromStat(path, name);
|
|
if (!entry.contains(UDSEntry::UDS_FILE_TYPE)) {
|
|
if (path.isEmpty()) {
|
|
// Root directory
|
|
entry.fastInsert(UDSEntry::UDS_NAME, QStringLiteral("."));
|
|
entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
|
|
entry.fastInsert(UDSEntry::UDS_ACCESS, 0755);
|
|
entry.fastInsert(UDSEntry::UDS_MIME_TYPE,
|
|
QStringLiteral("inode/directory"));
|
|
} else {
|
|
return WorkerResult::fail(ERR_DOES_NOT_EXIST,
|
|
m_client.lastError());
|
|
}
|
|
}
|
|
|
|
statEntry(entry);
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
WorkerResult OllieWorker::get(const QUrl &url)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
QString path = ninepPath(url);
|
|
QByteArray fileData = m_client.read(path);
|
|
if (fileData.isNull() && !m_client.lastError().isEmpty()) {
|
|
return WorkerResult::fail(ERR_CANNOT_READ,
|
|
m_client.lastError());
|
|
}
|
|
|
|
QMimeDatabase db;
|
|
QMimeType mime = db.mimeTypeForFileNameAndData(url.fileName(), fileData);
|
|
mimeType(mime.name());
|
|
|
|
totalSize(fileData.size());
|
|
data(fileData);
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
WorkerResult OllieWorker::put(const QUrl &url, int /*permissions*/, KIO::JobFlags /*flags*/)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
// Read incoming data from the job
|
|
QByteArray allData;
|
|
int result;
|
|
do {
|
|
QByteArray buffer;
|
|
dataReq();
|
|
result = readData(buffer);
|
|
if (result > 0)
|
|
allData.append(buffer);
|
|
} while (result > 0);
|
|
|
|
QString path = ninepPath(url);
|
|
if (!m_client.write(path, allData)) {
|
|
return WorkerResult::fail(ERR_CANNOT_WRITE,
|
|
m_client.lastError());
|
|
}
|
|
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
WorkerResult OllieWorker::mkdir(const QUrl &url, int /*permissions*/)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
QString path = ninepPath(url);
|
|
if (!m_client.mkdir(path)) {
|
|
return WorkerResult::fail(ERR_CANNOT_MKDIR,
|
|
m_client.lastError());
|
|
}
|
|
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
WorkerResult OllieWorker::del(const QUrl &url, bool /*isFile*/)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
QString path = ninepPath(url);
|
|
if (!m_client.remove(path)) {
|
|
return WorkerResult::fail(ERR_CANNOT_DELETE,
|
|
m_client.lastError());
|
|
}
|
|
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
WorkerResult OllieWorker::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags /*flags*/)
|
|
{
|
|
if (!ensureConnected()) {
|
|
return WorkerResult::fail(ERR_CANNOT_CONNECT,
|
|
QStringLiteral("Cannot connect to Ollie 9P server: ")
|
|
+ m_client.lastError());
|
|
}
|
|
|
|
QString srcPath = ninepPath(src);
|
|
QString newName = dest.fileName();
|
|
if (!m_client.rename(srcPath, newName)) {
|
|
return WorkerResult::fail(ERR_CANNOT_RENAME,
|
|
m_client.lastError());
|
|
}
|
|
|
|
return WorkerResult::pass();
|
|
}
|
|
|
|
#include "ollie_kio.moc"
|