add commond file

This commit is contained in:
brige 2025-10-13 08:20:53 +08:00
parent ddd69461dd
commit 51d84229da
7 changed files with 268 additions and 27 deletions

View File

@ -95,6 +95,8 @@ void WorkSpaceDlg::OnSure() {
WorkSpace* workSpace = WorkSpaceManager::Get().GetOrCreate(workspacePath, name);
workSpace->SetDescribe(ui->etDescribe->toPlainText());
workSpace->SetCommondFilePath(commondPath_);
// Execute commands configured for onCreate right after workspace is set up
workSpace->ExecuteCommands(WorkSpace::CommandWhen::OnCreate);
WorkSpaceManager::Get().SetCurrent(workSpace);
accept();
@ -115,33 +117,17 @@ void WorkSpaceDlg::OnSelectSavePath() {
void WorkSpaceDlg::OnSelectCommondPath() {
const QString workspacePath = Application::GetWorkSpacePath();
const QString savePath = QFileDialog::getExistingDirectory(this,
tr("select commond file directory"), workspacePath, QFileDialog::DontResolveSymlinks);
if (savePath.isEmpty()) {
LOG_WARN("save commond file is empty");
const QString xmlPath = QFileDialog::getOpenFileName(
this,
tr("select command xml file"),
workspacePath,
tr("XML files (*.xml);;All files (*.*)"));
if (xmlPath.isEmpty()) {
LOG_WARN("command xml file is empty");
return;
}
commondPath_ = savePath;
ui->leCommondPath->setText(QString("%1").arg(commondPath_));
LOG_INFO("select path: {}", commondPath_.toLocal8Bit().constData());
commondPath_ = xmlPath;
ui->leCommondPath->setText(commondPath_);
LOG_INFO("select command xml: {}", commondPath_.toLocal8Bit().constData());
}
//
//void WorkSpaceDlg::InitFrame() {
// FrameTitleBar* titleBar = new FrameTitleBar(this);
// titleBar->SetMainWidget(this);
//
// titleBar->SetSysButton(FrameTitleBar::FTB_ICON | FrameTitleBar::FTB_CLOSE);
//
// QVBoxLayout* layout = new QVBoxLayout(this);
// layout->setContentsMargins(0, 0, 0, 0);
// layout->setSpacing(0);
//
// layout->setStretch(0, 0);
// layout->setStretch(1, 1);
// layout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
// SetTitleBar(titleBar);
//
// QWidget* mainDilag_ = new QWidget(this);
// layout->addWidget(mainDilag_, 1);
// ui->setupUi(mainDilag_);
//}

View File

@ -0,0 +1,68 @@
#include "workspace/CommandExecutor.h"
#include <QFileInfo>
#include <QProcess>
#include <QProcessEnvironment>
#include "common/SpdLogger.h"
void CommandExecutor::Execute(WorkSpace* ws, WorkSpace::CommandWhen when) {
if (!ws) return;
if (!cmd_.enabled) return;
const QString whenStr = (when == WorkSpace::CommandWhen::OnCreate) ? QStringLiteral("oncreate") : QStringLiteral("onload");
// Build final arguments (already prepared by manager but honor rawArgs if provided)
QStringList argsList = cmd_.args;
auto pushArgs = [&argsList](const QString& s) {
if (!s.isEmpty()) {
for (const auto& part : s.split(' ', Qt::SkipEmptyParts)) {
argsList << part;
}
}
};
if (!cmd_.rawArgs.isEmpty()) {
pushArgs(cmd_.rawArgs);
}
const QString programLower = cmd_.program.toLower();
if (!cmd_.path.isEmpty()) {
if (programLower.endsWith("cmd.exe")) {
argsList << "/c" << cmd_.path;
} else if (programLower.endsWith("powershell.exe")) {
argsList << "-NoProfile" << "-ExecutionPolicy" << "Bypass" << "-File" << cmd_.path;
} else {
argsList << cmd_.path;
}
}
QProcess proc;
// Apply environment if provided
if (!cmd_.env.empty()) {
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
for (auto it = cmd_.env.begin(); it != cmd_.env.end(); ++it) {
env.insert(it.key(), it.value());
}
proc.setProcessEnvironment(env);
}
proc.setProgram(cmd_.program);
proc.setArguments(argsList);
proc.setWorkingDirectory(cmd_.workingDir.isEmpty() ? ws->GetDir() : cmd_.workingDir);
LOG_INFO("run command: name={} prog={} args={} cwd={} when={} desc={}",
cmd_.name.toLocal8Bit().constData(),
cmd_.program.toLocal8Bit().constData(),
argsList.join(' ').toLocal8Bit().constData(),
proc.workingDirectory().toLocal8Bit().constData(),
whenStr.toLocal8Bit().constData(),
cmd_.descript.toLocal8Bit().constData());
proc.start();
if (!proc.waitForStarted()) {
LOG_WARN("command failed to start: {}", cmd_.program.toLocal8Bit().constData());
return;
}
proc.waitForFinished(cmd_.timeoutMs);
const QByteArray out = proc.readAllStandardOutput();
const QByteArray err = proc.readAllStandardError();
LOG_INFO("command '{}' exitCode={} stdout={} stderr={}", cmd_.name.toLocal8Bit().constData(), proc.exitCode(), out.constData(), err.constData());
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <QString>
#include <QStringList>
#include <QMap>
#include "workspace/WorkSpace.h"
// Merge Command model into this header to reduce files
struct Command {
QString name;
QString program;
QStringList args; // final argument list to pass to QProcess
QString rawArgs; // original args string from XML (optional)
QString path; // script or executable path
QString workingDir; // working directory
bool enabled{true};
QMap<QString, QString> env; // environment key/value pairs
QString descript; // description
int timeoutMs{30000}; // default 30s
};
class CommandExecutor {
public:
explicit CommandExecutor(const Command& cmd) : cmd_(cmd) {}
void Execute(WorkSpace* ws, WorkSpace::CommandWhen when);
const Command& Get() const { return cmd_; }
private:
Command cmd_;
};

View File

@ -0,0 +1,117 @@
#include "workspace/CommandManager.h"
#include <QFileInfo>
#include "xml/tinyxml2.h"
#include "common/SpdLogger.h"
static QMap<QString, QString> parseEnvAttr(const QString& envAttr) {
QMap<QString, QString> env;
if (envAttr.isEmpty()) return env;
const auto pairs = envAttr.split(';', Qt::SkipEmptyParts);
for (const auto& p : pairs) {
const auto kv = p.split('=', Qt::KeepEmptyParts);
if (kv.size() >= 2) env.insert(kv[0].trimmed(), kv[1].trimmed());
}
return env;
}
void CommandManager::Reload(WorkSpace* ws) {
onCreate_.clear();
onLoad_.clear();
if (!ws) return;
const QString cmdPath = ws->GetCommondFilePath();
if (cmdPath.isEmpty()) {
LOG_INFO("no command xml configured");
return;
}
QFileInfo fi(cmdPath);
if (!fi.exists() || !fi.isFile()) {
LOG_WARN("command xml not found: {}", cmdPath.toLocal8Bit().constData());
return;
}
tinyxml2::XMLDocument doc;
auto rc = doc.LoadFile(cmdPath.toLocal8Bit().constData());
if (rc != tinyxml2::XML_SUCCESS) {
LOG_WARN("load command xml failed: {} rc:{}", cmdPath.toLocal8Bit().constData(), rc);
return;
}
auto* root = doc.RootElement();
if (!root) {
LOG_WARN("command xml has no root: {}", cmdPath.toLocal8Bit().constData());
return;
}
for (auto* node = root->FirstChildElement(); node; node = node->NextSiblingElement()) {
const char* tag = node->Name();
if (!tag) continue;
QString tagQ = QString::fromUtf8(tag).toLower();
if (tagQ != QLatin1String("commond") && tagQ != QLatin1String("command")) continue;
Command cmd;
if (const char* nameAttr = node->Attribute("name")) cmd.name = QString::fromUtf8(nameAttr);
if (const char* exeAttr = node->Attribute("exe")) cmd.program = QString::fromUtf8(exeAttr);
if (cmd.program.isEmpty()) {
if (const char* programAttr = node->Attribute("program")) cmd.program = QString::fromUtf8(programAttr);
}
if (const char* pathAttr = node->Attribute("path")) cmd.path = QString::fromUtf8(pathAttr);
if (cmd.path.isEmpty()) {
if (const char* pathTypo = node->Attribute("paht")) cmd.path = QString::fromUtf8(pathTypo);
}
if (const char* argsAttr = node->Attribute("args")) cmd.rawArgs = QString::fromUtf8(argsAttr);
if (const char* cwdAttr = node->Attribute("workingDir")) cmd.workingDir = QString::fromUtf8(cwdAttr);
if (cmd.workingDir.isEmpty()) {
if (const char* cwdAttr2 = node->Attribute("cwd")) cmd.workingDir = QString::fromUtf8(cwdAttr2);
}
if (const char* enabledAttr = node->Attribute("enabled")) {
QString en = QString::fromUtf8(enabledAttr).toLower();
cmd.enabled = !(en == QLatin1String("false") || en == QLatin1String("0"));
}
if (const char* descAttr = node->Attribute("descript")) cmd.descript = QString::fromUtf8(descAttr);
if (cmd.descript.isEmpty()) {
if (const char* desc2 = node->Attribute("description")) cmd.descript = QString::fromUtf8(desc2);
}
if (const char* timeoutAttr = node->Attribute("timeoutSec")) {
bool ok = false; int v = QString::fromUtf8(timeoutAttr).toInt(&ok);
if (ok && v > 0) cmd.timeoutMs = v * 1000;
}
// env: either attribute env="KEY=VAL;K2=V2" or child elements <env key="" value=""/>
if (const char* envAttr = node->Attribute("env")) {
cmd.env = parseEnvAttr(QString::fromUtf8(envAttr));
}
for (auto* envNode = node->FirstChildElement("env"); envNode; envNode = envNode->NextSiblingElement("env")) {
const char* k = envNode->Attribute("key");
const char* v = envNode->Attribute("value");
if (k && v) cmd.env.insert(QString::fromUtf8(k), QString::fromUtf8(v));
}
// Pre-build args list from rawArgs (actual insertion of path happens in executor)
if (!cmd.rawArgs.isEmpty()) {
for (const auto& part : cmd.rawArgs.split(' ', Qt::SkipEmptyParts)) {
cmd.args << part;
}
}
// when routing
WorkSpace::CommandWhen target = WorkSpace::CommandWhen::OnCreate; // default
if (const char* whenAttr = node->Attribute("when")) {
QString wa = QString::fromUtf8(whenAttr).toLower();
if (wa == QLatin1String("onload")) target = WorkSpace::CommandWhen::OnLoad;
}
auto exec = std::make_unique<CommandExecutor>(cmd);
if (target == WorkSpace::CommandWhen::OnCreate) onCreate_.push_back(std::move(exec));
else onLoad_.push_back(std::move(exec));
}
}
void CommandManager::Execute(WorkSpace* ws, WorkSpace::CommandWhen when) {
// Reload each time to reflect latest XML
Reload(ws);
auto& list = (when == WorkSpace::CommandWhen::OnCreate) ? onCreate_ : onLoad_;
for (auto& exec : list) {
exec->Execute(ws, when);
}
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <memory>
#include <vector>
#include "workspace/WorkSpace.h"
#include "workspace/CommandExecutor.h"
class CommandManager {
public:
void Reload(WorkSpace* ws);
void Execute(WorkSpace* ws, WorkSpace::CommandWhen when);
private:
std::vector<std::unique_ptr<CommandExecutor>> onCreate_;
std::vector<std::unique_ptr<CommandExecutor>> onLoad_;
};

View File

@ -6,6 +6,7 @@
#include "workspace/WorkSpaceXMLParse.h"
#include "workspace/WorkSpaceXMLWrite.h"
#include "workspace/CommandManager.h"
#include "workspace/WorkSpaceItem.h"
#include "workspace/Timestep.h"
@ -15,6 +16,7 @@
#include "common/SpdLogger.h"
#include "entities/Entity.h"
#include "utils/FileUtils.h"
#include <QProcess>
//#include "workspace/WorkSpaceItemGroup.h"
//#include "workspace/WorkSpaceRiverGroup.h"
//#include "workspace/WorkSpaceRiverNetGroup.h"
@ -25,6 +27,7 @@ WorkSpace::WorkSpace(QObject* parent) noexcept
: QObject(parent) {
uuid_ = QUuid::createUuid().toString();
homeViewpoint_ = osgEarth::Viewpoint("home", 120.000000, 25.000000, 100.000000, -2.500000, -90.000000, 8200000.000000);
cmdMgr_ = std::make_unique<CommandManager>();
}
WorkSpace::WorkSpace(const QString& path, QObject* parent)
@ -32,6 +35,7 @@ WorkSpace::WorkSpace(const QString& path, QObject* parent)
, path_(path){
uuid_ = QUuid::createUuid().toString();
homeViewpoint_ = osgEarth::Viewpoint("home", 120.000000, 25.000000, 100.000000, -2.500000, -90.000000, 8200000.000000);
cmdMgr_ = std::make_unique<CommandManager>();
}
const QString WorkSpace::GetDir() const {
@ -379,4 +383,13 @@ void WorkSpace::OnLoaded() {
if (nullptr != timestep_) {
emit TimestepChanged(timestep_);
}
// Execute commands configured for onLoad
ExecuteCommands(CommandWhen::OnLoad);
}
void WorkSpace::ExecuteCommands(CommandWhen when) {
if (!cmdMgr_) {
cmdMgr_ = std::make_unique<CommandManager>();
}
cmdMgr_->Execute(this, when);
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <map>
#include <memory>
#include <cstdint>
#include <QObject>
@ -15,6 +16,7 @@
//#include "../ui/chartPlot/DYTChart.h"
class WorkSpaceItem;
class CommandManager;
class WorkSpace : public QObject {
Q_OBJECT
@ -52,6 +54,10 @@ public:
void SetCommondFilePath(const QString& path);
const QString GetCommondFilePath() const;
// Execute command xml according to trigger
enum class CommandWhen { OnCreate, OnLoad };
void ExecuteCommands(CommandWhen when);
void SetSimMatlab(const QString& path);
const QString GetSimMatlab() const;
@ -172,6 +178,8 @@ private:
std::map<FileEntryType, std::vector<FileEntry>> files_;
// Monotonic sequence for file entries changes, used to trigger UI refresh
std::uint64_t filesSeq_{ 0 };
// Executor for command XML actions
std::unique_ptr<CommandManager> cmdMgr_;
public:
std::uint64_t GetFilesSeq() const { return filesSeq_; }
friend class WorkSpaceXMLWrite;