Save/Open Session - added UI and related infra to invoke OssnFileFormat; code to build sessionContent incomplete

This commit is contained in:
Srivats P 2016-04-14 07:44:07 +05:30
parent 2a77f73e9c
commit bcb5376f9d
16 changed files with 396 additions and 9 deletions

View File

@ -27,12 +27,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portstatswindow.h"
#include "portswindow.h"
#include "preferences.h"
#include "sessionfileformat.h"
#include "settings.h"
#include "ui_about.h"
#include "updater.h"
#include "fileformat.pb.h"
#include <QDockWidget>
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
#include <QProgressDialog>
extern const char* version;
extern const char* revision;
@ -75,7 +81,7 @@ MainWindow::MainWindow(QWidget *parent)
setupUi(this);
menuFile->insertActions(menuFile->actions().at(0), portsWindow->actions());
menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions());
statsDock->setWidget(statsWindow);
addDockWidget(Qt::BottomDockWidgetArea, statsDock);
@ -133,6 +139,83 @@ MainWindow::~MainWindow()
delete localServer_;
}
void MainWindow::on_actionOpenSession_triggered()
{
qDebug("Open Session Action");
static QString dirName;
QString fileName;
QString errorStr;
bool ret;
fileName = QFileDialog::getOpenFileName(this, tr("Open Session"), dirName);
if (fileName.isEmpty())
goto _exit;
if (portsWindow->portGroupCount()) {
if (QMessageBox::question(this,
tr("Open Session"),
tr("Existing session will be lost. Proceed?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) == QMessageBox::No)
goto _exit;
}
ret = openSession(fileName, errorStr);
if (!ret || !errorStr.isEmpty()) {
QMessageBox msgBox(this);
QStringList str = errorStr.split("\n\n\n\n");
msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical);
msgBox.setWindowTitle(qApp->applicationName());
msgBox.setText(str.at(0));
if (str.size() > 1)
msgBox.setDetailedText(str.at(1));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
dirName = QFileInfo(fileName).absolutePath();
_exit:
return;
}
void MainWindow::on_actionSaveSession_triggered()
{
qDebug("Save Session Action");
static QString fileName;
QStringList fileTypes = SessionFileFormat::supportedFileTypes();
QString fileType;
QString errorStr;
QFileDialog::Options options;
// On Mac OS with Native Dialog, getSaveFileName() ignores fileType.
// Although currently there's only one supported file type, we may
// have more in the future
#if defined(Q_OS_MAC)
options |= QFileDialog::DontUseNativeDialog;
#endif
if (fileTypes.size())
fileType = fileTypes.at(0);
fileName = QFileDialog::getSaveFileName(this, tr("Save Session"),
fileName, fileTypes.join(";;"), &fileType, options);
if (fileName.isEmpty())
goto _exit;
if (!saveSession(fileName, fileType, errorStr))
QMessageBox::critical(this, qApp->applicationName(), errorStr);
else if (!errorStr.isEmpty())
QMessageBox::warning(this, qApp->applicationName(), errorStr);
fileName = QFileInfo(fileName).absolutePath();
_exit:
return;
}
void MainWindow::on_actionPreferences_triggered()
{
Preferences *preferences = new Preferences();
@ -172,3 +255,118 @@ void MainWindow::onNewVersion(QString newVersion)
statusBar()->showMessage(QString("New Ostinato version %1 available. "
"Visit http://ostinato.org to download").arg(newVersion));
}
//! Returns true on success (or user cancel) and false on failure
bool MainWindow::openSession(QString fileName, QString &error)
{
bool ret = false;
QDialog *optDialog;
QProgressDialog progress("Opening Session", "Cancel", 0, 0, this);
OstProto::SessionContent session;
SessionFileFormat *fmt = SessionFileFormat::fileFormatFromFile(fileName);
if (fmt == NULL)
goto _fail;
if ((optDialog = fmt->openOptionsDialog()))
{
int ret;
optDialog->setParent(this, Qt::Dialog);
ret = optDialog->exec();
optDialog->setParent(0, Qt::Dialog);
if (ret == QDialog::Rejected)
goto _user_opt_cancel;
}
progress.setAutoReset(false);
progress.setAutoClose(false);
progress.setMinimumDuration(0);
progress.show();
setDisabled(true);
progress.setEnabled(true); // to override the mainWindow disable
connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString)));
connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int)));
connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
fmt->openAsync(fileName, session, error);
qDebug("after open async");
while (!fmt->isFinished())
qApp->processEvents();
qDebug("wait over for async operation");
if (!fmt->result())
goto _fail;
// process any remaining events posted from the thread
for (int i = 0; i < 10; i++)
qApp->processEvents();
// XXX: user can't cancel operation from here on!
progress.close();
portsWindow->openSession(&session, error);
_user_opt_cancel:
ret = true;
_fail:
progress.close();
setEnabled(true);
return ret;
}
bool MainWindow::saveSession(QString fileName, QString fileType, QString &error)
{
bool ret = false;
QProgressDialog progress("Saving Session", "Cancel", 0, 0, this);
SessionFileFormat *fmt = SessionFileFormat::fileFormatFromType(fileType);
OstProto::SessionContent session;
if (fmt == NULL)
goto _fail;
progress.setAutoReset(false);
progress.setAutoClose(false);
progress.setMinimumDuration(0);
progress.show();
setDisabled(true);
progress.setEnabled(true); // to override the mainWindow disable
// Fill in session
ret = portsWindow->saveSession(&session, error, &progress);
if (!ret)
goto _user_cancel;
connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString)));
connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int)));
connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
fmt->saveAsync(session, fileName, error);
qDebug("after save async");
while (!fmt->isFinished())
qApp->processEvents();
qDebug("wait over for async operation");
ret = fmt->result();
goto _exit;
_user_cancel:
goto _exit;
_fail:
error = QString("Unsupported File Type - %1").arg(fileType);
goto _exit;
_exit:
progress.close();
setEnabled(true);
return ret;
}

View File

@ -34,6 +34,9 @@ class MainWindow : public QMainWindow, private Ui::MainWindow
Q_OBJECT
private:
bool openSession(QString fileName, QString &error);
bool saveSession(QString fileName, QString fileType, QString &error);
QProcess *localServer_;
PortsWindow *portsWindow;
PortStatsWindow *statsWindow;
@ -48,6 +51,8 @@ public:
~MainWindow();
public slots:
void on_actionOpenSession_triggered();
void on_actionSaveSession_triggered();
void on_actionPreferences_triggered();
void on_actionViewRestoreDefaults_triggered();
void on_actionHelpAbout_triggered();

View File

@ -21,6 +21,8 @@
<property name="title" >
<string>&amp;File</string>
</property>
<addaction name="actionOpenSession" />
<addaction name="actionSaveSession" />
<addaction name="separator" />
<addaction name="actionPreferences" />
<addaction name="actionFileExit" />
@ -89,6 +91,16 @@
<string>Restore &amp;Defaults</string>
</property>
</action>
<action name="actionOpenSession" >
<property name="text" >
<string>Open Session ...</string>
</property>
</action>
<action name="actionSaveSession" >
<property name="text" >
<string>Save Session ...</string>
</property>
</action>
</widget>
<resources>
<include location="ostinato.qrc" />

View File

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "settings.h"
#include "emulproto.pb.h"
#include "fileformat.pb.h"
#include <QApplication>
#include <QCursor>
@ -50,6 +51,8 @@ PortGroup::PortGroup(QString serverName, quint16 port)
statsController = new PbRpcController(portIdList_, portStatsList_);
isGetStatsPending_ = false;
atConnectConfig_ = NULL;
compat = kUnknown;
reconnect = false;
@ -91,8 +94,21 @@ PortGroup::~PortGroup()
delete serviceStub;
delete rpcChannel;
delete statsController;
delete atConnectConfig_;
}
void PortGroup::setConfigAtConnect(const OstProto::PortGroupContent *config)
{
if (!config) {
delete atConnectConfig_;
atConnectConfig_ = NULL;
return;
}
if (!atConnectConfig_)
atConnectConfig_ = config->New();
atConnectConfig_->CopyFrom(*config);
}
// ------------------------------------------------
// Slots
@ -168,6 +184,14 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller)
compat = kCompatible;
if (atConnectConfig_)
{
// TODO: apply config
delete atConnectConfig_;
atConnectConfig_ = NULL;
}
else
{
OstProto::Void *void_ = new OstProto::Void;
OstProto::PortIdList *portIdList = new OstProto::PortIdList;

View File

@ -36,6 +36,10 @@ LOW
#define DEFAULT_SERVER_PORT 7878
namespace OstProto {
class PortGroupContent;
}
class QFile;
class QTimer;
@ -62,6 +66,8 @@ private:
OstProto::PortIdList *portIdList_;
OstProto::PortStatsList *portStatsList_;
OstProto::PortGroupContent *atConnectConfig_;
public: // FIXME(HIGH): member access
QList<Port*> mPorts;
@ -82,6 +88,8 @@ public:
}
void disconnectFromHost() { reconnect = false; rpcChannel->tearDown(); }
void setConfigAtConnect(const OstProto::PortGroupContent *config);
int numPorts() const { return mPorts.size(); }
quint32 id() const { return mPortGroupId; }

View File

@ -126,6 +126,20 @@ void PortGroupList::removePortGroup(PortGroup &portGroup)
mPortStatsModel.when_portListChanged();
}
void PortGroupList::removeAllPortGroups()
{
while (!mPortGroups.isEmpty()) {
PortGroup *pg = mPortGroups.at(0);
mPortGroupListModel.portGroupAboutToBeRemoved(pg);
mPortGroups.removeFirst();
delete pg;
}
mPortGroupListModel.portGroupRemoved();
mPortGroupListModel.when_portListChanged();
mPortStatsModel.when_portListChanged();
}
//....................
// Private Methods
//....................

View File

@ -72,6 +72,7 @@ public:
void addPortGroup(PortGroup &portGroup);
void removePortGroup(PortGroup &portGroup);
void removeAllPortGroups();
private:
int indexOfPortGroup(quint32 portGroupId);

View File

@ -57,6 +57,7 @@ private:
QIcon portIconFactory[kLinkStatesCount][kExclusiveStatesCount];
private slots:
// FIXME: these are invoked from outside - how come they are "private"?
void when_portGroupDataChanged(int portGroupId, int portId);
void portGroupAboutToBeAppended();

View File

@ -26,12 +26,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "streamconfigdialog.h"
#include "streamlistdelegate.h"
#include "fileformat.pb.h"
#include <QFileInfo>
#include <QInputDialog>
#include <QItemSelectionModel>
#include <QMainWindow>
#include <QMessageBox>
#include <QSortFilterProxyModel>
extern QMainWindow *mainWindow;
PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
: QWidget(parent), proxyPortModel(NULL)
{
@ -168,6 +173,75 @@ PortsWindow::~PortsWindow()
delete proxyPortModel;
}
int PortsWindow::portGroupCount()
{
return plm->numPortGroups();
}
//! Always return true
bool PortsWindow::openSession(
const OstProto::SessionContent *session,
QString &error)
{
QProgressDialog progress("Opening Session", NULL,
0, session->port_groups_size(), mainWindow);
progress.show();
progress.setEnabled(true); // since parent (mainWindow) is disabled
plm->removeAllPortGroups();
for (int i = 0; i < session->port_groups_size(); i++) {
const OstProto::PortGroupContent &pgc = session->port_groups(i);
PortGroup *pg = new PortGroup(QString::fromStdString(
pgc.server_name()),
quint16(pgc.server_port()));
pg->setConfigAtConnect(&pgc);
plm->addPortGroup(*pg);
progress.setValue(i+1);
}
return true;
}
/*!
* Prepare content to be saved for a session
*
* Returns false, if user cancels op; true, otherwise
*/
bool PortsWindow::saveSession(
OstProto::SessionContent *session, // OUT param
QString &error,
QProgressDialog *progress)
{
int n = portGroupCount();
if (progress) {
progress->setLabelText("Preparing Ports and PortGroups ...");
progress->setRange(0, n);
}
for (int i = 0; i < n; i++)
{
OstProto::PortGroupContent *pgc = session->add_port_groups();
PortGroup &pg = plm->portGroupByIndex(i);
pgc->set_server_name(pg.serverName().toStdString());
pgc->set_server_port(pg.serverPort());
// TODO: ports
if (progress) {
if (progress->wasCanceled())
return false;
progress->setValue(i);
}
if (i % 2 == 0)
qApp->processEvents();
}
return true;
}
void PortsWindow::showMyReservedPortsOnly(bool enabled)
{
if (!proxyPortModel)

View File

@ -26,8 +26,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portgrouplist.h"
class QAbstractItemDelegate;
class QProgressDialog;
class QSortFilterProxyModel;
namespace OstProto {
class SessionContent;
}
class PortsWindow : public QWidget, private Ui::PortsWindow
{
Q_OBJECT
@ -39,6 +44,14 @@ public:
PortsWindow(PortGroupList *pgl, QWidget *parent = 0);
~PortsWindow();
int portGroupCount();
bool openSession(const OstProto::SessionContent *session,
QString &error);
bool saveSession(OstProto::SessionContent *session,
QString &error,
QProgressDialog *progress = NULL);
signals:
void currentPortChanged(const QModelIndex &current,
const QModelIndex &previous);

View File

@ -32,8 +32,8 @@ bool FileFormat::openStreams(const QString fileName,
{
OstProto::FileMeta meta;
OstProto::FileContent content;
bool ret = NativeFileFormat::open(fileName, meta, content, error);
bool ret = NativeFileFormat::open(fileName, OstProto::kStreamsFileType,
meta, content, error);
if (!ret)
goto _fail;

View File

@ -45,7 +45,9 @@ message PortContent {
message PortGroupContent {
optional string server_name = 1;
repeated PortContent ports = 2;
optional uint32 server_port = 2;
repeated PortContent ports = 15;
}
message SessionContent {

View File

@ -31,6 +31,22 @@ const std::string NativeFileFormat::kFileMagicValue = "\xa7\xb7OSTINATO";
static const int kBaseHex = 16;
static QString fileTypeStr(OstProto::FileType fileType)
{
switch (fileType) {
case OstProto::kReservedFileType:
return QString("Reserved");
case OstProto::kStreamsFileType:
return QString("Streams");
case OstProto::kSessionFileType:
return QString("Streams");
default:
Q_ASSERT(false);
}
return QString("Unknown");
}
NativeFileFormat::NativeFileFormat()
{
/*
@ -54,6 +70,7 @@ NativeFileFormat::NativeFileFormat()
bool NativeFileFormat::open(
const QString fileName,
OstProto::FileType fileType,
OstProto::FileMeta &meta,
OstProto::FileContent &content,
QString &error)
@ -137,7 +154,7 @@ bool NativeFileFormat::open(
qDebug("%s: END MetaData", __FUNCTION__);
// MetaData Validation(s)
if (meta.data().file_type() != OstProto::kStreamsFileType)
if (meta.data().file_type() != fileType)
goto _unexpected_file_type;
if (meta.data().format_version_major() != kFileFormatVersionMajor)
@ -196,7 +213,9 @@ _incompatible_file_version:
.arg(kFileFormatVersionRevision);
goto _fail;
_unexpected_file_type:
error = QString(tr("%1 is not a streams file")).arg(fileName);
error = QString(tr("%1 is not a %2 file"))
.arg(fileName)
.arg(fileTypeStr(fileType));
goto _fail;
_metadata_parse_fail:
error = QString(tr("Failed parsing %1 meta data")).arg(fileName);

View File

@ -41,6 +41,7 @@ public:
NativeFileFormat();
bool open(const QString fileName,
OstProto::FileType fileType,
OstProto::FileMeta &meta,
OstProto::FileContent &content,
QString &error);

View File

@ -32,8 +32,8 @@ bool OssnFileFormat::open(const QString fileName,
{
OstProto::FileMeta meta;
OstProto::FileContent content;
bool ret = NativeFileFormat::open(fileName, meta, content, error);
bool ret = NativeFileFormat::open(fileName, OstProto::kSessionFileType,
meta, content, error);
if (!ret)
goto _exit;
@ -42,7 +42,7 @@ bool OssnFileFormat::open(const QString fileName,
postParseFixup(meta.data(), content);
session.CopyFrom(content.matter().streams());
session.CopyFrom(content.matter().session());
return true;

15
test/TODO.md Normal file
View File

@ -0,0 +1,15 @@
# TODO - Test Cases
## Session Save/Open
* Verify each save session triggers the file dialog at the last path used
* OSSN Session file format tests (TODO: expand)
* Verify no file is saved if user clicks 'Cancel' on the progress dialog while saving session file
* On open session, verify user is prompted if there are existing portgroups and not if there are no port groups
* Verify each open session triggers the file dialog at the last path used
* OSSN Session file format tests (TODO: expand)
* Verify no change in existing port groups if user clicks 'Cancel' on the options dialog
* Verify no change in existing port groups if user clicks 'Cancel' on the progress dialog while opening session file
* Verify all old portgroups are removed before new portgroups from the session file are added
* Verify config of portGroups loaded from the session file are correct
* Verify config of a portGroup is NOT restored to the config saved in session file after a open session - disconnect - connect