private void scanLocal()

in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/versioncontrol/offline/OfflineSynchronizer.java [461:594]


    private void scanLocal(final File file, final int depth) throws Exception {
        if (taskMonitor.isCanceled()) {
            throw new CanceledException();
        }

        // Check symlink attribute on the non-canonicalized name
        final FileSystemUtils util = FileSystemUtils.getInstance();
        final FileSystemAttributes attrs = util.getAttributes(file);

        String path = file.getAbsolutePath();
        if (!attrs.isSymbolicLink()) {
            path = canonicalPath(file);
        }

        // symlink not supported for previous versions, directly skip and return
        if (workspace.getClient().getServiceLevel().getValue() < WebServiceLevel.TFS_2012_2.getValue()
            && attrs.isSymbolicLink()) {
            serverFiles.remove(path);
            return;
        }

        final boolean exists = serverFiles.containsKey(path);
        final byte[] hashCode = serverFiles.remove(path);
        final ItemType serverItemType = (hashCode == null || hashCode.length == 0) ? ItemType.FOLDER : ItemType.FILE;

        /*
         * The file may not actually exist locally. This may happen if this is
         * the root and was based on user input. For example, the user can
         * select "Go Online" for a single file in Explorer that's pended for a
         * delete. In this case, we push the hashCode back into the serverFiles
         * list.
         */
        if (!attrs.isSymbolicLink() && !file.exists()) {
            serverFiles.put(path, hashCode);
        } else if (file.isFile() || attrs.isSymbolicLink()) {
            OfflineChangeType type = null;
            OfflineChangeType propertyType = null;

            // if the file does not exist on the server, we should pend an add
            if (!exists && detectAdded) {
                type = OfflineChangeType.ADD;
                if (attrs.isSymbolicLink()) {
                    propertyType = OfflineChangeType.SYMLINK;
                } else if (attrs.isExecutable()) {
                    propertyType = OfflineChangeType.EXEC;
                }
            } else if (exists) {
                /**
                 * For symbolic link, compute the MD5 hash based on the targeted
                 * link and compare with server hash
                 */
                if (attrs.isSymbolicLink()) {
                    final String targetLink = util.getSymbolicLink(file.getPath());
                    final byte[] localHashByLink = HashUtils.hashString(targetLink, null, HashUtils.ALGORITHM_MD5);
                    if (!Arrays.equals(localHashByLink, hashCode)) {
                        type = OfflineChangeType.EDIT;
                    }
                } else if (isChanged(file, hashCode)) {
                    type = OfflineChangeType.EDIT;
                }

                if (workspace.getClient().getServiceLevel().getValue() >= WebServiceLevel.TFS_2012.getValue()) {
                    // query server for symlink and exec property, detect
                    // property change on local disk
                    boolean symlinkOnServer = false;
                    boolean executable = false;
                    final ItemSet[] items = workspace.getClient().getItems(
                        new ItemSpec[] {
                            new ItemSpec(path, RecursionType.NONE)
                    },
                        LatestVersionSpec.INSTANCE,
                        DeletedState.ANY,
                        ItemType.ANY,
                        GetItemsOptions.NONE,
                        PropertyConstants.QUERY_ALL_PROPERTIES_FILTERS);

                    if (items != null
                        && items.length > 0
                        && items[0].getItems() != null
                        && items[0].getItems().length > 0) {
                        final PropertyValue[] propertyValues = items[0].getItems()[0].getPropertyValues();
                        symlinkOnServer = PropertyConstants.IS_SYMLINK.equals(
                            PropertyUtils.selectMatching(propertyValues, PropertyConstants.SYMBOLIC_KEY));
                        executable = PropertyConstants.EXECUTABLE_ENABLED_VALUE.equals(
                            PropertyUtils.selectMatching(propertyValues, PropertyConstants.EXECUTABLE_KEY));
                    }

                    if (symlinkOnServer != attrs.isSymbolicLink()) {
                        propertyType =
                            attrs.isSymbolicLink() ? OfflineChangeType.SYMLINK : OfflineChangeType.NOT_SYMLINK;
                    } else if (!attrs.isSymbolicLink() && executable != attrs.isExecutable()) {
                        propertyType = attrs.isExecutable() ? OfflineChangeType.EXEC : OfflineChangeType.NOT_EXEC;
                    }
                }
            }

            OfflineChange newChange = null;
            if (type != null && filter.shouldPend(file, type, serverItemType)) {
                newChange = new OfflineChange(path, type, serverItemType);
                if (propertyType != null) {
                    newChange.addChangeType(propertyType);
                }
            } else if (propertyType != null && filter.shouldPend(file, propertyType, serverItemType)) {
                newChange = new OfflineChange(path, propertyType, serverItemType);
            }

            if (newChange != null) {
                offlineChanges.add(newChange);
            }
        } else if (file.isDirectory()) {
            // pend an add if this directory didn't exist
            if (!exists && filter.shouldPend(file, OfflineChangeType.ADD, serverItemType)) {
                offlineChanges.add(new OfflineChange(path, OfflineChangeType.ADD, serverItemType));
            }

            if (filter.shouldRecurse(file)) {
                // recurse into this directory further
                if (shouldRecurse(file, depth)) {
                    final String[] contents = file.list();

                    for (int i = 0; i < contents.length; i++) {
                        final File child = new File(path + File.separatorChar + contents[i]);
                        scanLocal(child, depth + 1);
                    }
                }
            }
            // this directory was excluded - mark it as such so that we don
            // pend deletes against it later (since we're not scanning it
            // locally)
            else {
                excludes.add(path);
            }
        }
    }