diff --git a/src/shared/symbianutils/tcftrkdevice.cpp b/src/shared/symbianutils/tcftrkdevice.cpp index eb97691f728..202fb18a687 100644 --- a/src/shared/symbianutils/tcftrkdevice.cpp +++ b/src/shared/symbianutils/tcftrkdevice.cpp @@ -57,13 +57,17 @@ void TcfTrkCommandError::clear() alternativeOrganization.clear(); } +QDateTime TcfTrkCommandResult::tcfTimeToQDateTime(quint64 tcfTimeMS) +{ + const QDateTime time(QDate(1970, 1, 1)); + return time.addMSecs(tcfTimeMS); +} + void TcfTrkCommandError::write(QTextStream &str) const { if (isError()) { - if (timeMS) { - const QDateTime time(QDate(1970, 1, 1)); - str << time.addMSecs(timeMS).toString(Qt::ISODate) << ": "; - } + if (timeMS) + str << TcfTrkCommandResult::tcfTimeToQDateTime(timeMS).toString(Qt::ISODate) << ": "; str << "Error code: " << code << " '" << format << '\''; if (!alternativeOrganization.isEmpty()) @@ -95,9 +99,16 @@ bool TcfTrkCommandError::parse(const QVector &values) unsigned errorKeyCount = 0; clear(); do { - if (values.isEmpty() || values.back().type() != JsonValue::Object) + if (values.isEmpty()) break; - foreach (const JsonValue &c, values.back().children()) { + // Errors are mostly appended, except for FileSystem::open, in which case + // a string "null" file handle (sic!) follows the error. + const int last = values.size() - 1; + const int checkIndex = last == 1 && values.at(last).data() == "null" ? + last - 1 : last; + if (values.at(checkIndex).type() != JsonValue::Object) + break; + foreach (const JsonValue &c, values.at(checkIndex).children()) { if (c.name() == "Time") { timeMS = c.data().toULongLong(); errorKeyCount++; @@ -213,6 +224,10 @@ QString TcfTrkCommandResult::toString() const return rc; } +TcfTrkStatResponse::TcfTrkStatResponse() : size(0) +{ +} + // Entry for send queue. enum SpecialHandlingFlags { None =0, FakeRegisterGetMIntermediate = 0x1, @@ -953,6 +968,25 @@ QVector TcfTrkDevice::parseRegisterGetChildren(const TcfTrkCommandRe return rc; } +TcfTrkStatResponse TcfTrkDevice::parseStat(const TcfTrkCommandResult &r) +{ + TcfTrkStatResponse rc; + if (!r || r.values.size() < 1 || r.values.front().type() != JsonValue::Object) + return rc; + foreach(const JsonValue &v, r.values.front().children()) { + if (v.name() == "Size") { + rc.size = v.data().toULongLong(); + } else if (v.name() == "ATime") { + if (const quint64 atime = v.data().toULongLong()) + rc.accessTime = TcfTrkCommandResult::tcfTimeToQDateTime(atime); + } else if (v.name() == "MTime") { + if (const quint64 mtime = v.data().toULongLong()) + rc.modTime = TcfTrkCommandResult::tcfTimeToQDateTime(mtime); + } + } + return rc; +} + void TcfTrkDevice::sendRegistersGetChildrenCommand(const TcfTrkCallback &callBack, const QByteArray &contextId, const QVariant &cookie) @@ -1094,4 +1128,67 @@ void TcfTrkDevice::sendLoggingAddListenerCommand(const TcfTrkCallback &callBack, sendTcfTrkMessage(MessageWithReply, LoggingService, "addListener", data, callBack, cookie); } +void tcftrk::TcfTrkDevice::sendFileSystemOpenCommand(const tcftrk::TcfTrkCallback &callBack, + const QByteArray &name, + unsigned flags, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << name << '\0' << flags << '\0' << '{' << '}'; + sendTcfTrkMessage(MessageWithReply, FileSystemService, "open", data, callBack, cookie); +} + +void tcftrk::TcfTrkDevice::sendFileSystemFstatCommand(const TcfTrkCallback &callBack, + const QByteArray &handle, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << handle; + sendTcfTrkMessage(MessageWithReply, FileSystemService, "fstat", data, callBack, cookie); +} + +void tcftrk::TcfTrkDevice::sendFileSystemWriteCommand(const tcftrk::TcfTrkCallback &callBack, + const QByteArray &handle, + const QByteArray &dataIn, + unsigned offset, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << handle << '\0' << offset << '\0' << dataIn.toBase64(); + sendTcfTrkMessage(MessageWithReply, FileSystemService, "write", data, callBack, cookie); +} + +void tcftrk::TcfTrkDevice::sendFileSystemCloseCommand(const tcftrk::TcfTrkCallback &callBack, + const QByteArray &handle, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << handle; + sendTcfTrkMessage(MessageWithReply, FileSystemService, "close", data, callBack, cookie); +} + +void tcftrk::TcfTrkDevice::sendSymbianInstallSilentInstallCommand(const tcftrk::TcfTrkCallback &callBack, + const QByteArray &file, + const QByteArray &targetDrive, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << file << '\0' << targetDrive; + sendTcfTrkMessage(MessageWithReply, SymbianInstallService, "install", data, callBack, cookie); +} + +void tcftrk::TcfTrkDevice::sendSymbianInstallUIInstallCommand(const tcftrk::TcfTrkCallback &callBack, + const QByteArray &file, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << file; + sendTcfTrkMessage(MessageWithReply, SymbianInstallService, "installWithUI", data, callBack, cookie); +} } // namespace tcftrk diff --git a/src/shared/symbianutils/tcftrkdevice.h b/src/shared/symbianutils/tcftrkdevice.h index db58b27a98e..d8cfa14f453 100644 --- a/src/shared/symbianutils/tcftrkdevice.h +++ b/src/shared/symbianutils/tcftrkdevice.h @@ -40,6 +40,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE class QIODevice; @@ -79,7 +80,8 @@ struct SYMBIANUTILS_EXPORT TcfTrkCommandError { /* Answer to a Tcf command passed to the callback. */ struct SYMBIANUTILS_EXPORT TcfTrkCommandResult { - enum Type { + enum Type + { SuccessReply, // 'R' and no error -> all happy. CommandErrorReply, // 'R' with TcfTrkCommandError received ProgressReply, // 'P', progress indicator @@ -96,6 +98,8 @@ struct SYMBIANUTILS_EXPORT TcfTrkCommandResult { QString errorString() const; operator bool() const { return type == SuccessReply || type == ProgressReply; } + static QDateTime tcfTimeToQDateTime(quint64 tcfTimeMS); + Type type; Services service; QByteArray request; @@ -104,6 +108,16 @@ struct SYMBIANUTILS_EXPORT TcfTrkCommandResult { QVariant cookie; }; +// Response to stat/fstat +struct SYMBIANUTILS_EXPORT TcfTrkStatResponse +{ + TcfTrkStatResponse(); + + quint64 size; + QDateTime modTime; + QDateTime accessTime; +}; + typedef trk::Callback TcfTrkCallback; /* TcfTrkDevice: TCF communication helper using an asynchronous QIODevice @@ -125,9 +139,23 @@ class SYMBIANUTILS_EXPORT TcfTrkDevice : public QObject Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose) Q_OBJECT public: - enum MessageType { MessageWithReply, - MessageWithoutReply, /* Non-standard: "Settings:set" command does not reply */ - NoopMessage }; + // Flags for FileSystem:open + enum FileSystemOpenFlags + { + FileSystem_TCF_O_READ = 0x00000001, + FileSystem_TCF_O_WRITE = 0x00000002, + FileSystem_TCF_O_APPEND = 0x00000004, + FileSystem_TCF_O_CREAT = 0x00000008, + FileSystem_TCF_O_TRUNC = 0x00000010, + FileSystem_TCF_O_EXCL = 0x00000020 + }; + + enum MessageType + { + MessageWithReply, + MessageWithoutReply, /* Non-standard: "Settings:set" command does not reply */ + NoopMessage + }; typedef QSharedPointer IODevicePtr; @@ -270,11 +298,42 @@ public: const QByteArray &value, // binary value const QVariant &cookie = QVariant()); + // File System + void sendFileSystemOpenCommand(const TcfTrkCallback &callBack, + const QByteArray &name, + unsigned flags = FileSystem_TCF_O_READ, + const QVariant &cookie = QVariant()); + + void sendFileSystemFstatCommand(const TcfTrkCallback &callBack, + const QByteArray &handle, + const QVariant &cookie = QVariant()); + + void sendFileSystemWriteCommand(const TcfTrkCallback &callBack, + const QByteArray &handle, + const QByteArray &data, + unsigned offset = 0, + const QVariant &cookie = QVariant()); + + void sendFileSystemCloseCommand(const TcfTrkCallback &callBack, + const QByteArray &handle, + const QVariant &cookie = QVariant()); + + // Symbian Install + void sendSymbianInstallSilentInstallCommand(const TcfTrkCallback &callBack, + const QByteArray &file, + const QByteArray &targetDrive, + const QVariant &cookie = QVariant()); + + void sendSymbianInstallUIInstallCommand(const TcfTrkCallback &callBack, + const QByteArray &file, + const QVariant &cookie = QVariant()); + void sendLoggingAddListenerCommand(const TcfTrkCallback &callBack, const QVariant &cookie = QVariant()); static QByteArray parseMemoryGet(const TcfTrkCommandResult &r); static QVector parseRegisterGetChildren(const TcfTrkCommandResult &r); + static TcfTrkStatResponse parseStat(const TcfTrkCommandResult &r); signals: void genericTcfEvent(int service, const QByteArray &name, const QVector &value); diff --git a/src/shared/symbianutils/tcftrkmessage.cpp b/src/shared/symbianutils/tcftrkmessage.cpp index 201fce1fb88..f756682397a 100644 --- a/src/shared/symbianutils/tcftrkmessage.cpp +++ b/src/shared/symbianutils/tcftrkmessage.cpp @@ -36,7 +36,7 @@ // Names matching the enum static const char *serviceNamesC[] = { "Locator", "RunControl", "Processes", "Memory", "Settings", "Breakpoints", - "Registers", "Logging", + "Registers", "Logging", "FileSystem", "SymbianInstall", "UnknownService"}; namespace tcftrk { diff --git a/src/shared/symbianutils/tcftrkmessage.h b/src/shared/symbianutils/tcftrkmessage.h index e257a884784..2754612202f 100644 --- a/src/shared/symbianutils/tcftrkmessage.h +++ b/src/shared/symbianutils/tcftrkmessage.h @@ -53,6 +53,8 @@ enum Services { BreakpointsService, RegistersService, LoggingService, // non-standard, trk specific + FileSystemService, + SymbianInstallService, // non-standard, trk specific UnknownService }; // Note: Check string array 'serviceNamesC' of same size when modifying this. diff --git a/tests/tools/codaclient/codaclientapplication.cpp b/tests/tools/codaclient/codaclientapplication.cpp index f06c37241ee..1b66a54f349 100644 --- a/tests/tools/codaclient/codaclientapplication.cpp +++ b/tests/tools/codaclient/codaclientapplication.cpp @@ -31,6 +31,8 @@ #include "tcftrkdevice.h" #include +#include +#include #include @@ -39,10 +41,12 @@ static const char usageC[] = "Test client for Symbian CODA\n\n" "Usage:\n" "%1 launch [-d] address:port binary uid [--] [arguments]\n" -"%1 install address:port sis-file targetdrive\n" -"%1 copy address:port local-file remote-file\n" +"%1 install[-s] address:port sis-file [targetdrive]\n" +"%1 put address:port local-file remote-file\n" +"%1 stat address:port remote-file\n" "\nOptions:\n" -"-d Launch under Debug control\n"; +"-d Launch: Launch under Debug control (wait for termination)\n" +"-s Install: Silent installation\n"; static const unsigned short defaultPort = 65029; @@ -59,6 +63,9 @@ CodaClientApplication::CodaClientApplication(int &argc, char **argv) : m_launchUID(0), m_launchDebug(false), m_installTargetDrive(QLatin1String("C:")), + m_installSilently(false), + m_putWriteOk(false), + m_statFstatOk(false), m_verbose(0) { setApplicationName(QLatin1String("codaclient")); @@ -80,8 +87,10 @@ static inline CodaClientApplication::Mode modeArg(const QString &a) return CodaClientApplication::Launch; if (a == QLatin1String("install")) return CodaClientApplication::Install; - if (a == QLatin1String("copy")) - return CodaClientApplication::Copy; + if (a == QLatin1String("put")) + return CodaClientApplication::Put; + if (a == QLatin1String("stat")) + return CodaClientApplication::Stat; return CodaClientApplication::Invalid; } @@ -112,8 +121,11 @@ bool CodaClientApplication::parseArgument(const QString &a, int argNumber, QStri case Install: m_installSisFile = a; break; - case Copy: - m_copyLocalFile = a; + case Put: + m_putLocalFile = a; + break; + case Stat: + m_statRemoteFile = fixSlashes(a); break; default: break; @@ -131,8 +143,8 @@ bool CodaClientApplication::parseArgument(const QString &a, int argNumber, QStri case Install: m_installTargetDrive = a; break; - case Copy: - m_copyRemoteFile = fixSlashes(a); + case Put: + m_putRemoteFile = fixSlashes(a); break; default: break; @@ -167,6 +179,9 @@ CodaClientApplication::ParseArgsResult CodaClientApplication::parseArguments(QSt case 'd': m_launchDebug = true; break; + case 's': + m_installSilently = true; + break; default: *errorMessage = QString::fromLatin1("Invalid option %1").arg(*it); return ParseArgsError; @@ -192,11 +207,17 @@ CodaClientApplication::ParseArgsResult CodaClientApplication::parseArguments(QSt return ParseInitError; } break; - case Copy: - if (m_address.isEmpty() || m_copyLocalFile.isEmpty() || m_copyRemoteFile.isEmpty()) { - *errorMessage = QString::fromLatin1("Not enough parameters for copy."); + case Put: { + if (m_address.isEmpty() || m_putLocalFile.isEmpty() || m_putRemoteFile.isEmpty()) { + *errorMessage = QString::fromLatin1("Not enough parameters for put."); return ParseInitError; } + const QFileInfo fi(m_putLocalFile); + if (!fi.isFile() || !fi.isReadable()) { + *errorMessage = QString::fromLatin1("Local file '%1' not readable.").arg(m_putLocalFile); + return ParseInitError; + } + } break; default: break; @@ -220,11 +241,15 @@ bool CodaClientApplication::start() qPrintable(m_installSisFile), qPrintable(m_installTargetDrive), qPrintable(m_address), m_port); break; - case Copy: + case Put: std::printf("Copying '%s' to '%s' on %s:%hu\n", - qPrintable(m_copyLocalFile), qPrintable(m_copyRemoteFile), + qPrintable(m_putLocalFile), qPrintable(m_putRemoteFile), qPrintable(m_address), m_port); break; + case Stat: + std::printf("Retrieving attributes of '%s' from %s:%hu\n", + qPrintable(m_statRemoteFile), qPrintable(m_address), m_port); + break; case Invalid: break; } @@ -268,6 +293,91 @@ void CodaClientApplication::handleCreateProcess(const tcftrk::TcfTrkCommandResul } } +void CodaClientApplication::handleFileSystemOpen(const tcftrk::TcfTrkCommandResult &result) +{ + if (result.type != tcftrk::TcfTrkCommandResult::SuccessReply) { + std::fprintf(stderr, "Open remote file failed: %s\n", qPrintable(result.toString())); + doExit(-1); + return; + } + + if (result.values.size() < 1 || result.values.at(0).data().isEmpty()) { + std::fprintf(stderr, "Internal error: No filehandle obtained\n"); + doExit(-1); + return; + } + + m_remoteFileHandle = result.values.at(0).data(); + + if (m_mode == Stat) { + m_trkDevice->sendFileSystemFstatCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemFStat), + m_remoteFileHandle); + return; + } + // Put. + QFile localFile(m_putLocalFile); + if (!localFile.open(QIODevice::ReadOnly)) { // Should not fail, was checked before + std::fprintf(stderr, "Open local file failed: %s\n", qPrintable(localFile.errorString())); + doExit(-1); + return; + } + const QByteArray data = localFile.readAll(); + localFile.close(); + std::printf("Writing %d bytes to remote file '%s'\n", data.size(), m_remoteFileHandle.constData()); + m_trkDevice->sendFileSystemWriteCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemWrite), + m_remoteFileHandle, data); +} + +void CodaClientApplication::handleFileSystemWrite(const tcftrk::TcfTrkCommandResult &result) +{ + // Close remote file even if copy fails + m_putWriteOk = result.type == tcftrk::TcfTrkCommandResult::SuccessReply; + if (!m_putWriteOk) + std::fprintf(stderr, "Writing data failed: %s\n", qPrintable(result.toString())); + m_trkDevice->sendFileSystemCloseCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemClose), + m_remoteFileHandle); +} + +void CodaClientApplication::handleFileSystemFStat(const tcftrk::TcfTrkCommandResult &result) +{ + m_statFstatOk = result.type == tcftrk::TcfTrkCommandResult::SuccessReply; + // Close remote file even if copy fails + if (m_statFstatOk) { + const tcftrk::TcfTrkStatResponse statr = tcftrk::TcfTrkDevice::parseStat(result); + std::printf("File: %s\nSize: %llu bytes\nAccessed: %s\nModified: %s\n", + qPrintable(m_statRemoteFile), statr.size, + qPrintable(statr.accessTime.toString(Qt::LocalDate)), + qPrintable(statr.modTime.toString(Qt::LocalDate))); + } else { + std::fprintf(stderr, "FStat failed: %s\n", qPrintable(result.toString())); + } + m_trkDevice->sendFileSystemCloseCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemClose), + m_remoteFileHandle); +} + +void CodaClientApplication::handleFileSystemClose(const tcftrk::TcfTrkCommandResult &result) +{ + if (result.type == tcftrk::TcfTrkCommandResult::SuccessReply) { + std::printf("File closed.\n."); + const bool ok = m_mode == Put ? m_putWriteOk : m_statFstatOk; + doExit(ok ? 0 : -1); + } else { + std::fprintf(stderr, "File close failed: %s\n", qPrintable(result.toString())); + doExit(-1); + } +} + +void CodaClientApplication::handleSymbianInstall(const tcftrk::TcfTrkCommandResult &result) +{ + if (result.type == tcftrk::TcfTrkCommandResult::SuccessReply) { + std::printf("Installation succeeded\n."); + doExit(0); + } else { + std::fprintf(stderr, "Installation failed: %s\n", qPrintable(result.toString())); + doExit(-1); + } +} + void CodaClientApplication::slotTcftrkEvent (const tcftrk::TcfTrkEvent &ev) { std::printf("Event: %s\n", qPrintable(ev.toString())); @@ -279,8 +389,30 @@ void CodaClientApplication::slotTcftrkEvent (const tcftrk::TcfTrkEvent &ev) m_launchBinary, m_launchUID, m_launchArgs, QString(), m_launchDebug); break; case Install: + if (m_installSilently) { + m_trkDevice->sendSymbianInstallSilentInstallCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleSymbianInstall), + m_installSisFile.toAscii(), m_installTargetDrive.toAscii()); + } else { + m_trkDevice->sendSymbianInstallUIInstallCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleSymbianInstall), + m_installSisFile.toAscii()); + } break; - case Copy: + case Put: { + const unsigned flags = + tcftrk::TcfTrkDevice::FileSystem_TCF_O_WRITE + |tcftrk::TcfTrkDevice::FileSystem_TCF_O_CREAT + |tcftrk::TcfTrkDevice::FileSystem_TCF_O_TRUNC; + m_putWriteOk = false; + m_trkDevice->sendFileSystemOpenCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemOpen), + m_putRemoteFile.toAscii(), flags); +} + break; + case Stat: { + const unsigned flags = tcftrk::TcfTrkDevice::FileSystem_TCF_O_READ; + m_statFstatOk = false; + m_trkDevice->sendFileSystemOpenCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemOpen), + m_statRemoteFile.toAscii(), flags); +} break; case Invalid: break; diff --git a/tests/tools/codaclient/codaclientapplication.h b/tests/tools/codaclient/codaclientapplication.h index cbe7a37b164..ce2f604b727 100644 --- a/tests/tools/codaclient/codaclientapplication.h +++ b/tests/tools/codaclient/codaclientapplication.h @@ -44,7 +44,7 @@ class CodaClientApplication : public QCoreApplication { Q_OBJECT public: - enum Mode { Invalid, Launch, Copy, Install }; + enum Mode { Invalid, Launch, Put, Stat, Install }; explicit CodaClientApplication(int &argc, char **argv); ~CodaClientApplication(); @@ -64,6 +64,11 @@ private slots: private: bool parseArgument(const QString &a, int argNumber, QString *errorMessage); void handleCreateProcess(const tcftrk::TcfTrkCommandResult &result); + void handleFileSystemOpen(const tcftrk::TcfTrkCommandResult &result); + void handleFileSystemWrite(const tcftrk::TcfTrkCommandResult &result); + void handleFileSystemClose(const tcftrk::TcfTrkCommandResult &result); + void handleFileSystemFStat(const tcftrk::TcfTrkCommandResult &result); + void handleSymbianInstall(const tcftrk::TcfTrkCommandResult &result); void doExit(int ex); Mode m_mode; @@ -75,8 +80,13 @@ private: bool m_launchDebug; QString m_installSisFile; QString m_installTargetDrive; - QString m_copyLocalFile; - QString m_copyRemoteFile; + bool m_installSilently; + QString m_putLocalFile; + QString m_putRemoteFile; + bool m_putWriteOk; + bool m_statFstatOk; + QString m_statRemoteFile; + QByteArray m_remoteFileHandle; unsigned m_verbose; QScopedPointer m_trkDevice; };