private void processOperation()

in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/versioncontrol/engines/internal/GetEngine.java [1577:2442]


    private void processOperation(final GetOperation action, final AsyncGetOperation asyncOp) {
        Check.notNull(action, "action"); //$NON-NLS-1$
        Check.notNull(asyncOp, "asyncOp"); //$NON-NLS-1$

        // Get the new local item once since it has to be computed for
        // GetOperations.
        final String newLocalItem = action.getTargetLocalItem();

        /*
         * Check the path length here for compatibility with .NET, which
         * discovers the condition when ItemSpec.GetFullPath() is used.
         *
         * Only do for non-preview, as the .NET implementation would only
         * encounter the limit when writing files to disk.
         */
        if (newLocalItem != null && !asyncOp.isPreview()) {
            try {
                LocalPath.checkLocalItem(newLocalItem, null, false, false, false, true);
            } catch (final PathTooLongException e) {
                log.warn("Path too long, not getting", e); //$NON-NLS-1$
                onNonFatalError(
                    new VersionControlException(
                        MessageFormat.format(
                            Messages.getString("GetEngine.LocalPathTooLongFormat"), //$NON-NLS-1$
                            newLocalItem)),
                    asyncOp.getWorkspace());
                return;
            }
        }

        /*
         * If this get operation has been overridden by the target conflict
         * management code below (code that checks the existingLocalHash), then
         * just ignore this. To better understand this see the code below where
         * targetAction.ClearLocalItem() is called.
         */
        if (action.isDownloadCompleted()) {
            return;
        }

        // For conflicts, fire the conflict event.
        if (action.hasConflict()) {
            asyncOp.addConflict(action);
            recordEvent(asyncOp, OperationStatus.CONFLICT, action);
            return;
        }

        // Determine whether this is a pending rename that is changing the
        // path's case.
        final boolean isCaseChangingRename = action.isCaseChangingRename();

        /*
         * Tracks whether the operation's download is completed in this method,
         * versus being queued for asynchronous processing, so we can set
         * .tpattributes if it is complete.
         */
        boolean downloadCompletedHere = false;

        try {
            // **************************************
            // Error checks against the source item.
            // **************************************

            // Determine if the local item at the location we currently have it
            // exists.
            FileSystemAttributes existingLocalAttrs = new FileSystemAttributes();
            boolean existingLocalExists = false;

            if (action.getCurrentLocalItem() != null) {
                existingLocalAttrs = FileSystemUtils.getInstance().getAttributes(action.getCurrentLocalItem());
                existingLocalExists = existingLocalAttrs.exists();

                log.debug(MessageFormat.format(
                    "existingLocalAttrs = {0}, existingLocalExists = {1}", //$NON-NLS-1$
                    existingLocalAttrs,
                    existingLocalExists));

                /*
                 * If we are undoing an edit that is not also an add, set the
                 * file back to read-only so that we will not see it as a
                 * writable file later.
                 */
                if (asyncOp.getType() == ProcessType.UNDO) {
                    if (action.getChangeType().contains(ChangeType.EDIT)
                        && action.getChangeType().contains(ChangeType.ADD) == false) {
                        action.setOkayToOverwriteExistingLocal(true);

                        if (WorkspaceLocation.SERVER == asyncOp.getWorkspace().getLocation()
                            && existingLocalAttrs.isReadOnly() == false) {
                            existingLocalAttrs.setReadOnly(true);
                            existingLocalAttrs.setArchive(false);
                            FileSystemUtils.getInstance().setAttributes(
                                action.getCurrentLocalItem(),
                                existingLocalAttrs);
                            log.debug(
                                MessageFormat.format(
                                    "Setting file to read only (archive=true) as part of undoing an edit: {0}", //$NON-NLS-1$
                                    action.getCurrentLocalItem()));
                        }
                    }
                }

                // Check for problems deleting the source local file/directory.
                if (existingLocalExists
                    && (newLocalItem == null
                        || LocalPath.equals(action.getCurrentLocalItem(), newLocalItem) == false)) {
                    // Check if we are getting a file but the source is actually
                    // a directory.
                    if (action.getItemType() == ItemType.FILE
                        && !existingLocalAttrs.isSymbolicLink()
                        && existingLocalAttrs.isDirectory()) {
                        // I have decided to not make this an error, but rather
                        // to skip the deletion of the source.
                        existingLocalExists = false;
                    }
                }
            }

            // **************************************
            // Error checks against the target item.
            // **************************************

            /*
             * Check if there is a get operation against the target item. This
             * code is critical for breaking rename cycles (the case where all
             * items involved in the rename cycle have pending changes just
             * results in a conflict for each -- otherwise, get should make
             * progress).
             */
            FileSystemAttributes newLocalAttrs = new FileSystemAttributes();
            boolean newLocalExists = false;
            GetOperation targetAction = null;
            if (newLocalItem != null) {
                newLocalAttrs = FileSystemUtils.getInstance().getAttributes(newLocalItem);
                newLocalExists = newLocalAttrs.exists();

                log.debug(MessageFormat.format(
                    "newLocalAttrs = {0}, newLocalExists = {1}", //$NON-NLS-1$
                    newLocalAttrs.toString(),
                    newLocalExists));
                log.debug(MessageFormat.format("NewContentNeeded = {0}", action.isNewContentNeeded())); //$NON-NLS-1$

                // Check if we are getting a file but the target is actually a
                // directory.
                if (newLocalExists
                    && action.getItemType() != ItemType.FOLDER
                    && !newLocalAttrs.isSymbolicLink()
                    && newLocalAttrs.isDirectory()) {
                    asyncOp.addWarning(OperationStatus.TARGET_IS_DIRECTORY, action);
                    log.debug(MessageFormat.format("TargetIsDirectory, newLocalItem = {0}", newLocalItem)); //$NON-NLS-1$
                    return;
                }

                targetAction = asyncOp.getExistingLocalHash().get(newLocalItem);
                if (targetAction != null && targetAction != action && !isCaseChangingRename) {
                    /*
                     * Check if there is a pending change against the target
                     * item. If there is a target pending change and we're not
                     * processing the results of a merge or unshelve or undo
                     * (e.g., unshelve cyclic rename), we'll stop processing the
                     * current action. None of these errors are possible if this
                     * is a case changing rename.
                     */
                    if (newLocalExists
                        && action.getType() != ProcessType.UNSHELVE
                        && action.getType() != ProcessType.MERGE
                        && action.getType() != ProcessType.ROLLBACK
                        && action.getType() != ProcessType.UNDO
                        && targetAction.getEffectiveChangeType().isEmpty() == false) {
                        asyncOp.addWarning(OperationStatus.TARGET_LOCAL_PENDING, action, targetAction);
                        log.debug(
                            MessageFormat.format(
                                "TargetLocalPending, newLocalItem = {0}, targetAction.ChangeType = {1}", //$NON-NLS-1$
                                newLocalItem,
                                targetAction.getEffectiveChangeType()));
                        return;
                    } else if (newLocalExists
                        && !asyncOp.isOverwrite()
                        && isWritableFileConflict(asyncOp, action, newLocalAttrs)
                        && newLocalAttrs.isSymbolicLink() == false) {
                        // We have to stop if the target item is a writable
                        // file.
                        asyncOp.addWarning(OperationStatus.TARGET_WRITABLE, action);
                        log.debug(MessageFormat.format("TargetWritable, newLocalItem = {0}", newLocalItem)); //$NON-NLS-1$
                        return;
                    } else {
                        /*
                         * We have a get operation with this target as the
                         * source but it doesn't have a pending change. If the
                         * target is just a file, processing this action will
                         * handle it and there is no reason to complete the
                         * source portion of the target action. If the target is
                         * a folder, it will get added to the hash of items to
                         * not delete when the directory is created (see further
                         * down).
                         */
                        if (targetAction.getItemType() == ItemType.FILE) {
                            /*
                             * In order to make this work in the face of
                             * crashes, I need to actually tell the server that
                             * I no longer have it. We must lock the target
                             * action across both clearing the local item and
                             * posting the update to prevent a race condition
                             * where the ULV call for a download could happen in
                             * between.
                             */
                            synchronized (targetAction) {
                                log.debug(MessageFormat.format(
                                    "ProcessOperation: clearing source local item {0}", //$NON-NLS-1$
                                    action.getCurrentLocalItem()));

                                if (!asyncOp.isPreview() && !targetAction.isDownloadCompleted()) {
                                    /*
                                     * For a delete, we can complete the
                                     * operation (for merge, we need to ack it
                                     * as resolved as well). Otherwise, just
                                     * tell the server we don't have the item.
                                     */
                                    if (targetAction.isDelete()) {
                                        asyncOp.queueLocalVersionUpdate(
                                            targetAction,
                                            null,
                                            targetAction.getVersionLocal());
                                    }
                                }

                                /*
                                 * Only set the DownloadCompleted flag if the
                                 * target action is a delete; othwerwise, the
                                 * action hasn't been completed.
                                 */
                                if (targetAction.isDelete() && !targetAction.isDownloadCompleted()) {
                                    targetAction.setDownloadCompleted(true);
                                    downloadCompletedHere = true;
                                    recordEvent(asyncOp, OperationStatus.DELETING, targetAction);
                                }

                                // We no longer have this item at this location
                                // --
                                // don't call until after using
                                // the local item path in the ULV call.
                                targetAction.clearLocalItem();

                                // Now remove the location from the hash.
                                asyncOp.getExistingLocalHash().remove(newLocalItem);
                            }
                        }
                    }
                }
            }

            // if true, this is pending add which is being undone and we are
            // asked to delete it afterwards
            final boolean deleteAsUndoAdd = shouldDeleteAsUndoAdd(asyncOp, action);

            // **************************************
            // Time to perform the get.
            // **************************************

            // Check if we have something to get (rather than just deleting
            // something).
            if (!action.isDelete()) {
                // Handle getting folders very differently from getting files.
                if (action.getItemType() == ItemType.FOLDER) {
                    // Check if the target item is a writable file.
                    if (!asyncOp.isOverwrite()
                        && newLocalExists
                        && isWritableFileConflict(asyncOp, action, newLocalAttrs)) {
                        asyncOp.addWarning(OperationStatus.TARGET_WRITABLE, action);
                        log.debug(MessageFormat.format("TargetWritable, newLocalItem = {0}", newLocalItem)); //$NON-NLS-1$
                        return;
                    }

                    // Check if an item exists at the target location.
                    if (!asyncOp.isPreview() && !asyncOp.isNoDiskUpdate() && newLocalExists) {
                        // If it is just a file (we've already confirmed it's
                        // read-only) just delete it.
                        if (!newLocalAttrs.isDirectory()) {
                            if (!new File(newLocalItem).delete()) {
                                throw new IOException(
                                    MessageFormat.format(
                                        Messages.getString("GetEngine.CouldNotDeleteFileFormat"), //$NON-NLS-1$
                                        newLocalItem));
                            } else {
                                log.debug(
                                    MessageFormat.format(
                                        "Deleting read-only file that''s in the way of a directory: {0}", //$NON-NLS-1$
                                        newLocalItem));
                            }
                            newLocalExists = false;
                        }
                    }

                    String sourceLocalItem = null;

                    // if we are case changing rename and the item exists
                    // locally
                    if (isCaseChangingRename && existingLocalExists) {
                        sourceLocalItem = action.getCurrentLocalItem();
                    } else if (newLocalExists) {
                        // if the target already exists and we have a delete on
                        // the same path
                        // we convert it to a rename -- this takes care of the
                        // get /remap case
                        // where the case changes.
                        if (targetAction != null
                            && targetAction.getItemType() == ItemType.FOLDER
                            && targetAction.isDelete()
                            && LocalPath.lastPartEqualsCaseSensitive(
                                targetAction.getCurrentLocalItem(),
                                newLocalItem) == false) {
                            sourceLocalItem = targetAction.getCurrentLocalItem();
                        }
                    }

                    // Create the directory. The call to create a directory does
                    // not throw an exception
                    // if the dir already exists (e.g., due to a race
                    // condition).
                    if (!asyncOp.isPreview() && (!newLocalExists || sourceLocalItem != null)) {
                        // If this is a case changing rename then we can safely
                        // do a Directory.Move, so we do,
                        // but only if source directory exists (Bug: 448888)
                        if (sourceLocalItem != null) {
                            try {
                                FileHelpers.rename(sourceLocalItem, newLocalItem);
                                log.debug(MessageFormat.format(
                                    "Renamed directory: {0} -> {1}", //$NON-NLS-1$
                                    action.getCurrentLocalItem(),
                                    newLocalItem));
                            } catch (final IOException e) {
                                onNonFatalError(
                                    new IOException(MessageFormat.format(
                                        Messages.getString("GetEngine.FailedToRenameDirectoryFormat"), //$NON-NLS-1$
                                        sourceLocalItem,
                                        newLocalItem)),
                                    asyncOp.getWorkspace());
                            }
                        } else {
                            if (!asyncOp.isNoDiskUpdate()) {
                                final File newLocalFile = new File(newLocalItem);

                                if (newLocalFile.mkdirs() == false) {
                                    /*
                                     * Double-check that the directory does not
                                     * exist to avoid race conditions in mkdirs.
                                     */
                                    if (!newLocalFile.isDirectory()) {
                                        onNonFatalError(
                                            new IOException(MessageFormat.format(
                                                Messages.getString("GetEngine.FailedToCreateDirectoryFormat"), //$NON-NLS-1$
                                                newLocalItem)),
                                            asyncOp.getWorkspace());
                                    }
                                } else {
                                    log.debug(MessageFormat.format("Created directory: {0}", newLocalItem)); //$NON-NLS-1$
                                }
                            }
                        }
                    }

                    if (deleteAsUndoAdd) {
                        if (!asyncOp.getDeletes().containsKey(newLocalItem)) {
                            asyncOp.getDeletes().put(newLocalItem, action);
                        }
                    }
                    // Ensure that the newLocalItem folder will never be deleted
                    // by another operation.
                    else if (!asyncOp.getDontDeleteFolderHash().containsKey(newLocalItem)) {
                        asyncOp.getDontDeleteFolderHash().put(newLocalItem, action);
                    }

                    // Schedule the source file/directory for deletion if it is
                    // different from the target.
                    if (existingLocalExists && !LocalPath.equals(action.getCurrentLocalItem(), newLocalItem)) {
                        if (!asyncOp.getDeletes().containsKey(action.getCurrentLocalItem())) {
                            asyncOp.getDeletes().put(action.getCurrentLocalItem(), action);
                        }

                        // Go ahead and record the "move" and notify the server.
                        recordEvent(
                            asyncOp,
                            action.getCurrentLocalItem() == null ? OperationStatus.GETTING : OperationStatus.REPLACING,
                            action);

                        if (!asyncOp.isPreview()) {
                            asyncOp.queueLocalVersionUpdate(
                                action,
                                action.getTargetLocalItem(),
                                action.getVersionServer());
                            action.setDownloadCompleted(true);
                            downloadCompletedHere = true;
                        }
                    } else {
                        recordEvent(
                            asyncOp,
                            action.getCurrentLocalItem() == null ? OperationStatus.GETTING : OperationStatus.REPLACING,
                            action);

                        if (!asyncOp.isPreview()) {
                            if (asyncOp.getType() != ProcessType.PEND && asyncOp.getType() != ProcessType.UNDO
                                || !action.getEffectiveChangeType().contains(ChangeType.ADD)) {
                                /*
                                 * Queue a request to tell the server that I got
                                 * it. In a local workspace, when getting a
                                 * folder that we already have, use the force
                                 * option.
                                 */
                                asyncOp.queueLocalVersionUpdate(
                                    action,
                                    action.getTargetLocalItem(),
                                    action.getVersionServer(),
                                    asyncOp.getWorkspace().getLocation() == WorkspaceLocation.LOCAL);

                                action.setDownloadCompleted(true);
                                downloadCompletedHere = true;
                            }
                        }
                    }
                } else
                // Getting a file.
                {
                    /*
                     * If we are editing an existing file or the file exists at
                     * a different location, GetAll is false, and version local
                     * is the same as on the server, move the file.
                     */
                    if (existingLocalExists
                        && (action.getEffectiveChangeType().contains(ChangeType.EDIT)
                            && action.getVersionLocal() == action.getVersionServer()
                            || !asyncOp.isGetAll() && !action.isNewContentNeeded())) {
                        try {
                            // Force ignore case here so we can detect
                            // case-changing renames on all platforms.
                            if (LocalPath.equals(action.getCurrentLocalItem(), newLocalItem, true)) {
                                // When edit is true and we get to this point
                                // with the path not having
                                // changed, it is either a GetAll or there is
                                // nothing to download (the
                                // content didn't change on the server even
                                // though the version number did).
                                if (action.getEffectiveChangeType().contains(ChangeType.EDIT) && asyncOp.isGetAll()) {
                                    // When GetAll is specified and the file is
                                    // being edited, we obviously cannot
                                    // download the file. Do NOT add it to the
                                    // retry list!
                                    recordEvent(asyncOp, OperationStatus.UNABLE_TO_REFRESH, action);
                                    asyncOp.getStatus().incrementNumWarnings();
                                } else {
                                    // If this isn't a preview and this is a case changing rename. (i.e. rename $/project -> $/PROJECT)
                                    if (!asyncOp.isPreview() && isCaseChangingRename && !asyncOp.isNoDiskUpdate()) {
                                        try {
                                            FileHelpers.rename(action.getCurrentLocalItem(), newLocalItem);
                                            log.debug(MessageFormat.format(
                                                "Renamed file from {0} to {1}", //$NON-NLS-1$
                                                action.getCurrentLocalItem(),
                                                newLocalItem));
                                        } catch (Exception e) {
                                            onNonFatalError(
                                                new IOException(MessageFormat.format(
                                                    Messages.getString("GetEngine.FailedToRenameFileFormat"), //$NON-NLS-1$
                                                    action.getCurrentLocalItem(),
                                                    newLocalItem)),
                                                asyncOp.getWorkspace());
                                        }
                                    }

                                    // For get there's nothing to do -- just go
                                    // ahead and fire the event. For pend/undo
                                    // the action happened on the server and we
                                    // need to fire the event, though there
                                    // is no disk update.
                                    if (asyncOp.getType() != ProcessType.GET || isCaseChangingRename) {
                                        recordEvent(asyncOp, OperationStatus.GETTING, action);
                                    }
                                }

                                // There's nothing that needs to be downloaded.
                                if (!asyncOp.isPreview()) {
                                    Check.isTrue(
                                        !action.isNewContentNeeded()
                                            || (action.getEffectiveChangeType().contains(ChangeType.EDIT)
                                                && asyncOp.isGetAll()),
                                        MessageFormat.format(
                                            "Local and server versions expected to be equal except for edit: {0}", //$NON-NLS-1$
                                            action));

                                    if (deleteAsUndoAdd) {
                                        if (new File(newLocalItem).delete() == false) {
                                            throw new IOException(
                                                MessageFormat.format(
                                                    Messages.getString("GetEngine.CouldNotDeleteFileFormat"), //$NON-NLS-1$
                                                    newLocalItem));
                                        }
                                    }
                                    // No need to queue a local version update
                                    // for the undo of a pending add. The call
                                    // to UndoPendingChanges removes the local
                                    // version row as part of that
                                    // call.
                                    else if (!(ProcessType.UNDO == asyncOp.getType()
                                        && action.getChangeType().contains(ChangeType.ADD))) {
                                        asyncOp.queueLocalVersionUpdate(
                                            action,
                                            action.getTargetLocalItem(),
                                            action.getVersionServer());

                                        action.setDownloadCompleted(true);
                                        downloadCompletedHere = true;
                                    }
                                }

                                return;
                            }
                        } finally {
                            /*
                             * If the file is checked out, make sure it is
                             * writable. This is also necessary for the code
                             * further down that maintains the read-only setting
                             * of the source when performing the copy/delete
                             * move.
                             */
                            if (!asyncOp.isPreview()
                                && action.getEffectiveChangeType().contains(ChangeType.EDIT)
                                && existingLocalAttrs.isReadOnly()
                                && !existingLocalAttrs.isSymbolicLink()) {
                                existingLocalAttrs.setReadOnly(false);
                                existingLocalAttrs.setArchive(true);
                                FileSystemUtils.getInstance().setAttributes(
                                    action.getCurrentLocalItem(),
                                    existingLocalAttrs);
                                log.debug(MessageFormat.format(
                                    "Set edited file to read/write (archive=true): {0}", //$NON-NLS-1$
                                    action.getCurrentLocalItem()));
                            }
                        }

                        // Check for a writable target before attempting the
                        // move (we know it's not a directory).
                        if (!asyncOp.isOverwrite()
                            && newLocalExists
                            && isWritableFileConflict(asyncOp, action, newLocalAttrs)
                            && !asyncOp.isNoDiskUpdate()) {
                            asyncOp.addWarning(OperationStatus.TARGET_WRITABLE, action);
                            return;
                        }

                        if (!asyncOp.isPreview() && !deleteAsUndoAdd && !asyncOp.isNoDiskUpdate()) {
                            /**
                             * We may get a rename for a file to a directory
                             * that does not exist. (Particularly when handling
                             * name conflicts and the user chooses the
                             * destination filename.)
                             */
                            FileHelpers.createDirectoryIfNecessary(LocalPath.getParent(newLocalItem));

                            // Copy the source over the target file (we know at
                            // this point that the target
                            // must be read-only). If we are undoing pending add
                            // which is under pending rename, we just don't copy
                            // the file.

                            if (newLocalAttrs.exists()) {
                                new File(newLocalItem).delete();
                            }

                            if (!existingLocalAttrs.isSymbolicLink()) {
                                FileCopyHelper.copy(action.getCurrentLocalItem(), newLocalItem);
                                log.debug(MessageFormat.format(
                                    "Copied file from {0} to {1}", //$NON-NLS-1$
                                    action.getCurrentLocalItem(),
                                    newLocalItem));

                                // Apply the appropriate last-write time
                                // forward.
                                if (asyncOp.getWorkspace().getOptions().contains(WorkspaceOptions.SET_FILE_TO_CHECKIN)
                                    && !DotNETDate.MIN_CALENDAR.equals(action.getVersionServerDate())) {
                                    final File newLocalFile = new File(newLocalItem);
                                    if (existingLocalAttrs.isReadOnly()) {
                                        existingLocalAttrs.setReadOnly(false);
                                        FileSystemUtils.getInstance().setAttributes(newLocalFile, existingLocalAttrs);
                                        existingLocalAttrs.setReadOnly(true);
                                    }

                                    if (action.getEffectiveChangeType().contains(ChangeType.EDIT)) {
                                        newLocalFile.setLastModified(action.getVersionServerDate().getTimeInMillis());
                                    } else {
                                        // Pending edit; carry the existing
                                        // timestamp forward
                                        final FileSystemAttributes attrs =
                                            FileSystemUtils.getInstance().getAttributes(action.getCurrentLocalItem());
                                        newLocalFile.setLastModified(attrs.getModificationTime().getJavaTime());
                                    }
                                }
                            } else {
                                final FileSystemUtils util = FileSystemUtils.getInstance();
                                final String destinationPath = util.getSymbolicLink(action.getCurrentLocalItem());
                                util.createSymbolicLink(destinationPath, newLocalItem);
                            }

                            // We must preserve the read/write setting since the
                            // user may have attrib'ed the file outside of this program.
                            FileSystemUtils.getInstance().setAttributes(newLocalItem, existingLocalAttrs);
                        }

                        // Report that we are actually getting the file.
                        recordEvent(
                            asyncOp,
                            action.getCurrentLocalItem() == null ? OperationStatus.GETTING : OperationStatus.REPLACING,
                            action);

                        if (asyncOp.isPreview()) {
                            return;
                        }

                        // Tell the server that the file is in the new location.
                        asyncOp.queueLocalVersionUpdate(action, action.getTargetLocalItem(), action.getVersionServer());

                        synchronized (action) {
                            action.setDownloadCompleted(true);
                            downloadCompletedHere = true;

                            // Delete the source.
                            if (!asyncOp.isNoDiskUpdate()) {
                                if (new File(action.getCurrentLocalItem()).delete() == false) {
                                    throw new IOException(
                                        MessageFormat.format(
                                            Messages.getString("GetEngine.CouldNotDeleteFileFormat"), //$NON-NLS-1$
                                            action.getCurrentLocalItem()));
                                } else {
                                    log.debug(MessageFormat.format(
                                        "Deleted file source of move: {0}", //$NON-NLS-1$
                                        action.getCurrentLocalItem()));
                                }
                            }
                        }
                    } else {
                        // Download the file unless the source or target is
                        // writable and Overwrite is not specified.
                        if (action.getEffectiveChangeType().contains(ChangeType.ADD) && !action.isNewContentNeeded()) {
                            // If the action is for a file with a pending add
                            // and we don't have or it is not on disk, there is
                            // nothing more we can do. For pend or undo, the
                            // change still happened on the server, so fire the
                            // regular event.
                            if (asyncOp.getType() == ProcessType.PEND || asyncOp.getType() == ProcessType.UNDO) {
                                recordEvent(asyncOp, OperationStatus.GETTING, action);
                            }

                            // Let the user know that there is an error unless
                            // we are processing an Undo request or a Pend that
                            // has Preview turned on (happens in the VSIP code).
                            if (!asyncOp.isNoDiskUpdate()
                                && asyncOp.getType() != ProcessType.UNDO
                                && (!asyncOp.isPreview() || asyncOp.getType() != ProcessType.PEND)) {
                                if (newLocalItem != null) {
                                    onNonFatalError(
                                        new VersionControlException(MessageFormat.format(
                                            Messages.getString("GetEngine.AddedItemMissingLocallyFormat"), //$NON-NLS-1$
                                            newLocalItem)),
                                        asyncOp.getWorkspace());
                                } else {
                                    onNonFatalError(
                                        new VersionControlException(MessageFormat.format(
                                            Messages.getString("GetEngine.AddedItemMissingLocallyFormat"), //$NON-NLS-1$
                                            action.getCurrentLocalItem())),
                                        asyncOp.getWorkspace());
                                }
                            }

                            return;
                        } else if (!asyncOp.isOverwrite()
                            && WorkspaceLocation.SERVER == asyncOp.getWorkspace().getLocation()
                            && existingLocalExists
                            && !existingLocalAttrs.isReadOnly()
                            && !action.isOkayToOverwriteExistingLocal()
                            && !isCaseChangingRename
                            && !localContentIsRedundant(action.getCurrentLocalItem(), action.getHashValue())
                            && !existingLocalAttrs.isSymbolicLink()) {
                            asyncOp.addWarning(OperationStatus.SOURCE_WRITABLE, action, null);
                        } else if (!asyncOp.isOverwrite()
                            && newLocalExists
                            && isWritableFileConflict(asyncOp, action, newLocalAttrs)
                            && !isCaseChangingRename
                            && !newLocalAttrs.isSymbolicLink()) {
                            asyncOp.addWarning(OperationStatus.TARGET_WRITABLE, action);
                            return;
                        } else {
                            // Report that we are actually getting the file.
                            recordEvent(
                                asyncOp,
                                action.getCurrentLocalItem() == null ? OperationStatus.GETTING
                                    : OperationStatus.REPLACING,
                                action);

                            if (action.isContentDestroyed()) {
                                onNonFatalError(
                                    new DestroyedContentUnavailableException(MessageFormat.format(
                                        Messages.getString("GetEngine.DestroyedFileContentUnavailableExceptionFormat"), //$NON-NLS-1$
                                        action.getVersionServer(),
                                        action.getTargetLocalItem())),
                                    asyncOp.getWorkspace());
                                return;
                            }

                            // Don't go any further if we aren't actually
                            // getting it.
                            if (asyncOp.isPreview()) {
                                return;
                            }

                            Check.isTrue(
                                !deleteAsUndoAdd,
                                "We are downloading file which is not needed (undo of pending add)"); //$NON-NLS-1$

                            if (action.isUndo()
                                && null != action.getBaselineFileGUID()
                                && null == action.getDownloadURL()) {
                                // Local workspace offline undo (baseline folder
                                // restore)
                                if (!asyncOp.isNoDiskUpdate()) {
                                    // check symbolic link first
                                    final boolean isSymlink = PropertyConstants.IS_SYMLINK.equals(
                                        PropertyUtils.selectMatching(
                                            action.getPropertyValues(),
                                            PropertyConstants.SYMBOLIC_KEY));

                                    asyncOp.getBaselineFolders().copyBaselineToTarget(
                                        action.getBaselineFileGUID(),
                                        action.getTargetLocalItem(),
                                        -1,
                                        action.getHashValue(),
                                        isSymlink);

                                }

                                if (asyncOp.getWorkspace().getOptions().contains(WorkspaceOptions.SET_FILE_TO_CHECKIN)
                                    && !DotNETDate.MIN_CALENDAR.equals(action.getVersionServerDate())) {
                                    new File(action.getTargetLocalItem()).setLastModified(
                                        action.getVersionServerDate().getTimeInMillis());
                                }

                                asyncOp.queueLocalVersionUpdate(
                                    new ClientLocalVersionUpdate(
                                        action.getSourceServerItem(),
                                        action.getItemID(),
                                        action.getTargetLocalItem(),
                                        action.getVersionServer(),
                                        action.getEncoding(),
                                        false,
                                        action.getPropertyValues()));

                                if (existingLocalExists
                                    && action.getCurrentLocalItem() != null
                                    && !LocalPath.equals(action.getCurrentLocalItem(), action.getTargetLocalItem())
                                    && !asyncOp.isNoDiskUpdate()) {
                                    Check.isTrue(
                                        action.getItemType() != ItemType.FOLDER,
                                        MessageFormat.format(
                                            "Should not try to delete a folder here: {0}", //$NON-NLS-1$
                                            action.toString()));

                                    deleteSource(action, existingLocalAttrs);
                                }

                                downloadCompletedHere = true;
                            } else if (null != action.getDownloadURL()) {
                                // Download URL get (common case)
                                asyncGetFile(
                                    action,
                                    existingLocalExists,
                                    existingLocalAttrs,
                                    newLocalExists,
                                    newLocalAttrs,
                                    asyncOp);
                            }
                        }
                    }
                }
            } else
            // Operation is a delete.
            {
                // if the server sent back any and we want to do this if the
                // existing item is a directory
                if (action.getItemType() == ItemType.FOLDER
                    || (action.getItemType() == ItemType.ANY
                        && !existingLocalAttrs.isSymbolicLink()
                        && existingLocalAttrs.isDirectory())) {
                    // Normally, we just have to queue folder deletes. This is
                    // because we don't want to do it until folders are empty
                    // and the async nature of get makes it really hard to
                    // determine when that is. When the current local path is
                    // null, we just fire an event.
                    if (action.getCurrentLocalItem() == null) {
                        recordEvent(asyncOp, OperationStatus.DELETING, action);
                    } else if (!asyncOp.getDeletes().containsKey(action.getCurrentLocalItem())) {
                        asyncOp.getDeletes().put(action.getCurrentLocalItem(), action);
                    }
                } else {
                    // If the file is writable, stop. Otherwise, delete the
                    // file.
                    if (!asyncOp.isOverwrite()
                        && existingLocalExists
                        && WorkspaceLocation.SERVER == asyncOp.getWorkspace().getLocation()
                        && !existingLocalAttrs.isReadOnly()
                        && !existingLocalAttrs.isSymbolicLink()
                        && !action.isOkayToOverwriteExistingLocal()) {
                        Check.isTrue(
                            !action.getEffectiveChangeType().contains(ChangeType.EDIT),
                            MessageFormat.format(
                                "The edit bit is set, yet we are trying to delete this file: {0}", //$NON-NLS-1$
                                action));
                        asyncOp.addWarning(OperationStatus.SOURCE_WRITABLE, action, null);
                    } else {
                        recordEvent(asyncOp, OperationStatus.DELETING, action);

                        // Don't go any further if we aren't actually deleting
                        // it.
                        if (asyncOp.isPreview()) {
                            return;
                        }

                        // Delete the file and acknowledge it.
                        deleteSource(action, existingLocalAttrs);

                        asyncOp.queueLocalVersionUpdate(
                            action,
                            null,
                            action.getVersionServer() != 0 ? action.getVersionServer() : action.getVersionLocal());
                        action.setDownloadCompleted(true);
                        downloadCompletedHere = true;
                    }
                }
            }
        } catch (final PathTooLongException e) {
            /*
             * We already checked the target item at the top of this method, but
             * LocalPath.canonicalize or LocalPath.checkLocalItem may have
             * detected another path that exceeds the limit.
             */
            log.warn("Path too long, not getting", e); //$NON-NLS-1$
            onNonFatalError(
                new VersionControlException(MessageFormat.format(
                    Messages.getString("GetEngine.LocalPathTooLongFormat"), //$NON-NLS-1$
                    newLocalItem)),
                asyncOp.getWorkspace());
        } catch (final CanceledException e) {
            // Don't convert these to non-fatals
            throw e;
        } catch (final Exception e) {
            // Note that we'll catch exceptions due to problems such as unable
            // to open a file for writing because another process has it locked.

            log.warn("Caught and converted an exception: ", e); //$NON-NLS-1$
            onNonFatalError(e, asyncOp.getWorkspace());
        } finally {
            /*
             * Apply .tpattributes if the download completed here. If it was
             * queued for asynch processing, it will not be marked completed
             * here and attributes get applied elsewhere.
             */
            if (downloadCompletedHere && !asyncOp.isPreview() && !asyncOp.isNoDiskUpdate()) {
                applyFileAttributesAfterGet(asyncOp, action);
            }
        }
    }