1
\$\begingroup\$

I am writing a CAD program plug-in which is used to batch translate a list of CAD files stored in a text file. After each individual CAD file is successfully processed I want to remove the CAD file from the list. The reason for doing this is because the CAD program may crash while translating the file, and I want to be able to restart and pickup where it left off. There may be as many as 150,000 files in the list, so I want to make sure that I am not wasting time.

#include "stdafx.h"
#include "funcs.h" // Header where ExportKC() is declared. Required for plug-in
#include "ck_sdk.h" // SDK header file
#include "ck_iterator.h" // SDK header file
#include <iostream>
#include <fstream>

#define PROCESS_FILE _T("C:\\scripts\\cadstapel.txt")
#define MB_TITLE (_T("Batch Export"))
#define MB_OK_STOP (MB_OK | MB_ICONSTOP)
#define ERROR_CREATE_FAIL _T("File create failed\n%s\n%s")
#define ERROR_NO_FILES _T("%s is empty.")
#define ERROR_LIST_PROCESS _T("Error in line %d")
#define ERROR_UNKNOWN _T("Unknown Error 0x%08x (%d)")

CString GetErrorString(DWORD lastErr); // I did not write this function
int ExportSTEPFile(CKPart &currentPart, std::wstring &exportFileName, size_t &solidCount);

int ExportKC()
{
  std::wifstream fileList;
  std::wstring currentFile;
  std::vector<std::wstring> allFiles;
  CString fileListName(PROCESS_FILE);

  fileList.open(fileListName, std::ios::in);
  if (fileList.fail())
  {
    DWORD lastError = ::GetLastError();
    CString errMsg = GetErrorString(lastError);
    CString s;
    s.Format(ERROR_CREATE_FAIL, fileListName, errMsg);
    MessageBox(NULL, s, MB_TITLE, MB_OK_STOP);
    return CKError;
  }

  // Read file contents into vector
  while (std::getline(fileList, currentFile))
  {
    allFiles.push_back(currentFile);
  }

  // No files in list
  if (allFiles.size() == 0)
  {
    CString s;
    s.Format(ERROR_NO_FILES, PROCESS_FILE);
    MessageBox(NULL, s, MB_TITLE, MB_OK_STOP);
  }
  fileList.close();

  // Copy vector to keep track of remaining files
  std::vector<std::wstring> remainFiles = allFiles;

  // Process files in allFiles vector
  std::wstring exportFileName;
  std::wstring exportPartName;
  int status = CKNoError;
  for (size_t i = 0; i < allFiles.size(); ++i)
  {
    // Start processing files here. Possible for processing to fail and cause program to hang
    CKPart currentPart;
    std::vector<std::wstring> partList;
    status = CKNoError;

    // Get the part file names in the design file
    status = sys_partfiles_list(allFiles[i], partList); // SDK function call
    if (status != CKNoError)
      return status;
    for (size_t j = 0; j < partList.size(); ++j)
    {
      status = sys_part_load(allFiles[i], partList[j], currentPart, true, CKS::FileUnspecified, true); // SDK function call
      if (status != CKNoError && status != CK_PART_OPEN)
        return status;
      // Create new file name for export
      exportFileName = allFiles[i];
      exportPartName = partList[j];
      // Add brackets to part name
      exportPartName.insert(0, _T("["));
      exportPartName.append(_T("]"));
      // Extract base name from full file name
      size_t endBase = exportFileName.find_last_of(_T('.'));
      if (endBase == std::wstring::npos)
      {
        CString errMsg;
        errMsg.Format(ERROR_LIST_PROCESS, i + 2);
        errMsg.AppendFormat(_T(" %s"), exportFileName.c_str());
        MessageBox(NULL, errMsg, MB_TITLE, MB_OK_STOP);
        return CKError;
      }
      size_t fileNameLength = endBase;
      if (fileNameLength > 0)
      {
        exportFileName = exportFileName.substr(0, fileNameLength);
      }
      exportFileName.append(exportPartName);
      size_t solidCount = 0;
      CKSEntityArray allSolids;
      status = ExportSTEPFile(currentPart, exportFileName, solidCount);
      if (status != CKNoError)
        return status;
      // If no solids in file, create copy.
      if (!solidCount)
      {
        std::wifstream src(allFiles[i].c_str(), std::ios::binary);
        std::wstring copyFile = exportFileName + (_T(".(copy)ckd"));
        std::wofstream dst(copyFile.c_str(), std::ios::binary);
        dst << src.rdbuf();
      }
      status = currentPart.Close(true, true);
    }

    // If processing suceeds remove processed file from remainFiles vector
    remainFiles.erase(remainFiles.begin());

    // Open file and write remaining files
    std::wofstream updatedFile;
    updatedFile.open(fileListName, std::ios::out | std::ios::trunc);
    if (updatedFile.fail())
    {
      DWORD lastError = ::GetLastError();
      CString errMsg = GetErrorString(lastError);
      CString s;
      s.Format(ERROR_CREATE_FAIL, fileListName, errMsg);
      MessageBox(NULL, s, MB_TITLE, MB_OK_STOP);
      return CKError;
    }

    for (int j = 0; j < remainFiles.size(); ++j)
    {
      updatedFile << remainFiles[j] << std::endl;
    }
    updatedFile.close();
  }
  return status;
}

// Export STEP file
int ExportSTEPFile(CKPart &currentPart, std::wstring &exportFileName, size_t &solidCount)
{
  if (!currentPart.IsValid())
  {
    return CK_NO_PART;
  }
  CKMaskTypes entType;
  int status = CKNoError;
  // Iterate through entire database and look for solids
  CKSEntityArray allSolids;
  CKSEntityIterator iter(currentPart);
  while (iter.IsValid())
  {
    status = currentPart.GetEntityType(iter, entType);
    if (entType == CKMaskSolid)
    {
      allSolids.push_back(iter);
    }
    ++iter;
  }
  std::wstring exportFile;
  std::wstring blank(_T(""));
  currentPart.SetSTEPExportOptions(1, 0, blank, blank, blank, blank, blank, false);
  CKSCoord basePos(0.0, 0.0, 0.0);
  exportFile = exportFileName + (_T(".stp"));
  solidCount = allSolids.size();
  if (solidCount)
  {
    status = currentPart.WriteEntitiesToFile(allSolids, CKS::FileSTEP, exportFile, basePos);
  }
  return status;
}

// Gets readable error string
CString GetErrorString(DWORD lastErr)
{
  CString errMsg;
  LPWSTR errBuff;
  if (::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    NULL,
    lastErr,
    0,
    (LPWSTR)&errBuff,
    0,
    NULL) == 0)
  {
    errMsg.Format(ERROR_UNKNOWN, lastErr, LOWORD(lastErr));
  }
  else
  {
    LPWSTR p = _tcschr(errBuff, _T('\r'));
    if (p != NULL)
    {
      *p = _T('\0');
    }
    errMsg = errBuff;
    ::LocalFree(errBuff);
  }
  return errMsg;
}

I should note that I need to use Visual Studio 2010 compiler for the plug-in to work with my CAD program. Am I doing anything inefficiently, or not conforming to good form?

Sample text file contents:

D:\Jobs\United\NC\100030043\100-030-043.ckd
D:\Jobs\United\NC\100080019\100-080-019.ckd
D:\Jobs\United\NC\102965\102965_Rev_A.ckd
D:\Jobs\United\NC\109614\109614_Rev_B.ckd
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Can you put an example of the file format in your question? \$\endgroup\$
    – cr_oag
    Commented Sep 30, 2015 at 18:36
  • \$\begingroup\$ Welcome to Code Review! Judging by certain comments in your code ("Process files here", "Error handling", etc), your code is hypothetical/incomplete. Therefore, this post is off-topic. \$\endgroup\$
    – SirPython
    Commented Sep 30, 2015 at 23:18
  • \$\begingroup\$ @SirPython I only omitted the code in those spots for brevity. This is actual code from my project with what I felt is all the relevant code to be reviewed included. Please advise on how you think I should proceed from here. \$\endgroup\$
    – Paul H.
    Commented Oct 1, 2015 at 0:00
  • 2
    \$\begingroup\$ Stack Overflow likes brevity, Code Review does not. Adding all relevant code would increase the quality of future reviews. \$\endgroup\$
    – Mast
    Commented Oct 1, 2015 at 0:05

1 Answer 1

1
\$\begingroup\$

Form

  • There is no need to explicitly call close() on file streams, as the destructor (of one of the base stream classes) ensures that the files are closed at the end of their scope.
  • Consider making your streams throw an exception when they get to an unacceptable state. For example ifs.exceptions( ifs.exceptions() | std::ios::badbit ); will cause ifs to throw an exception if the std::ios::badbit flag is set at some point in that stream. You can of course | that together with others, like std::ios::failbit, depending on your needs.

Basic algorithm

Since you have very large files, you should consider streaming the file one line at a time instead of loading them all once; your program would use a lot less memory. You would also not have to copy your std::vector<std::wstring>, which is very large!

In order to know which files you've already processed, you could create a new file that holds a list of all the files you've processed and write to it as you stream from the original file. This is of course, assuming you want to keep the original file intact. I will continue under this assumption as the other case (simply delete from the list as items are processed) is simpler.

Check the Notes section for a better idea that you can implement with inspiration from the following section.

Implementation

The following is a sample implementation for the algorithm I described in the previous section. For brevity's sake, I did not include the items mentioned under the Form section, nor did I include exception checking; I will leave those things for you to figure out. It is also most likely not as optimal as it should be, but it's merely meant to show you streaming.

Sample

#include <string>
#include <fstream>
void process_file( std::wstring const& file_name )
{
    // file stream for read: unprocessed files
    std::wifstream ifs( file_name );

    // used to process the file containing unprocessed lines
    std::wstring file_name_processed = file_name + L".processed";

    // used to write to the file holding the processed lines
    std::wofstream ofs_processed( file_name_processed,
        std::ios::app | std::ios::out );

    // used to read from the file mentioned above
    std::wifstream ifs_processed( file_name_processed );

    // determine last processed line from file of processed lines
    std::wstring last_line_processed;
    std::wstring next_processed_line; // used to peek ahead
    while ( std::getline( ifs_processed, next_processed_line ) &&
        next_processed_line.size() > 0 )
    {
        last_line_processed = std::move( next_processed_line );
    }

    std::wstring line_to_process; // hold current line
    while ( ifs ) // while in good state, process one line at a time (streaming)
    {
        if ( last_line_processed == line_to_process )
        {
            if ( !std::getline( ifs, line_to_process ) )
            {
                break;
            }
        }
        else if ( last_line_processed.size() > 0 )
        {
            std::getline( ifs, line_to_process );
            continue;
        }

        /*
            ... PROCESS FILE HERE ...
        */

        last_line_processed = line_to_process;

        // add it to the file containing the list of processed files.
        ofs_processed << last_line_processed + L"\n";
    }
} // streams are automatically closed here

Notes

A faster way to check if a file has been processed would be to add a character indicating that it has been processed, for example:

D:\Jobs\United\NC\100030043\100-030-043.ckd
D:\Jobs\United\NC\100080019\100-080-019.ckd
D:\Jobs\United\NC\102965\102965_Rev_A.ckd
D:\Jobs\United\NC\109614\109614_Rev_B.ckd

Becomes

!D:\Jobs\United\NC\100030043\100-030-043.ckd
!D:\Jobs\United\NC\100080019\100-080-019.ckd
!D:\Jobs\United\NC\102965\102965_Rev_A.ckd
D:\Jobs\United\NC\109614\109614_Rev_B.ckd

To indicate that the first three files have been processed. You would then only have to check the first character of every line you read, instead of the whole file name, to know which files have been processed. This is the technique I would personally use, as it doesn't require an extra file and it keeps the original file mostly intact while providing a much faster comparison.

\$\endgroup\$
2
  • \$\begingroup\$ In the first paragraph under "Notes", I don't know how to do what you state ("modify and simply remove items") using streams . That is why I was using vectors. I'll research this. Thanks. \$\endgroup\$
    – Paul H.
    Commented Oct 1, 2015 at 17:43
  • \$\begingroup\$ @PaulH. I've removed that paragraph for being misleading. \$\endgroup\$
    – cr_oag
    Commented Oct 1, 2015 at 21:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.