/**
   * File state cache is closing down, any resources attached to the file state must be released.
   *
   * @param state FileState
   */
  public void fileStateClosed(FileState state) {

    // DEBUG

    if (state == null) {
      Debug.println("%%%%% FileLoader.fileStateClosed() state=NULL %%%%%");
      return;
    }

    // Check if the file state has an associated file

    FileSegmentInfo segInfo = (FileSegmentInfo) state.findAttribute(DBFileSegmentInfo);

    if (segInfo != null
        && segInfo.isQueued() == false
        && segInfo.hasStatus() != FileSegmentInfo.SaveWait) {

      try {

        // Delete the temporary file

        segInfo.deleteTemporaryFile();

        // Debug

        if (Debug.EnableInfo && hasDebug())
          Debug.println("$$ Deleted temporary file " + segInfo.getTemporaryFile() + " [CLOSED] $$");
      } catch (IOException ex) {
      }
    }
  }
  /**
   * Re-create, or attach, a file request to the file state.
   *
   * @param fid int
   * @param tempPath String
   * @param virtPath String
   * @param sts int
   * @return FileState
   */
  protected final FileState createFileStateForRequest(
      int fid, String tempPath, String virtPath, int sts) {

    // Find, or create, the file state for the file/directory

    FileState state = m_stateCache.findFileState(virtPath, true);

    synchronized (state) {

      // Prevent the file state from expiring whilst the request is queued against it

      state.setExpiryTime(FileState.NoTimeout);

      // Indicate that the file exists, set the unique file id

      state.setFileStatus(FileStatus.FileExists);
      state.setFileId(fid);

      // Check if the file segment has been attached to the file state

      FileSegmentInfo fileSegInfo = (FileSegmentInfo) state.findAttribute(DBFileSegmentInfo);
      FileSegment fileSeg = null;

      if (fileSegInfo == null) {

        // Create a new file segment

        fileSegInfo = new FileSegmentInfo();
        fileSegInfo.setTemporaryFile(tempPath);

        fileSeg = new FileSegment(fileSegInfo, true);
        fileSeg.setStatus(sts, true);

        // Add the segment to the file state cache

        state.addAttribute(DBFileSegmentInfo, fileSegInfo);
      } else {

        // Make sure the file segment indicates its part of a queued request

        fileSeg = new FileSegment(fileSegInfo, true);
        fileSeg.setStatus(sts, true);
      }
    }

    // Return the file state

    return state;
  }
  /**
   * Delete the specified file data
   *
   * @param fname String
   * @param fid int
   * @param stid int
   * @exception IOException
   */
  public void deleteFile(String fname, int fid, int stid) throws IOException {

    // Delete the file data from the database

    try {

      // Find the associated file state

      FileState fstate = m_stateCache.findFileState(fname, false);

      if (fstate != null) {

        // Get the file segment details

        FileSegmentInfo fileSegInfo = (FileSegmentInfo) fstate.removeAttribute(DBFileSegmentInfo);
        if (fileSegInfo != null) {
          try {

            // Change the file segment status

            fileSegInfo.setQueued(false);
            fileSegInfo.setStatus(FileSegmentInfo.Initial);

            // Delete the temporary file

            fileSegInfo.deleteTemporaryFile();
          } catch (Exception ex) {

            // DEBUG

            if (Debug.EnableInfo && hasDebug())
              Debug.println(
                  "## ObjIdLoader failed to delete temp file " + fileSegInfo.getTemporaryFile());
          }
        }
      }
    } catch (Exception ex) {

      // DEBUG

      if (Debug.EnableInfo && hasDebug())
        Debug.println("## ObjIdLoader deleteFile() error, " + ex.toString());
    }
  }
  /**
   * File state has expired. The listener can control whether the file state is removed from the
   * cache, or not.
   *
   * @param state FileState
   * @return true to remove the file state from the cache, or false to leave the file state in the
   *     cache
   */
  public boolean fileStateExpired(FileState state) {

    // Check if the file state has an associated file segment

    FileSegmentInfo segInfo = (FileSegmentInfo) state.findAttribute(DBFileSegmentInfo);
    boolean expire = true;

    if (segInfo != null) {

      // Check if the file has a request queued

      if (segInfo.isQueued() == false) {

        try {

          // Delete the temporary file and reset the segment status so that the data may be loaded
          // again
          // if required.

          if (segInfo.hasStatus() != FileSegmentInfo.Initial) {

            // Delete the temporary file

            try {
              segInfo.deleteTemporaryFile();
            } catch (IOException ex) {

              // DEBUG

              if (Debug.EnableError) {
                Debug.println("Delete temp file error: " + ex.toString());
                File tempFile = new File(segInfo.getTemporaryFile());
                Debug.println(
                    "  TempFile file="
                        + tempFile.getAbsolutePath()
                        + ", exists="
                        + tempFile.exists());
                Debug.println("  FileState state=" + state);
                Debug.println("  FileSegmentInfo segInfo=" + segInfo);
                Debug.println("  StateCache size=" + m_stateCache.numberOfStates());
              }
            }

            // Remove the file segment, reset the file segment back to the initial state

            state.removeAttribute(DBFileSegmentInfo);
            segInfo.setStatus(FileSegmentInfo.Initial);

            // Reset the file state to indicate file data load required

            state.setDataStatus(FileState.FILE_LOADWAIT);

            // Check if the temporary file sub-directory is now empty, and it is not the current
            // temporary
            // sub-directory

            if (segInfo.getTemporaryFile().startsWith(m_curTempName) == false) {

              // Check if the sub-directory is empty

              File tempFile = new File(segInfo.getTemporaryFile());
              File subDir = tempFile.getParentFile();
              String[] files = subDir.list();
              if (files == null || files.length == 0) {

                // Delete the sub-directory

                subDir.delete();

                // DEBUG

                if (Debug.EnableInfo && hasDebug())
                  Debug.println(
                      "$$ Deleted temporary directory "
                          + subDir.getPath()
                          + ", curTempName="
                          + m_curTempName
                          + ", tempFile="
                          + segInfo.getTemporaryFile());
              }
            }

            // Indicate that the file state should not be deleted

            expire = false;

            // Debug

            if (Debug.EnableInfo && hasDebug())
              Debug.println(
                  "$$ Deleted temporary file " + segInfo.getTemporaryFile() + " [EXPIRED] $$");
          }

          // If the file state is not to be deleted reset the file state expiration timer

          if (expire == false)
            state.setExpiryTime(
                System.currentTimeMillis() + m_stateCache.getFileStateExpireInterval());
        } catch (Exception ex) {

          // DEBUG

          if (Debug.EnableError) {
            Debug.println("$$  " + ex.toString());
            Debug.println("  state=" + state);
          }
        }
      } else {

        // DEBUG

        if (hasDebug()) {
          File tempFile = new File(segInfo.getTemporaryFile());
          if (tempFile.exists() == false) {
            Debug.println("== Skipped file state, queued " + state);
            Debug.println("   File seg=" + segInfo);
          }
        }

        // File state is queued, do not expire

        expire = false;
      }
    } else if (state.isDirectory()) {

      // Nothing to do when it's a directory, just allow it to expire

      expire = true;
    } else if (Debug.EnableInfo && hasDebug()) Debug.println("$$ Expiring state=" + state);

    // Return true if the file state can be expired

    return expire;
  }