Skip to content

The Chameleon Pathnames

The title might be as well “When the pathname is not what it has to be”.

The experience of developing plugins for Adobe Acrobat/Reader reserved me different surprises, that made the task more challenging. One of the biggest surprises I had was the impact of the Adobe’s Cloud idea over the Acrobat’ API within Acrobat products. Their feature idea is to keep all the already opened documents within their Cloud in order to make them available to different devices you’re interacting with.

In my case, having interactions with external non-Adobe’s applications, the things complicated when trying to get the file pathname. This option is coming enabled by default.

This is how the Acrobat.com Cloud looks like within Acrobat products This is how the Acrobat.com Cloud looks like within Acrobat products

Usually, when we are thinking to files path we expect to have something similar to GetFullPathName(). But according to Acrobat SDK’s concept: not every file opened into Acrobat/Reader has to be a local disk file. It may be associated with a stream, a network file, etc.

The reason why I was looking to get the correct file path is that my plugin and others are connecting to a system that expects the local or network file path. So it was needed to find a way to get a usual file path.

The challenge I am talking about has reproduced with an Adobe.com environment activated, having such a file already synchronized in the Acrobat.com cloud by using:

acrobat.com_path_functions

But both API’s functions return proper values with non-Cloud files. With a local filename not already uploaded within Acrobat.com I got the correct file path with both functions.

So the workaround I was thinking invokes the next steps:

1. Get the file path using ASFileSysDIPathFromPath(). In case your project is a Unicode project don’t forget that the returned type is a char* and you’ll need to encode it to the proper Unicode (UTF-8 in my case).

AVDoc avDoc = AVAppGetActiveDoc();
if (NULL == avDoc)  // no doc is loaded
{
  char strErrorMsg[MAX_PATH] = {0};
  strcat_s(strErrorMsg, "There is no opened file.");
  AVAlertNote(strErrorMsg);
  return;
}
PDDoc pdDoc = AVDocGetPDDoc(doc);
ASFile fileinfo = PDDocGetFile(pdDoc);
ASFileSys fileSys = ASFileGetFileSys(fileinfo);
ASPathName pathname = ASFileAcquirePathName(fileinfo);
char* szFilePath = ASFileSysDIPathFromPath(fileSys, pathname, pathname);
// this declaration it's just for sample
// szFilePath = "/Acrobat.com/
// 89323c47-676e-44a3-9fc3-cd18fe57bc91/3ac312be-c13d-4a00-bd57-b454a52019e3/myFile.pdf"
char* szAnsiPath = ASFileSysDisplayStringFromPath(fileSys, pathname);
auto strTmpPath = std::vector<wchar_t>{0};  // get the length of the path
int result =
    MultiByteToWideChar(CP_UTF8, NULL, szAnsiPath, strlen(szAnsiPath), NULL, 0);
if (result > 0) {
  strTmpPath.resize(result + 1);  // encode into UTF-8 the array
  result = MultiByteToWideChar(CP_UTF8, NULL, szAnsiPath, strlen(szAnsiPath),
                               &strTmpPath[0], result);
  strTmpPath[result] = '\0';
  if (result == 0)
    TRACE_ERROR(
        _T("The transformation ANSI to UNICODE has failed. Error code: %d"),
        GetLastError());
}
tstring sFilePath(&strTmpPath[0]);  // continue by using the sFilePath

2. Check if it is a cloud path (starts with Acrobat.com:).

if (ptr4Cloud && ptr4Cloud->checkCloudPath(sFilePath)) {
  tstring newFileGeneratedPath;
  ptr4Cloud->SaveFileOnDisk(
      pdDoc, sFilePath,
      newFileGeneratedPath);  // use the newFileGeneratedPath as expected 
}

where

bool FooCloud::checkCloudPath(const ustring& strCloudPath) {
  if (m_cloudPrefix.length() > strCloudPath.length()) {
    return false;
  }
  return (std::mismatch(m_cloudPrefix.begin(), m_cloudPrefix.end(),
    strCloudPath.begin())
    .first == m_cloudPrefix.end());
}

3. Save the file content into a temporary file (ex. C:\Users\Silviu.Ardelean\AppData\Local\Temp\)

void FooCloud::SaveFileOnDisk(PDDoc pdDoc,
                              const tstring& strCloudPath,
                              tstring& newFilePath) {
  DWORD dwRetVal = 0;
  TCHAR lpTempPathBuffer[MAX_PATH + 1] = {0};
  tstring fileName = getFileName(strCloudPath);
  dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
  if (dwRetVal != 0)
    newFilePath = lpTempPathBuffer;
  newFilePath += (!fileName.empty() && dwRetVal != 0)
                     ? fileName
                     : _T("YourFile.pdf");  // create the effective file
  ASFileSys fileSysX = ASGetDefaultFileSys();
  ASPathName new_path =
      ASFileSysCreatePathName(fileSysX, ASAtomFromString("Cstring"),
                              (const void*)CW2A(newFilePath.c_str()), NULL);
  PDDocCopyParams saveParams =
      (PDDocCopyParams)ASmalloc(sizeof(PDDocCopyParamsRec));
  saveParams->size = sizeof(PDDocCopyParamsRec);
  saveParams->newPath = new_path;
  saveParams->fileSys = fileSysX;
  saveParams->cancelProc = NULL;
  saveParams->cancelProcData = NULL;
  saveParams->progMon = NULL;
  saveParams->progMonData = NULL;
  saveParams->saveChanges = false;
  PDDocCopyToFile(pdDoc, saveParams);
  m_listFiles.push_back(newFilePath);
  ASfree(saveParams);
  ASFileSysReleasePath(fileSysX, new_path);
}

where

tstring FooCloud::getFileName(const tstring& strCloudPath) {
  return (strCloudPath.length() < m_cloudPrefix.length())
    ? _T("")
    : strCloudPath.substr(m_cloudPrefix.length());
}

4. Provided the temporary file path to the proxy module that expects it to interact with my system.

5. Clean/delete the temporary files on plugin uploading – PluginUnload() callback.

Additional comments
In case your plugins will interact with external non-Adobe’s application most probably you’ll have to do different tricks. Because of the way the Acrobat SDK is designed, without direct support for wchar_t and std::wstring you will need to make different conversions and encoding/decoding (ex. the ATL macros CW2A, CA2W(), functions such MultiByteToWideChar() on Windows, etc).

If you don’t have to interact with external non-Adobe’s applications be confident with Acrobat’s SDK types and data structures. In this way, you’ll avoid such conversions.

Silviu Ardelean

Software Engineer

More Posts - Website

Follow Me:
TwitterFacebookPinterest

2 thoughts on “The Chameleon Pathnames”

  1. This was helpful, but I ended up doing it a different way. I used ASFileSysIsLocal(), then ASFileSysAcquireFileSysPath() if it wasn’t local.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.