# HG changeset patch # User Vladimir Voskresensky # Date 1301866529 -14400 # Node ID 9f3296f5eadbac662fd100bce5084b8b85b181fc # Parent 42f556074587a17eee78ba0649c71d50280ad844 fix for #196886: Can not rename source file in imported remote project diff --git a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteDirectory.java b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteDirectory.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteDirectory.java +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteDirectory.java @@ -79,6 +79,7 @@ import org.openide.filesystems.FileEvent; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; import org.openide.util.Exceptions; /** @@ -533,6 +534,231 @@ return true; } + @Override + protected final void renameChild(FileLock lock, RemoteFileObjectBase directChild2Rename, String newNameExt) throws + ConnectException, IOException, InterruptedException, CancellationException, ExecutionException { + String nameExt2Rename = directChild2Rename.getNameExt(); + String name2Rename = directChild2Rename.getName(); + String ext2Rename = directChild2Rename.getExt(); + String path2Rename = directChild2Rename.getPath(); + File storageFile = new File(getCache(), RemoteFileSystem.CACHE_FILE_NAME); + + checkConnection(this, true); + + Lock writeLock = RemoteFileSystem.getLock(getCache()).writeLock(); + if (trace) {trace("waiting for lock");} // NOI18N + writeLock.lock(); + try { + DirectoryStorage storage = getExistingDirectoryStorage(); + boolean fromMemOrDiskCache = (storage != DirectoryStorage.EMPTY); + if (!getCache().exists()) { + getCache().mkdirs(); + if (!getCache().exists()) { + throw new IOException("Can not create cache directory " + getCache()); // NOI18N + } + } + if (trace) {trace("renaming");} // NOI18N + ProcessUtils.ExitStatus ret = ProcessUtils.executeInDir(getPath(), getExecutionEnvironment(), "mv", nameExt2Rename, newNameExt);// NOI18N + if (!ret.isOK()) { + throw new IOException(ret.error); + } + + if (trace) {trace("synchronizing");} // NOI18N + Exception problem = null; + Map newEntries = Collections.emptyMap(); + try { + newEntries = readEntries(storage, true, newNameExt); + } catch (FileNotFoundException ex) { + throw ex; + } catch (IOException ex) { + problem = ex; + } catch (ExecutionException ex) { + problem = ex; + } + if (problem != null) { + if (!ConnectionManager.getInstance().isConnectedTo(getExecutionEnvironment())) { + // connection was broken while we read directory content - add notification + getFileSystem().getRemoteFileSupport().addPendingFile(this); + throw new ConnectException(problem.getMessage()); + } else { + boolean fileNotFoundException = isFileNotFoundException(problem); + if (fileNotFoundException) { + synchronized (refLock) { + storageRef = new SoftReference(DirectoryStorage.EMPTY); + } + } + if (!fileNotFoundException) { + if (problem instanceof IOException) { + throw (IOException) problem; + } else if (problem instanceof ExecutionException) { + throw (ExecutionException) problem; + } else { + throw new IllegalStateException("Unexpected exception class: " + problem.getClass().getName(), problem); //NOI18N + } + } + } + } + getFileSystem().incrementDirSyncCount(); + Map> dupLowerNames = new HashMap>(); + boolean hasDups = false; + boolean changed = true; + Set keepCacheNames = new HashSet(); + List entriesToFireChanged = new ArrayList(); + List entriesToFireCreated = new ArrayList(); + List filesToFireDeleted = new ArrayList(); + for (DirEntry newEntry : newEntries.values()) { + if (newEntry.isValid()) { + String cacheName; + DirEntry oldEntry = storage.getValidEntry(newEntry.getName()); + if (oldEntry == null || !oldEntry.isValid()) { + changed = true; + cacheName = RemoteFileSystemUtils.escapeFileName(newEntry.getName()); + if (newEntry.getName().equals(newNameExt)) { + DirEntry renamedEntry = storage.getValidEntry(nameExt2Rename); + // NPE check? + cacheName = renamedEntry.getCache(); + } else if (fromMemOrDiskCache) { + entriesToFireCreated.add(newEntry); + } + } else { + if (oldEntry.isSameType(newEntry)) { + cacheName = oldEntry.getCache(); + keepCacheNames.add(newEntry); + boolean fire = false; + if (!newEntry.isSameLastModified(oldEntry)) { + if (newEntry.isPlainFile()) { + changed = fire = true; + File entryCache = new File(getCache(), oldEntry.getCache()); + if (entryCache.exists()) { + if (trace) { + trace("removing cache for updated file {0}", entryCache.getAbsolutePath()); + } // NOI18N + entryCache.delete(); // TODO: We must just mark it as invalid instead of physically deleting cache file... + } + } + } else if (!equals(newEntry.getLinkTarget(), oldEntry.getLinkTarget())) { + changed = fire = true; // TODO: we forgot old link path, probably should be passed to change event + getFileSystem().getFactory().setLink(this, getPath() + '/' + newEntry.getName(), newEntry.getLinkTarget()); + } else if (!newEntry.getAccessAsString().equals(oldEntry.getAccessAsString())) { + changed = fire = true; + } else if (!newEntry.isSameUser(oldEntry)) { + changed = fire = true; + } else if (!newEntry.isSameGroup(oldEntry)) { + changed = fire = true; + } else if (newEntry.getSize() != oldEntry.getSize()) { + changed = fire = true;// TODO: shouldn't it be the same as time stamp change? + } + if (fire) { + entriesToFireChanged.add(newEntry); + } + } else { + changed = true; + FileObject removedFO = invalidate(oldEntry); + // remove old + if (removedFO != null) { + filesToFireDeleted.add(removedFO); + } + // add new + entriesToFireCreated.add(newEntry); + cacheName = RemoteFileSystemUtils.escapeFileName(newEntry.getName()); + } + } + newEntry.setCache(cacheName); + if (!RemoteFileSystemUtils.isSystemCaseSensitive()) { + String lowerCacheName = newEntry.getCache().toLowerCase(); + List dupEntries = dupLowerNames.get(lowerCacheName); + if (dupEntries == null) { + dupEntries = new ArrayList(); + dupLowerNames.put(lowerCacheName, dupEntries); + } else { + hasDups = true; + } + dupEntries.add(newEntry); + } + } else { + changed = true; + } + } + if (changed) { + // Check for removal + for (DirEntry oldEntry : storage.listValid()) { + if (!oldEntry.getName().equals(nameExt2Rename)) { + DirEntry newEntry = newEntries.get(oldEntry.getName()); + if (newEntry == null || !newEntry.isValid()) { + FileObject removedFO = invalidate(oldEntry); + if (removedFO != null) { + filesToFireDeleted.add(removedFO); + } + } + } + } + if (hasDups) { + for (Map.Entry> mapEntry : + new ArrayList>>(dupLowerNames.entrySet())) { + + List dupEntries = mapEntry.getValue(); + if (dupEntries.size() > 1) { + for (int i = 0; i < dupEntries.size(); i++) { + DirEntry entry = dupEntries.get(i); + if (keepCacheNames.contains(entry)) { + continue; // keep the one that already exists + } + // all duplicates will have postfix + for (int j = 0; j < Integer.MAX_VALUE; j++) { + String cacheName = mapEntry.getKey() + '_' + j; + String lowerCacheName = cacheName.toLowerCase(); + if (!dupLowerNames.containsKey(lowerCacheName)) { + if (trace) { + trace("resolving cache names conflict in {0}: {1} -> {2}", // NOI18N + getCache().getAbsolutePath(), entry.getCache(), cacheName); + } + entry.setCache(cacheName); + dupLowerNames.put(lowerCacheName, Collections.singletonList(entry)); + break; + } + } + } + } + } + } + storage = new DirectoryStorage(storageFile, newEntries.values()); + storage.store(); + } else { + storage.touch(); + } + // always put new content in cache + // do it before firing events, to give liseners real content + synchronized (refLock) { + storageRef = new SoftReference(storage); + } + // fire all event under lock + if (changed) { + for (FileObject deleted : filesToFireDeleted) { + fireFileDeletedEvent(getListeners(), new FileEvent(this, deleted)); + } + for (DirEntry entry : entriesToFireCreated) { + RemoteFileObjectBase fo = createFileObject(entry); + fireRemoteFileObjectCreated(fo); + } + for (DirEntry entry : entriesToFireChanged) { + RemoteFileObjectBase fo = getFileSystem().getFactory().getCachedFileObject(getPath() + '/' + entry.getName()); + if (fo != null) { + fireFileChangedEvent(getListeners(), new FileEvent(fo)); + } + } + // rename itself + String newPath = getPath() + '/' + newNameExt; + getFileSystem().getFactory().rename(path2Rename, newPath, directChild2Rename); + // fire rename + fireFileRenamedEvent(directChild2Rename.getListeners(), new FileRenameEvent(directChild2Rename, directChild2Rename, name2Rename, ext2Rename)); + fireFileRenamedEvent(this.getListeners(), new FileRenameEvent(this, directChild2Rename, name2Rename, ext2Rename)); + //fireFileChangedEvent(getListeners(), new FileEvent(this)); + } + } finally { + writeLock.unlock(); + } + } + private DirectoryStorage getDirectoryStorageImpl(boolean forceRefresh, String expectedName, String childName) throws ConnectException, IOException, InterruptedException, CancellationException, ExecutionException { diff --git a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectBase.java b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectBase.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectBase.java +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectBase.java @@ -72,7 +72,7 @@ private final RemoteFileSystem fileSystem; private final RemoteFileObjectBase parent; - private final String remotePath; + private volatile String remotePath; private final File cache; private CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); private final FileLock lock = new FileLock(); @@ -340,7 +340,40 @@ @Override public void rename(FileLock lock, String name, String ext) throws IOException { - throw new ReadOnlyException(); + RemoteFileObjectBase p = getParent(); + if (p != null) { + String newNameExt = composeName(name, ext); + if (newNameExt.equals(getNameExt())) { + // nothing to rename + return; + } + if (!p.isValid()) { + throw new IOException("Can not rename in " + p.getPath());//NOI18N + } + // Can not rename in read only folder + if (!p.canWrite()) { + throw new IOException("Can not rename in read only " + p.getPath());//NOI18N + } + // check there are no other child with such name + if (p.getFileObject(newNameExt) != null) { + throw new IOException("Can not rename to " + newNameExt);//NOI18N + } + + if (!ConnectionManager.getInstance().isConnectedTo(getExecutionEnvironment())) { + throw new IOException("No connection: Can not rename in " + p.getPath()); //NOI18N + } + try { + p.renameChild(lock, this, newNameExt); + } catch (ConnectException ex) { + throw new IOException("No connection: Can not rename in " + p.getPath(), ex); //NOI18N + } catch (InterruptedException ex) { + throw new IOException("interrupted: Can not rename in " + p.getPath(), ex); //NOI18N + } catch (CancellationException ex) { + throw new IOException("cancelled: Can not rename in " + p.getPath(), ex); //NOI18N + } catch (ExecutionException ex) { + throw new IOException("Can not rename to " + newNameExt + ": exception occurred", ex); // NOI18N + } + } } @Override @@ -365,6 +398,12 @@ } public abstract FileType getType(); + protected abstract void renameChild(FileLock lock, RemoteFileObjectBase toRename, String newNameExt) + throws ConnectException, IOException, InterruptedException, CancellationException, ExecutionException; + + final void renamePath(String newPath) { + this.remotePath = newPath; + } private static class ReadOnlyException extends IOException { public ReadOnlyException() { diff --git a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectFactory.java b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectFactory.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectFactory.java +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectFactory.java @@ -84,7 +84,7 @@ public RemoteFileObjectBase getCachedFileObject(String path) { return fileObjectsCache.get(path); } - + private void scheduleCleanDeadEntries() { cleaningTask.schedule(CLEAN_INTERVAL); } @@ -195,7 +195,13 @@ } return fo; } - + + public void rename(String path2Rename, String newPath, RemoteFileObjectBase directChild2Rename) { + fileObjectsCache.remove(path2Rename, directChild2Rename); + directChild2Rename.renamePath(newPath); + fileObjectsCache.putIfAbsent(newPath, directChild2Rename); + } + public void setLink(RemoteDirectory parent, String linkRemotePath, String linkTarget) { RemoteFileObjectBase fo = fileObjectsCache.get(linkRemotePath); if (fo != null) { diff --git a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteLinkBase.java b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteLinkBase.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteLinkBase.java +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteLinkBase.java @@ -215,6 +215,22 @@ } @Override + public void rename(FileLock lock, String name, String ext) throws IOException { + RemoteFileObjectBase delegate = getDelegate(); + if (delegate != null) { + delegate.rename(lock, name, ext); + } else { + throw new IOException("can not rename " + getPath()); //NOI18N + } + } + + @Override + protected void renameChild(FileLock lock, RemoteFileObjectBase toRename, String newNameExt) + throws ConnectException, IOException, InterruptedException, CancellationException, ExecutionException{ + throw new UnsupportedOperationException(); + } + + @Override public FileObject createFolder(String name) throws IOException { RemoteFileObjectBase delegate = getDelegate(); if (delegate != null) { diff --git a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemotePlainFile.java b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemotePlainFile.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemotePlainFile.java +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemotePlainFile.java @@ -167,6 +167,12 @@ } @Override + protected void renameChild(FileLock lock, RemoteFileObjectBase toRename, String newNameExt) + throws ConnectException, IOException, InterruptedException, CancellationException, ExecutionException { + throw new UnsupportedOperationException(); + } + + @Override public OutputStream getOutputStream(FileLock lock) throws IOException { if (!isValid()) { throw new FileNotFoundException("FileObject " + this + " is not valid."); //NOI18N