Commit da31ea76 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Symbian/CODA client: Add put/install/stat modes.

parent e25b69b2
......@@ -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<JsonValue> &values)
unsigned errorKeyCount = 0;
clear();
do {
if (values.isEmpty() || values.back().type() != JsonValue::Object)
if (values.isEmpty())
break;
// 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.back().children()) {
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<QByteArray> 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
......@@ -40,6 +40,7 @@
#include <QtCore/QVector>
#include <QtCore/QVariant>
#include <QtCore/QStringList>
#include <QtCore/QDateTime>
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<const TcfTrkCommandResult &> 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<QIODevice> 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<QByteArray> parseRegisterGetChildren(const TcfTrkCommandResult &r);
static TcfTrkStatResponse parseStat(const TcfTrkCommandResult &r);
signals:
void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value);
......
......@@ -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 {
......
......@@ -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.
......
......@@ -31,6 +31,8 @@
#include "tcftrkdevice.h"
#include <QtNetwork/QTcpSocket>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <cstdio>
......@@ -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;
......
......@@ -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<tcftrk::TcfTrkDevice> m_trkDevice;
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment