ModifyHotlinkNodeID Not Working?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-18
09:51 PM
- last edited on
2022-10-04
04:47 PM
by
Daniel Kassai
What am I doing wrong?
I am running Archicad 22 in Trial mode to test the plugin.
Code:
GS::Array<API_Guid> gotlinkNodeIDs; if (ACAPI_Database (APIDb_GetHotlinkNodesID, NULL, &gotlinkNodeIDs) != NoError) { DBPrintf("Error getting hot link nodes!"); return; } for (UIndex i = 0; i < gotlinkNodeIDs.GetSize (); i++) { GSErrCode err; // Get hotlink from GUID API_HotlinkNode hotlinkNode; BNZeroMemory (&hotlinkNode, sizeof (API_HotlinkNode)); hotlinkNode.guid = gotlinkNodeIDs; GS::UniString string = APIGuidToString(hotlinkNode.guid); const char *guidString = string.ToCStr(); DBPrintf("Getting Link %s ... \n", guidString); err = ACAPI_Database (APIDb_GetHotlinkNodeID, &hotlinkNode, nullptr); if (err != NoError) { DBPrintf("Error getting link! %d \n", err); } else { // Print current link + get current path GS::UniString displayLinkText = hotlinkNode.sourceLocation->ToDisplayText(); const char *displayLinkTextCString = displayLinkText.ToCStr(); IO::Path currentPath; hotlinkNode.sourceLocation->ToUTF8POSIXPath(¤tPath); GS::UniString currentPathString (currentPath); DBPrintf("\t Old: %s \n", displayLinkTextCString); // Remove old share from path currentPathString.DeleteFirst("/Studio Server"); // Set new server and add current path GS::UniString newLocationURL ("smb://new.server.fqdn/Studio Server/"); newLocationURL.Append(currentPathString); // Set hotlinknode sourceLocation to new location object IO::Location newFileLocation (newLocationURL); hotlinkNode.sourceLocation = &newFileLocation; // new display text link to show new link displayLinkText = newFileLocation.ToDisplayText(); displayLinkTextCString = displayLinkText.ToCStr(); DBPrintf("\t New: %s \n", displayLinkTextCString); // Update the hotlink via API err = ACAPI_Database (APIDb_ModifyHotlinkNodeID, &hotlinkNode); // rename hotlink node if (err != NoError) { DBPrintf("\t ERROR: Hot link not updated: %d.", err); } else { DBPrintf("\t Hot link updated."); } // Update cache if (err == NoError && hotlinkNode.guid != APINULLGuid) { // update cache content DBPrintf(" Updating cache."); ACAPI_Database (APIDb_UpdateHotlinkCacheID, const_cast<API_Guid*> (&hotlinkNode.guid)); } DBPrintf("\n"); } } WriteReport("Updated all links");Result:
Dec 19 09:15:04 Archicad 22.app[4649]: Getting Link 396470F1-4EFC-DEDC-D658-45A971C4367B ... Dec 19 09:15:04 Archicad 22.app[4649]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/Archicad Test file/modules/Level 1-4 - shell.mod Dec 19 09:15:04 Archicad 22.app[4649]: New: smb://new.server.fqdn/Studio%20Server/TEMPORARY/Archicad%20Test%20file/modules/Level%201-4%20-%20shell.mod Dec 19 09:15:04 Archicad 22.app[4649]: Hot link updated. Updating cache. Dec 19 09:15:04 Archicad 22.app[4649]: Getting Link B94B6C0E-53F4-CD2C-2EC7-187D7936434B ... Dec 19 09:15:04 Archicad 22.app[4649]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/Archicad Test file/modules/19. Type A.mod Dec 19 09:15:04 Archicad 22.app[4649]: New: smb://new.server.fqdn/Studio%20Server/TEMPORARY/Archicad%20Test%20file/modules/19.%20Type%20A.mod Dec 19 09:15:04 Archicad 22.app[4649]: Hot link updated. Updating cache. Dec 19 09:15:04 Archicad 22.app[4649]: Getting Link 7962E17F-5ADC-A333-446E-B6EF1AAC39E0 ... Dec 19 09:15:04 Archicad 22.app[4649]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/Archicad Test file/modules/Bathroom 1.mod Dec 19 09:15:04 Archicad 22.app[4649]: New: smb://new.server.fqdn/Studio%20Server/TEMPORARY/Archicad%20Test%20file/modules/Bathroom%201.mod Dec 19 09:15:04 Archicad 22.app[4649]: Hot link updated. Updating cache. Dec 19 09:15:04 Archicad 22.app[4649]: Getting Link BCFE0B7F-CCF2-7160-B1D2-A7A265105E42 ... Dec 19 09:15:04 Archicad 22.app[4649]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/Archicad Test file/modules/Kitchen 1.mod Dec 19 09:15:04 Archicad 22.app[4649]: New: smb://new.server.fqdn/Studio%20Server/TEMPORARY/Archicad%20Test%20file/modules/Kitchen%201.mod Dec 19 09:15:04 Archicad 22.app[4649]: Hot link updated. Updating cache. Dec 19 09:15:04 Archicad 22.app[4649]: Getting Link 47CCE46F-9620-BE50-9ACB-147F8FFF0FE4 ... Dec 19 09:15:04 Archicad 22.app[4649]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/Archicad Test file/modules/16. Type D.mod Dec 19 09:15:04 Archicad 22.app[4649]: New: smb://new.server.fqdn/Studio%20Server/TEMPORARY/Archicad%20Test%20file/modules/16.%20Type%20D.mod Dec 19 09:15:04 Archicad 22.app[4649]: Hot link updated. Updating cache. Dec 19 09:15:04 Archicad 22.app[4649]: Getting Link AE237499-B9A2-44B2-EC61-BD1BDBD4CE38 ... Dec 19 09:15:04 Archicad 22.app[4649]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/Archicad Test file/modules/Kitchen 2.mod Dec 19 09:15:04 Archicad 22.app[4649]: New: smb://new.server.fqdn/Studio%20Server/TEMPORARY/Archicad%20Test%20file/modules/Kitchen%202.mod Dec 19 09:15:04 Archicad 22.app[4649]: Hot link updated. Updating cache.
Solved! Go to Solution.
- Labels:
-
Add-On (C++)
Accepted Solutions

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-19 02:58 PM
You're right, the APIDb_ModifyHotlinkNodeID method returns NoError even if the location modification was unsuccessful in case of an illegal input location. It's a bug in the API, I added it to our database.
I modified your code a little bit and it seems working now:
GS::Array<API_Guid> gotlinkNodeIDs; if (ACAPI_Database (APIDb_GetHotlinkNodesID, NULL, &gotlinkNodeIDs) != NoError) { DBPrintf ("Error getting hot link nodes!"); return; } for (UIndex i = 0; i < gotlinkNodeIDs.GetSize (); i++) { GSErrCode err; // Get hotlink from GUID API_HotlinkNode hotlinkNode = {}; hotlinkNode.guid = gotlinkNodeIDs; GS::UniString guidString = APIGuidToString (hotlinkNode.guid); DBPrintf ("Getting Link %s ... \n", guidString.ToCStr ().Get ()); err = ACAPI_Database (APIDb_GetHotlinkNodeID, &hotlinkNode, nullptr); if (err != NoError) { DBPrintf ("Error getting link! %d \n", err); } else { // Print current link + get current path GS::UniString displayLinkText = hotlinkNode.sourceLocation->ToDisplayText (); DBPrintf ("\t Old: %s \n", displayLinkText.ToCStr ().Get ()); // Remove old share from path class : public IO::NameEnumerator { bool append = false; public: IO::RelativeLocation result; virtual void NameFound (const IO::Name& name) { if (append) { result.Append (name); } else { if (name.ToString ().IsEqual ("Studio Server", GS::UniString::CaseInsensitive)) { append = true; } } } } localNameEnumerator; hotlinkNode.sourceLocation->EnumerateLocalNames (&localNameEnumerator); // Set new server and add current path IO::Location newFileLocation ("smb://new.server.fqdn/Studio Server"); newFileLocation.AppendToLocal (localNameEnumerator.result); // Set hotlinknode sourceLocation to new location object delete hotlinkNode.sourceLocation; hotlinkNode.sourceLocation = &newFileLocation; // new display text link to show new link displayLinkText = newFileLocation.ToDisplayText (); DBPrintf ("\t New: %s \n", displayLinkText.ToCStr ().Get ()); // Update the hotlink via API err = ACAPI_Database (APIDb_ModifyHotlinkNodeID, &hotlinkNode); // rename hotlink node if (err != NoError) { DBPrintf ("\t ERROR: Hot link not updated: %d.", err); } else { DBPrintf ("\t Hot link updated."); } // Update cache if (err == NoError && hotlinkNode.guid != APINULLGuid) { // update cache content DBPrintf (" Updating cache."); ACAPI_Database (APIDb_UpdateHotlinkCacheID, const_cast<API_Guid*> (&hotlinkNode.guid)); } DBPrintf ("\n"); } }
I found the following bugs in your code:
- Before set hotlinkNode.sourceLocation, make sure to delete the previous location allocated by the API to prevent memory leak:
delete hotlinkNode.sourceLocation; hotlinkNode.sourceLocation = &newFileLocation;
- As you can see in your log too, there are two '/' signs next to each other at the middle of the location:
afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/19. Type A.mod
API handled this location as illegal, that caused your code did not work. The modified code avoids this issue.
Note that you can check your location validity using the IsLegalInNativeFileSystem function:if (hotlinkNode.sourceLocation->IsLegalInNativeFileSystem ())
If you want to check whether a given location is exists in the filesystem, then you can do it like this:bool exists = false; GSErrCode err = IO::fileSystem.Contains (projectLocation, &exists); if (err == NoError && exists)
(including FileSystem.hpp can be necessary) - Your code was macOS platform specific. If you use it only on macOS, then it's not a problem. Now the modified code should work on both platforms.
Tibor

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-19 02:58 PM
You're right, the APIDb_ModifyHotlinkNodeID method returns NoError even if the location modification was unsuccessful in case of an illegal input location. It's a bug in the API, I added it to our database.
I modified your code a little bit and it seems working now:
GS::Array<API_Guid> gotlinkNodeIDs; if (ACAPI_Database (APIDb_GetHotlinkNodesID, NULL, &gotlinkNodeIDs) != NoError) { DBPrintf ("Error getting hot link nodes!"); return; } for (UIndex i = 0; i < gotlinkNodeIDs.GetSize (); i++) { GSErrCode err; // Get hotlink from GUID API_HotlinkNode hotlinkNode = {}; hotlinkNode.guid = gotlinkNodeIDs; GS::UniString guidString = APIGuidToString (hotlinkNode.guid); DBPrintf ("Getting Link %s ... \n", guidString.ToCStr ().Get ()); err = ACAPI_Database (APIDb_GetHotlinkNodeID, &hotlinkNode, nullptr); if (err != NoError) { DBPrintf ("Error getting link! %d \n", err); } else { // Print current link + get current path GS::UniString displayLinkText = hotlinkNode.sourceLocation->ToDisplayText (); DBPrintf ("\t Old: %s \n", displayLinkText.ToCStr ().Get ()); // Remove old share from path class : public IO::NameEnumerator { bool append = false; public: IO::RelativeLocation result; virtual void NameFound (const IO::Name& name) { if (append) { result.Append (name); } else { if (name.ToString ().IsEqual ("Studio Server", GS::UniString::CaseInsensitive)) { append = true; } } } } localNameEnumerator; hotlinkNode.sourceLocation->EnumerateLocalNames (&localNameEnumerator); // Set new server and add current path IO::Location newFileLocation ("smb://new.server.fqdn/Studio Server"); newFileLocation.AppendToLocal (localNameEnumerator.result); // Set hotlinknode sourceLocation to new location object delete hotlinkNode.sourceLocation; hotlinkNode.sourceLocation = &newFileLocation; // new display text link to show new link displayLinkText = newFileLocation.ToDisplayText (); DBPrintf ("\t New: %s \n", displayLinkText.ToCStr ().Get ()); // Update the hotlink via API err = ACAPI_Database (APIDb_ModifyHotlinkNodeID, &hotlinkNode); // rename hotlink node if (err != NoError) { DBPrintf ("\t ERROR: Hot link not updated: %d.", err); } else { DBPrintf ("\t Hot link updated."); } // Update cache if (err == NoError && hotlinkNode.guid != APINULLGuid) { // update cache content DBPrintf (" Updating cache."); ACAPI_Database (APIDb_UpdateHotlinkCacheID, const_cast<API_Guid*> (&hotlinkNode.guid)); } DBPrintf ("\n"); } }
I found the following bugs in your code:
- Before set hotlinkNode.sourceLocation, make sure to delete the previous location allocated by the API to prevent memory leak:
delete hotlinkNode.sourceLocation; hotlinkNode.sourceLocation = &newFileLocation;
- As you can see in your log too, there are two '/' signs next to each other at the middle of the location:
afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/19. Type A.mod
API handled this location as illegal, that caused your code did not work. The modified code avoids this issue.
Note that you can check your location validity using the IsLegalInNativeFileSystem function:if (hotlinkNode.sourceLocation->IsLegalInNativeFileSystem ())
If you want to check whether a given location is exists in the filesystem, then you can do it like this:bool exists = false; GSErrCode err = IO::fileSystem.Contains (projectLocation, &exists); if (err == NoError && exists)
(including FileSystem.hpp can be necessary) - Your code was macOS platform specific. If you use it only on macOS, then it's not a problem. Now the modified code should work on both platforms.
Tibor
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-19 07:52 PM
Thanks for the quick and informative reply

Unfortunately I'm still getting errors when setting a new valid location. As you can see in the logs, the new location paths pass the validation + file exists tests, however the API is returning error code: -2130312311 (APIERR_REFUSEDPAR)
What is the refused parameter here? It seems to pass all of the tests.
Code
GS::Array<API_Guid> gotlinkNodeIDs; if (ACAPI_Database (APIDb_GetHotlinkNodesID, NULL, &gotlinkNodeIDs) != NoError) { DBPrintf ("Error getting hot link nodes!"); return; } for (UIndex i = 0; i < gotlinkNodeIDs.GetSize (); i++) { GSErrCode err; // Get hotlink from GUID API_HotlinkNode hotlinkNode = {}; hotlinkNode.guid = gotlinkNodeIDs; GS::UniString guidString = APIGuidToString (hotlinkNode.guid); DBPrintf ("Getting Link %s ... \n", guidString.ToCStr ().Get ()); err = ACAPI_Database (APIDb_GetHotlinkNodeID, &hotlinkNode, nullptr); if (err != NoError) { DBPrintf ("Error getting link! %d \n", err); } else { // Print current link + get current path GS::UniString displayLinkText = hotlinkNode.sourceLocation->ToDisplayText (); DBPrintf ("\t Old: %s \n", displayLinkText.ToCStr ().Get ()); // Remove old share from path class : public IO::NameEnumerator { bool append = false; public: IO::RelativeLocation result; virtual void NameFound (const IO::Name& name) { if (append) { result.Append (name); } else { if (name.ToString ().Contains ("Studio Server", GS::UniString::CaseInsensitive)) { append = true; } } } } localNameEnumerator; hotlinkNode.sourceLocation->EnumerateLocalNames (&localNameEnumerator); // Set new server and add current path IO::Location newFileLocation ("smb://Storage.local/Storage"); newFileLocation.AppendToLocal (localNameEnumerator.result); // new display text link to show new link displayLinkText = newFileLocation.ToDisplayText (); DBPrintf ("\t New: %s \n\t ", displayLinkText.ToCStr ().Get ()); if (newFileLocation.IsLegalInNativeFileSystem ()) { DBPrintf("Path is legal... "); } else { DBPrintf("Path not legal... "); } bool exists = false; GSErrCode err = IO::fileSystem.Contains (newFileLocation, &exists); if (err == NoError && exists) { DBPrintf("Path exists... "); } else { DBPrintf("Path doesn't exist... "); } // Set hotlinknode sourceLocation to new location object delete hotlinkNode.sourceLocation; hotlinkNode.sourceLocation = &newFileLocation; // Update the hotlink via API err = ACAPI_Database (APIDb_ModifyHotlinkNodeID, &hotlinkNode); if (err != NoError) { DBPrintf ("ERROR: Hot link not updated: %d. ", err); } else { DBPrintf ("Hot link updated. "); } // Update cache if (err == NoError && hotlinkNode.guid != APINULLGuid) { // update cache content DBPrintf ("Updating cache."); ACAPI_Database (APIDb_UpdateHotlinkCacheID, const_cast<API_Guid*> (&hotlinkNode.guid)); } DBPrintf ("\n"); } }Result
Dec 20 07:45:06 ARCHICAD 22.app[7578]: Getting Link 396470F1-4EFC-DEDC-D658-45A971C4367B ... Dec 20 07:45:06 ARCHICAD 22.app[7578]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/Level 1-4 - shell.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: New: smb://Storage.local/Storage/TEMPORARY/ARCHICAD%20Test%20file/modules/Level%201-4%20-%20shell.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: Path is legal... Path exists... ERROR: Hot link not updated: -2130312311. Dec 20 07:45:06 ARCHICAD 22.app[7578]: Getting Link B94B6C0E-53F4-CD2C-2EC7-187D7936434B ... Dec 20 07:45:06 ARCHICAD 22.app[7578]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/19. Type A.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: New: smb://Storage.local/Storage/TEMPORARY/ARCHICAD%20Test%20file/modules/19.%20Type%20A.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: Path is legal... Path exists... ERROR: Hot link not updated: -2130312311. Dec 20 07:45:06 ARCHICAD 22.app[7578]: Getting Link 7962E17F-5ADC-A333-446E-B6EF1AAC39E0 ... Dec 20 07:45:06 ARCHICAD 22.app[7578]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/Bathroom 1.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: New: smb://Storage.local/Storage/TEMPORARY/ARCHICAD%20Test%20file/modules/Bathroom%201.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: Path is legal... Path exists... ERROR: Hot link not updated: -2130312311. Dec 20 07:45:06 ARCHICAD 22.app[7578]: Getting Link BCFE0B7F-CCF2-7160-B1D2-A7A265105E42 ... Dec 20 07:45:06 ARCHICAD 22.app[7578]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/Kitchen 1.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: New: smb://Storage.local/Storage/TEMPORARY/ARCHICAD%20Test%20file/modules/Kitchen%201.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: Path is legal... Path exists... ERROR: Hot link not updated: -2130312311. Dec 20 07:45:06 ARCHICAD 22.app[7578]: Getting Link 47CCE46F-9620-BE50-9ACB-147F8FFF0FE4 ... Dec 20 07:45:06 ARCHICAD 22.app[7578]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/16. Type D.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: New: smb://Storage.local/Storage/TEMPORARY/ARCHICAD%20Test%20file/modules/16.%20Type%20D.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: Path is legal... Path exists... ERROR: Hot link not updated: -2130312311. Dec 20 07:45:06 ARCHICAD 22.app[7578]: Getting Link AE237499-B9A2-44B2-EC61-BD1BDBD4CE38 ... Dec 20 07:45:06 ARCHICAD 22.app[7578]: Old: afp://old.server.fqdn/Studio%20Server//TEMPORARY/ARCHICAD Test file/modules/Kitchen 2.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: New: smb://Storage.local/Storage/TEMPORARY/ARCHICAD%20Test%20file/modules/Kitchen%202.mod Dec 20 07:45:06 ARCHICAD 22.app[7578]: Path is legal... Path exists... ERROR: Hot link not updated: -2130312311.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-19 08:45 PM
I was running in Trial mode with a test project + modules that the client provided. The module required opening and re-saving as Trial mode in order for the re-link to work.
Does the API require that the new relinked file is available, valid, and accessible? What about links in locked elements, how can they be dealt with?

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-19 09:24 PM
doingsomethingwrong wrote:Yes, APIDb_ModifyHotlinkNodeID will return APIERR_REFUSEDPAR if the given location is valid but not accessible.
Does the API require that the new relinked file is available, valid, and accessible?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-19 09:32 PM
Is there a way to update the hot links within a nested hot link in the master ArchiCAD file? Or do the hot links require updating per linked module.
Also, is there any way to provide user feedback via an alert or window? I can't seem to find that in the API (sorry, perhaps I'm not looking hard enough).

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2018-12-20 10:07 AM
doingsomethingwrong wrote:The easiest way is to use DGAlert or DGResAlert functions (see DG.h header file):
Also, is there any way to provide user feedback via an alert or window?
short DGAlert (short alertType, const GS::UniString& titleText = GS::UniString(), const GS::UniString& largeText = GS::UniString(), const GS::UniString& smallText = GS::UniString(), const GS::UniString& button1Text = GS::UniString(), const GS::UniString& button2Text = GS::UniString(), const GS::UniString& button3Text = GS::UniString());The return value is the index of the clicked button. So if it returns 1, then the button1 was clicked.
Example:
if (DGAlert (DG_WARNING, "Warning", "Are you sure?", "Description.", "Yes", "No") == 1) { // 'Yes' was clicked }
DGResAlert is the same, but the texts will come from the resources, so you can set them in your GRC file:
short DGResAlert (GSResModule resModule, short alertId);Example:
if (DGResAlert (ACAPI_GetOwnResModule (), 130) == 1) { // ... } // GRC: 'GALR' 130 Warning "Warning" { /* largeText */ "Are you sure?" /* smallText */ "Description." /* button1 */ "Yes" /* button2 */ "No" /* button3 */ "" }