diff --git a/dali/dfuplus/dfuplus.cpp b/dali/dfuplus/dfuplus.cpp index 45ce90c3283..39093d3a993 100644 --- a/dali/dfuplus/dfuplus.cpp +++ b/dali/dfuplus/dfuplus.cpp @@ -316,6 +316,8 @@ int CDfuPlusHelper::doit() return remove(); else if(stricmp(action, "rename") == 0) return rename(); + else if(stricmp(action, "dfsrename") == 0) + return dfsrename(); else if(stricmp(action, "list") == 0) return list(); else if(stricmp(action, "recover") == 0) @@ -1250,6 +1252,89 @@ int CDfuPlusHelper::rename() return 0; } +int CDfuPlusHelper::dfsrename() +{ + // Collect all oldname/newname pairs from the globals + IArrayOf renameItems; + + // First, check for simple oldname/newname pair + const char* oldname = globals->queryProp("oldname"); + const char* newname = globals->queryProp("newname"); + if (oldname && newname) + { + Owned item = createDFSFileRenameItem(); + item->setOldName(oldname); + item->setNewName(newname); + renameItems.append(*item.getClear()); + } + + // Then check for numbered oldname1/newname1, oldname2/newname2, etc. + for (int i = 1; i < 1000; i++) // reasonable limit + { + StringBuffer oldnameProp, newnameProp; + oldnameProp.appendf("oldname%d", i); + newnameProp.appendf("newname%d", i); + + const char* oldnameN = globals->queryProp(oldnameProp.str()); + const char* newnameN = globals->queryProp(newnameProp.str()); + + if (oldnameN && newnameN) + { + Owned item = createDFSFileRenameItem(); + item->setOldName(oldnameN); + item->setNewName(newnameN); + renameItems.append(*item.getClear()); + } + else if (oldnameN || newnameN) + { + throw MakeStringException(-1, "Both oldname%d and newname%d must be specified", i, i); + } + else + { + break; // No more numbered pairs + } + } + + if (renameItems.ordinality() == 0) + throw MakeStringException(-1, "No files specified for rename. Use oldname=/newname= or oldname1=/newname1=, etc."); + + info("\nRenaming %d file(s)\n", renameItems.ordinality()); + + Owned req = dfuclient->createDFSFileRenameRequest(); + setMtlsSecret(req->rpc()); + req->setFileRenames(renameItems); + + Owned result = dfuclient->DFSFileRename(req); + + // Check for general exceptions + if (outputServiceCallExceptions(result)) + return -1; + + // Display results for each rename + IArrayOf& results = result->getResults(); + int successCount = 0; + int failCount = 0; + + ForEachItemIn(i, results) + { + IConstDFSFileRenameResult& res = results.item(i); + if (res.getSuccess()) + { + info("Successfully renamed: %s -> %s\n", res.getOldName(), res.getNewName()); + successCount++; + } + else + { + error("Failed to rename: %s -> %s: %s\n", res.getOldName(), res.getNewName(), res.getMessage()); + failCount++; + } + } + + info("\nRename Summary: %d succeeded, %d failed\n", successCount, failCount); + + return (failCount > 0) ? -1 : 0; +} + int CDfuPlusHelper::list() { diff --git a/dali/dfuplus/dfuplus.hpp b/dali/dfuplus/dfuplus.hpp index 1d7c9c428e3..c768065e6e4 100644 --- a/dali/dfuplus/dfuplus.hpp +++ b/dali/dfuplus/dfuplus.hpp @@ -53,6 +53,7 @@ class CDfuPlusHelper : public CInterface, implements IInterface int copy(); int remove(); int rename(); + int dfsrename(); int list(); int recover(); int superfile(const char* action); diff --git a/dali/dfuplus/main.cpp b/dali/dfuplus/main.cpp index e7eb7c342b3..f4b6abf18b3 100644 --- a/dali/dfuplus/main.cpp +++ b/dali/dfuplus/main.cpp @@ -31,7 +31,7 @@ void handleSyntax() out.append("Usage:\n"); out.append(" dfuplus [-v|--version] | action=[spray|replicate|despray|copy|remove|rename|\n"); - out.append(" list|addsuper|removesuper|listsuper|\n"); + out.append(" dfsrename|list|addsuper|removesuper|listsuper|\n"); out.append(" copysuper|dafilesrv|savexml|add|status|\n"); out.append(" abort|resubmit|monitor] {}\n\n"); out.append(" -v | --version -- display version info\n\n"); @@ -131,6 +131,15 @@ void handleSyntax() out.append(" rename options:\n"); out.append(" srcname=\n"); out.append(" dstname=\n"); + out.append(" dfsrename options:\n"); + out.append(" oldname=\n"); + out.append(" newname=\n"); + out.append(" oldname1=\n"); + out.append(" newname1=\n"); + out.append(" ...\n"); + out.append(" oldnameN=\n"); + out.append(" newnameN=\n"); + out.append(" -- Efficiently rename one or more files using direct filesystem operations\n"); out.append(" list options:\n"); out.append(" name=\n"); out.append(" saveto=\n"); diff --git a/esp/scm/ws_dfu.ecm b/esp/scm/ws_dfu.ecm index 1f12ef2ead5..bc9f02da54a 100644 --- a/esp/scm/ws_dfu.ecm +++ b/esp/scm/ws_dfu.ecm @@ -1009,12 +1009,36 @@ ESPresponse [exceptions_inline, nil_remove] DFUMetaInquiryResponse ESParray Fields; }; +ESPStruct [min_ver("1.68")] DFSFileRenameItem +{ + string OldName; + string NewName; +}; + +ESPrequest DFSFileRenameRequest +{ + [min_ver("1.68")] ESParray FileRenames; +}; + +ESPStruct [min_ver("1.68")] DFSFileRenameResult +{ + string OldName; + string NewName; + bool Success; + string Message; +}; + +ESPresponse [exceptions_inline, nil_remove] DFSFileRenameResponse +{ + [min_ver("1.68")] ESParray Results; +}; + // =========================================================================== ESPservice [ auth_feature("DEFERRED"), - version("1.67"), - default_client_version("1.67"), + version("1.68"), + default_client_version("1.68"), noforms, exceptions_inline("./smc_xslt/exceptions.xslt")] WsDfu { @@ -1040,6 +1064,8 @@ ESPservice [ ESPmethod [auth_feature("DfuAccess:WRITE")] Add(AddRequest, AddResponse); ESPmethod [auth_feature("DfuAccess:WRITE")] AddRemote(AddRemoteRequest, AddRemoteResponse); + ESPmethod [auth_feature("DfuAccess:WRITE"), min_ver("1.68")] DFSFileRename(DFSFileRenameRequest, DFSFileRenameResponse); + ESPmethod ListHistory(ListHistoryRequest, ListHistoryResponse); ESPmethod EraseHistory(EraseHistoryRequest, EraseHistoryResponse); ESPmethod DFURecordTypeInfo(DFURecordTypeInfoRequest, DFURecordTypeInfoResponse); diff --git a/esp/services/ws_dfu/ws_dfuService.cpp b/esp/services/ws_dfu/ws_dfuService.cpp index e066c877538..6ca419af5b2 100644 --- a/esp/services/ws_dfu/ws_dfuService.cpp +++ b/esp/services/ws_dfu/ws_dfuService.cpp @@ -4165,6 +4165,75 @@ bool CWsDfuEx::onAddRemote(IEspContext &context, IEspAddRemoteRequest &req, IEsp return true; } +bool CWsDfuEx::onDFSFileRename(IEspContext &context, IEspDFSFileRenameRequest &req, IEspDFSFileRenameResponse &resp) +{ + try + { + context.ensureFeatureAccess(FEATURE_URL, SecAccess_Write, ECLWATCH_DFU_ACCESS_DENIED, "WsDfu::DFSFileRename: Permission denied."); + + StringBuffer username; + context.getUserID(username); + Owned userdesc; + if(username.length() > 0) + { + userdesc.setown(createUserDescriptor()); + userdesc->set(username.str(), context.queryPassword(), context.querySignature()); + } + + IArrayOf& items = req.getFileRenames(); + if (items.ordinality() == 0) + throw MakeStringException(ECLWATCH_MISSING_PARAMS, "No files specified for rename."); + + IArrayOf results; + IDistributedFileDirectory &fdir = queryDistributedFileDirectory(); + + ForEachItemIn(i, items) + { + IConstDFSFileRenameItem& item = items.item(i); + const char* oldname = item.getOldName(); + const char* newname = item.getNewName(); + + Owned result = createDFSFileRenameResult(); + result->setOldName(oldname); + result->setNewName(newname); + + try + { + if (isEmptyString(oldname)) + throw MakeStringException(ECLWATCH_INVALID_INPUT, "Old file name cannot be empty."); + if (isEmptyString(newname)) + throw MakeStringException(ECLWATCH_INVALID_INPUT, "New file name cannot be empty."); + + PROGLOG("DFSFileRename: Renaming %s to %s", oldname, newname); + + // Use renamePhysical to rename both logical entry and physical files + fdir.renamePhysical(oldname, newname, userdesc.get(), nullptr); + + result->setSuccess(true); + result->setMessage("Successfully renamed."); + } + catch(IException* e) + { + StringBuffer msg; + e->errorMessage(msg); + ERRLOG("DFSFileRename failed for %s to %s: %s", oldname, newname, msg.str()); + result->setSuccess(false); + result->setMessage(msg.str()); + e->Release(); + } + + results.append(*result.getClear()); + } + + resp.setResults(results); + } + catch(IException* e) + { + FORWARDEXCEPTION(context, e, ECLWATCH_INTERNAL_ERROR); + } + return true; +} + const int INTEGELSIZE = 20; const int REALSIZE = 32; const int STRINGSIZE = 128; diff --git a/esp/services/ws_dfu/ws_dfuService.hpp b/esp/services/ws_dfu/ws_dfuService.hpp index 6d129426a8a..1007a1f6061 100644 --- a/esp/services/ws_dfu/ws_dfuService.hpp +++ b/esp/services/ws_dfu/ws_dfuService.hpp @@ -178,6 +178,7 @@ class CWsDfuEx : public CWsDfu virtual bool onSavexml(IEspContext &context, IEspSavexmlRequest &req, IEspSavexmlResponse &resp); virtual bool onAdd(IEspContext &context, IEspAddRequest &req, IEspAddResponse &resp); virtual bool onAddRemote(IEspContext &context, IEspAddRemoteRequest &req, IEspAddRemoteResponse &resp); + virtual bool onDFSFileRename(IEspContext &context, IEspDFSFileRenameRequest &req, IEspDFSFileRenameResponse &resp); virtual bool onSuperfileList(IEspContext &context, IEspSuperfileListRequest &req, IEspSuperfileListResponse &resp); virtual bool onSuperfileAction(IEspContext &context, IEspSuperfileActionRequest &req, IEspSuperfileActionResponse &resp); virtual bool onListHistory(IEspContext &context, IEspListHistoryRequest &req, IEspListHistoryResponse &resp);