diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxCopyFeature.java b/box/src/main/java/ch/cyberduck/core/box/BoxCopyFeature.java index 0771b441440..d12f1a91a1f 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxCopyFeature.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxCopyFeature.java @@ -39,6 +39,8 @@ import java.util.Collections; import java.util.EnumSet; +import static ch.cyberduck.core.features.Copy.validate; + public class BoxCopyFeature implements Copy { private static final Logger log = LogManager.getLogger(BoxCopyFeature.class); @@ -89,5 +91,6 @@ public void preflight(final Path source, final Path target) throws BackgroundExc if(!BoxTouchFeature.validate(target.getName())) { throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); } + validate(session.getCaseSensitivity(), source, target); } } diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxFileidProvider.java b/box/src/main/java/ch/cyberduck/core/box/BoxFileidProvider.java index a083a6ae5d5..bdbdce6a01b 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxFileidProvider.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxFileidProvider.java @@ -16,9 +16,9 @@ */ import ch.cyberduck.core.CachingFileIdProvider; +import ch.cyberduck.core.CaseInsensitivePathPredicate; import ch.cyberduck.core.DisabledListProgressListener; import ch.cyberduck.core.Path; -import ch.cyberduck.core.SimplePathPredicate; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.FileIdProvider; @@ -53,7 +53,7 @@ public String getFileId(final Path file) throws BackgroundException { return ROOT; } final Path f = new BoxListService(session, this).list(file.getParent(), - new DisabledListProgressListener()).find(new SimplePathPredicate(file)); + new DisabledListProgressListener()).find(new CaseInsensitivePathPredicate(file)); if(null == f) { throw new NotfoundException(file.getAbsolute()); } diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxMoveFeature.java b/box/src/main/java/ch/cyberduck/core/box/BoxMoveFeature.java index dbe84ef275b..dd5f8285781 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxMoveFeature.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxMoveFeature.java @@ -55,8 +55,10 @@ public BoxMoveFeature(final BoxSession session, final BoxFileidProvider fileid) public Path move(final Path file, final Path renamed, final TransferStatus status, final Delete.Callback delete, final ConnectionCallback callback) throws BackgroundException { try { if(status.isExists()) { - log.warn("Delete file {} to be replaced with {}", renamed, file); - new BoxDeleteFeature(session, fileid).delete(Collections.singletonList(renamed), callback, delete); + if(!fileid.getFileId(file).equals(fileid.getFileId(renamed))) { + log.warn("Delete file {} to be replaced with {}", renamed, file); + new BoxDeleteFeature(session, fileid).delete(Collections.singletonList(renamed), callback, delete); + } } final String id = fileid.getFileId(file); if(file.isDirectory()) { @@ -93,7 +95,7 @@ public EnumSet features(final Path source, final Path target) { @Override public void preflight(final Path source, final Path target) throws BackgroundException { if(!BoxTouchFeature.validate(target.getName())) { - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())).withFile(source); } } } diff --git a/box/src/test/java/ch/cyberduck/core/box/BoxFileidProviderTest.java b/box/src/test/java/ch/cyberduck/core/box/BoxFileidProviderTest.java index 6b595236ea9..771504fb304 100644 --- a/box/src/test/java/ch/cyberduck/core/box/BoxFileidProviderTest.java +++ b/box/src/test/java/ch/cyberduck/core/box/BoxFileidProviderTest.java @@ -15,15 +15,24 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.AlphanumericRandomStringService; +import ch.cyberduck.core.DisabledLoginCallback; import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; +import ch.cyberduck.core.exception.NotfoundException; +import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.shared.DefaultHomeFinderService; +import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; +import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.experimental.categories.Category; +import java.util.Collections; import java.util.EnumSet; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; @Category(IntegrationTest.class) public class BoxFileidProviderTest extends AbstractBoxTest { @@ -33,4 +42,29 @@ public void getFileIdRoot() throws Exception { assertEquals(BoxFileidProvider.ROOT, new BoxFileidProvider(session).getFileId( new Path("/", EnumSet.of(Path.Type.directory)))); } + + @Test + public void getFileIdFile() throws Exception { + final BoxFileidProvider nodeid = new BoxFileidProvider(session); + final Path home = new DefaultHomeFinderService(session).find(); + final String name = String.format("%s%s", new AlphanumericRandomStringService().random(), new AlphanumericRandomStringService().random()); + final Path file = new BoxTouchFeature(session, nodeid).touch(new Path(home, name, EnumSet.of(Path.Type.file)), new TransferStatus()); + nodeid.clear(); + final String nodeId = nodeid.getFileId(new Path(home, name, EnumSet.of(Path.Type.file))); + assertNotNull(nodeId); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home.withAttributes(PathAttributes.EMPTY), name, EnumSet.of(Path.Type.file)))); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home, StringUtils.upperCase(name), EnumSet.of(Path.Type.file)))); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home, StringUtils.lowerCase(name), EnumSet.of(Path.Type.file)))); + try { + assertNull(nodeid.getFileId(new Path(home, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)))); + fail(); + } + catch(NotfoundException e) { + // Expected + } + new BoxDeleteFeature(session, nodeid).delete(Collections.singletonList(file), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } } \ No newline at end of file diff --git a/box/src/test/java/ch/cyberduck/core/box/BoxMoveFeatureTest.java b/box/src/test/java/ch/cyberduck/core/box/BoxMoveFeatureTest.java index 099b97e75c6..6da0abea2a8 100644 --- a/box/src/test/java/ch/cyberduck/core/box/BoxMoveFeatureTest.java +++ b/box/src/test/java/ch/cyberduck/core/box/BoxMoveFeatureTest.java @@ -28,6 +28,7 @@ import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; +import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -90,4 +91,14 @@ public void testMoveNotFound() throws Exception { final Path test = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); new BoxMoveFeature(session, fileid).move(test, new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus(), new Delete.DisabledCallback(), new DisabledConnectionCallback()); } + + @Test + public void testRenameCaseOnly() throws Exception { + final BoxFileidProvider fileid = new BoxFileidProvider(session); + final String name = new AlphanumericRandomStringService().random(); + final Path file = new BoxTouchFeature(session, fileid).touch(new Path(new DefaultHomeFinderService(session).find(), StringUtils.capitalize(name), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Path rename = new Path(new DefaultHomeFinderService(session).find(), StringUtils.lowerCase(name), EnumSet.of(Path.Type.file)); + new BoxMoveFeature(session, fileid).move(file, rename, new TransferStatus().exists(true), new Delete.DisabledCallback(), new DisabledConnectionCallback()); + new BoxDeleteFeature(session, fileid).delete(Collections.singletonList(rename), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } } \ No newline at end of file diff --git a/brick/src/main/java/ch/cyberduck/core/brick/BrickCopyFeature.java b/brick/src/main/java/ch/cyberduck/core/brick/BrickCopyFeature.java index aa6d09abb9b..24ee9a4971f 100644 --- a/brick/src/main/java/ch/cyberduck/core/brick/BrickCopyFeature.java +++ b/brick/src/main/java/ch/cyberduck/core/brick/BrickCopyFeature.java @@ -34,6 +34,8 @@ import java.util.Collections; import java.util.EnumSet; +import static ch.cyberduck.core.features.Copy.validate; + public class BrickCopyFeature extends BrickFileMigrationFeature implements Copy { private static final Logger log = LogManager.getLogger(BrickCopyFeature.class); @@ -69,4 +71,10 @@ public Path copy(final Path file, final Path target, final TransferStatus status public EnumSet features(final Path source, final Path target) { return EnumSet.of(Flags.recursive); } + + @Override + public void preflight(final Path source, final Path target) throws BackgroundException { + Copy.super.preflight(source, target); + validate(session.getCaseSensitivity(), source, target); + } } diff --git a/core/src/main/java/ch/cyberduck/core/CaseInsensitivePathPredicate.java b/core/src/main/java/ch/cyberduck/core/CaseInsensitivePathPredicate.java index 23f7cf1c062..8f7075a1e5f 100644 --- a/core/src/main/java/ch/cyberduck/core/CaseInsensitivePathPredicate.java +++ b/core/src/main/java/ch/cyberduck/core/CaseInsensitivePathPredicate.java @@ -15,18 +15,17 @@ * GNU General Public License for more details. */ -import ch.cyberduck.core.unicode.NFCNormalizer; -import ch.cyberduck.core.unicode.UnicodeNormalizer; - import org.apache.commons.lang3.StringUtils; -public final class CaseInsensitivePathPredicate extends SimplePathPredicate { - - private static final UnicodeNormalizer normalizer = new NFCNormalizer(); +public class CaseInsensitivePathPredicate extends SimplePathPredicate { public CaseInsensitivePathPredicate(final Path file) { super(file.isSymbolicLink() ? Path.Type.symboliclink : file.isFile() ? Path.Type.file : Path.Type.directory, - StringUtils.lowerCase(normalizer.normalize(file.getAbsolute()).toString())); + StringUtils.lowerCase(file.getAbsolute())); + } + + public CaseInsensitivePathPredicate(final Path.Type type, final String path) { + super(type, StringUtils.lowerCase(path)); } @Override diff --git a/core/src/main/java/ch/cyberduck/core/features/Copy.java b/core/src/main/java/ch/cyberduck/core/features/Copy.java index 8b470875a52..920a7c386ee 100644 --- a/core/src/main/java/ch/cyberduck/core/features/Copy.java +++ b/core/src/main/java/ch/cyberduck/core/features/Copy.java @@ -15,10 +15,13 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.CaseInsensitivePathPredicate; import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.LocaleFactory; import ch.cyberduck.core.Path; +import ch.cyberduck.core.Protocol; import ch.cyberduck.core.Session; +import ch.cyberduck.core.SimplePathPredicate; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; @@ -86,8 +89,32 @@ default void preflight(final Path source, final Path target) throws BackgroundEx throw new UnsupportedException(MessageFormat.format(LocaleFactory.localizedString("Cannot copy {0}", "Error"), source.getName())).withFile(source); } + validate(Protocol.Case.sensitive, source, target); } + /** + * Fail when source and target only differ with change in case of filename and protocol is case-insensitive + * + * @throws UnsupportedException Copying file to same location is not supported + */ + static void validate(final Protocol.Case sensitivity, final Path source, final Path target) throws BackgroundException { + switch(sensitivity) { + case insensitive: + if(new CaseInsensitivePathPredicate(source).test(target)) { + throw new UnsupportedException(MessageFormat.format(LocaleFactory.localizedString("Cannot copy {0}", "Error"), + source.getName())).withFile(source); + } + break; + case sensitive: + if(new SimplePathPredicate(source).test(target)) { + throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot copy {0}", "Error"), + source.getName())).withFile(source); + } + break; + } + } + + /** * @return Supported features */ diff --git a/core/src/main/java/ch/cyberduck/core/features/Move.java b/core/src/main/java/ch/cyberduck/core/features/Move.java index 8c39eb1e215..bf1440c33e3 100644 --- a/core/src/main/java/ch/cyberduck/core/features/Move.java +++ b/core/src/main/java/ch/cyberduck/core/features/Move.java @@ -19,6 +19,7 @@ import ch.cyberduck.core.LocaleFactory; import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; +import ch.cyberduck.core.SimplePathPredicate; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; @@ -85,15 +86,19 @@ default void preflight(final Path source, final Path target) throws BackgroundEx throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source); } + // Deny move to self + if(new SimplePathPredicate(source).test(target)) { + throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), + source.getName())).withFile(source); + } } - /** * @return Supported features */ - default EnumSet features(Path source, Path target) { - return EnumSet.noneOf(Flags.class); - } + default EnumSet features(Path source, Path target) { + return EnumSet.noneOf(Flags.class); + } /** * Feature flags diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java index 20612ca011b..c7da5c13e26 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java @@ -65,7 +65,7 @@ public EnumSet features(final Path source, final Path target) { @Override public void preflight(final Path source, final Path target) throws BackgroundException { if(!vault.getFilenameProvider().isValid(target.getName())) { - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())).withFile(source); } proxy.preflight(source, target); } diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java index 433e6b9c152..b7acc2e9c78 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java @@ -71,7 +71,7 @@ public EnumSet features(final Path source, final Path target) { @Override public void preflight(final Path source, final Path target) throws BackgroundException { if(!vault.getFilenameProvider().isValid(target.getName())) { - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())).withFile(source); } proxy.preflight(source, target); } diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraMoveFeature.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraMoveFeature.java index 41ed07e66ab..14ea4102885 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraMoveFeature.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraMoveFeature.java @@ -34,7 +34,7 @@ public CteraMoveFeature(final CteraSession session) { @Override public void preflight(final Path source, final Path target) throws BackgroundException { if(!CteraTouchFeature.validate(target.getName())) { - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), target.getName())).withFile(source); } assumeRole(source, DELETEPERMISSION); // defaults to Acl.EMPTY (disabling role checking) if target does not exist diff --git a/deepbox/src/main/java/ch/cyberduck/core/deepbox/DeepboxMoveFeature.java b/deepbox/src/main/java/ch/cyberduck/core/deepbox/DeepboxMoveFeature.java index fde73f00532..d18d2c06aef 100644 --- a/deepbox/src/main/java/ch/cyberduck/core/deepbox/DeepboxMoveFeature.java +++ b/deepbox/src/main/java/ch/cyberduck/core/deepbox/DeepboxMoveFeature.java @@ -84,7 +84,7 @@ public void preflight(final Path source, final Path target) throws BackgroundExc throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source); } if(target.isRoot() || new DeepboxPathContainerService(session, fileid).isContainer(target) || new DeepboxPathContainerService(session, fileid).isInTrash(target)) { - throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); + throw new AccessDeniedException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())).withFile(source); } final Acl acl = source.attributes().getAcl(); if(Acl.EMPTY == acl) { diff --git a/deepbox/src/test/java/ch/cyberduck/core/deepbox/DeepboxFindFeatureTest.java b/deepbox/src/test/java/ch/cyberduck/core/deepbox/DeepboxFindFeatureTest.java index 0463eefeaca..4b9d8548f23 100644 --- a/deepbox/src/test/java/ch/cyberduck/core/deepbox/DeepboxFindFeatureTest.java +++ b/deepbox/src/test/java/ch/cyberduck/core/deepbox/DeepboxFindFeatureTest.java @@ -24,6 +24,7 @@ import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; +import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -66,10 +67,11 @@ public void testFindDirectory() throws Exception { @Test public void testFindFile() throws Exception { final Path box = new Path("/ORG 4 - DeepBox Desktop App/ORG 4 - DeepBox Desktop App/ORG3:Box1/Documents", EnumSet.of(Path.Type.directory, Path.Type.volume)); - final Path file = new Path(box, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); + final Path file = new Path(box, StringUtils.lowerCase(new AlphanumericRandomStringService().random()), EnumSet.of(Path.Type.file)); final DeepboxIdProvider nodeid = new DeepboxIdProvider(session); new DeepboxTouchFeature(session, nodeid).touch(file, new TransferStatus()); assertTrue(new DeepboxFindFeature(session, nodeid).find(file)); + assertFalse(new DeepboxFindFeature(session, nodeid).find(new Path(box, StringUtils.upperCase(file.getName()), EnumSet.of(Path.Type.file)))); assertFalse(new DeepboxFindFeature(session, nodeid).find(new Path(file.getAbsolute(), EnumSet.of(Path.Type.directory)))); new DeepboxDeleteFeature(session, nodeid).delete(Collections.singletonList(file), new DisabledLoginCallback(), new Delete.DisabledCallback()); } diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java index d429d47dfa7..2a23a91cf3d 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java @@ -39,6 +39,8 @@ import java.util.EnumSet; import java.util.Objects; +import static ch.cyberduck.core.features.Copy.validate; + public class SDSCopyFeature implements Copy { private static final Logger log = LogManager.getLogger(SDSCopyFeature.class); @@ -101,5 +103,6 @@ public void preflight(final Path source, final Path target) throws BackgroundExc log.warn("Deny copy of {} to {}", source, target); throw new UnsupportedException(MessageFormat.format(LocaleFactory.localizedString("Cannot copy {0}", "Error"), source.getName())).withFile(source); } + validate(session.getCaseSensitivity(), source, target); } } diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java index 31c3aecf0be..bf45b996c71 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java @@ -130,7 +130,7 @@ public void preflight(final Path source, final Path target) throws BackgroundExc } if(!SDSTouchFeature.validate(target.getName())) { log.warn("Validation failed for target name {}", target); - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(target); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), target.getName())).withFile(source); } final SDSPermissionsFeature acl = new SDSPermissionsFeature(session, nodeid); if(!new SimplePathPredicate(source.getParent()).test(target.getParent())) { diff --git a/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxCopyFeature.java b/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxCopyFeature.java index 2b328810d91..3eb2651ff42 100644 --- a/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxCopyFeature.java +++ b/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxCopyFeature.java @@ -37,6 +37,8 @@ import com.dropbox.core.v2.files.DbxUserFilesRequests; import com.dropbox.core.v2.files.RelocationResult; +import static ch.cyberduck.core.features.Copy.validate; + public class DropboxCopyFeature implements Copy { private static final Logger log = LogManager.getLogger(DropboxCopyFeature.class); @@ -75,5 +77,6 @@ public void preflight(final Path source, final Path target) throws BackgroundExc if(!DropboxTouchFeature.validate(target.getName())) { throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); } + validate(session.getCaseSensitivity(), source, target); } } diff --git a/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxMoveFeature.java b/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxMoveFeature.java index 019f0a8849e..8d1d1483ee9 100644 --- a/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxMoveFeature.java +++ b/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxMoveFeature.java @@ -15,6 +15,7 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.CaseInsensitivePathPredicate; import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.LocaleFactory; import ch.cyberduck.core.Path; @@ -51,8 +52,10 @@ public DropboxMoveFeature(final DropboxSession session) { public Path move(final Path file, final Path renamed, final TransferStatus status, final Delete.Callback callback, final ConnectionCallback connectionCallback) throws BackgroundException { try { if(status.isExists()) { - log.warn("Delete file {} to be replaced with {}", renamed, file); - new DropboxDeleteFeature(session).delete(Collections.singletonMap(renamed, status), connectionCallback, callback); + if(!new CaseInsensitivePathPredicate(file).test(renamed)) { + log.warn("Delete file {} to be replaced with {}", renamed, file); + new DropboxDeleteFeature(session).delete(Collections.singletonMap(renamed, status), connectionCallback, callback); + } } final RelocationResult result = new DbxUserFilesRequests(session.getClient(file)).moveV2(containerService.getKey(file), containerService.getKey(renamed)); return renamed.withAttributes(new DropboxAttributesFinderFeature(session).toAttributes(result.getMetadata())); @@ -70,7 +73,7 @@ public EnumSet features(final Path source, final Path target) { @Override public void preflight(final Path source, final Path target) throws BackgroundException { if(!DropboxTouchFeature.validate(target.getName())) { - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())).withFile(source); } } } diff --git a/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxMoveFeatureTest.java b/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxMoveFeatureTest.java index 1b58f64931c..d89059fa0c0 100644 --- a/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxMoveFeatureTest.java +++ b/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxMoveFeatureTest.java @@ -153,4 +153,14 @@ public void testMoveNotFound() throws Exception { final Path test = new Path(home, UUID.randomUUID().toString(), EnumSet.of(Path.Type.file)); assertThrows(NotfoundException.class, () -> feature.move(test, new Path(home, UUID.randomUUID().toString(), EnumSet.of(Path.Type.file)), new TransferStatus(), new Delete.DisabledCallback(), new DisabledConnectionCallback())); } + + @Test + public void testRenameCaseOnly() throws Exception { + final Path home = new DefaultHomeFinderService(session).find(); + final String name = new AlphanumericRandomStringService().random(); + final Path file = new DropboxTouchFeature(session).touch(new Path(home, StringUtils.upperCase(name), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Path rename = new Path(home, StringUtils.lowerCase(name), EnumSet.of(Path.Type.file)); + new DropboxMoveFeature(session).move(file, rename, new TransferStatus().exists(true), new Delete.DisabledCallback(), new DisabledConnectionCallback()); + new DropboxDeleteFeature(session).delete(Collections.singletonList(rename), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } } diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueCopyFeature.java b/eue/src/main/java/ch/cyberduck/core/eue/EueCopyFeature.java index 2ec039e3dd9..4591927b34f 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueCopyFeature.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueCopyFeature.java @@ -45,6 +45,8 @@ import java.util.Collections; import java.util.EnumSet; +import static ch.cyberduck.core.features.Copy.validate; + public class EueCopyFeature implements Copy { private static final Logger log = LogManager.getLogger(EueCopyFeature.class); @@ -136,6 +138,7 @@ public void preflight(final Path source, final Path target) throws BackgroundExc if(!EueTouchFeature.validate(target.getName())) { throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); } + validate(session.getCaseSensitivity(), source, target); } @Override diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueMoveFeature.java b/eue/src/main/java/ch/cyberduck/core/eue/EueMoveFeature.java index 479ad0fcb2e..2822fb26fb3 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueMoveFeature.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueMoveFeature.java @@ -148,7 +148,7 @@ public void preflight(final Path source, final Path target) throws BackgroundExc throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source); } if(!EueTouchFeature.validate(target.getName())) { - throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())); + throw new InvalidFilenameException(MessageFormat.format(LocaleFactory.localizedString("Cannot create {0}", "Error"), target.getName())).withFile(source); } } diff --git a/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveMoveFeature.java b/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveMoveFeature.java index 74accc7f4a1..ae5589087ba 100644 --- a/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveMoveFeature.java +++ b/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveMoveFeature.java @@ -104,7 +104,7 @@ public EnumSet features(final Path source, final Path target) { @Override public void preflight(final Path source, final Path target) throws BackgroundException { if(target.getParent().isRoot()) { - throw new UnsupportedException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(target); + throw new UnsupportedException(MessageFormat.format(LocaleFactory.localizedString("Cannot rename {0}", "Error"), source.getName())).withFile(source); } if(source.isPlaceholder()) { // Disable for application/vnd.google-apps diff --git a/nio/src/main/java/ch/cyberduck/core/nio/LocalCopyFeature.java b/nio/src/main/java/ch/cyberduck/core/nio/LocalCopyFeature.java index 6246d5297be..d31bdcc58d1 100644 --- a/nio/src/main/java/ch/cyberduck/core/nio/LocalCopyFeature.java +++ b/nio/src/main/java/ch/cyberduck/core/nio/LocalCopyFeature.java @@ -27,6 +27,8 @@ import java.nio.file.StandardCopyOption; import java.util.EnumSet; +import static ch.cyberduck.core.features.Copy.validate; + public class LocalCopyFeature implements Copy { private final LocalSession session; @@ -47,6 +49,12 @@ public Path copy(final Path source, final Path target, final TransferStatus stat } } + @Override + public void preflight(final Path source, final Path target) throws BackgroundException { + Copy.super.preflight(source, target); + validate(session.getCaseSensitivity(), source, target); + } + @Override public EnumSet features(final Path source, final Path target) { return EnumSet.of(Flags.recursive); diff --git a/nio/src/main/java/ch/cyberduck/core/nio/LocalMoveFeature.java b/nio/src/main/java/ch/cyberduck/core/nio/LocalMoveFeature.java index e981191619f..843766ce75d 100644 --- a/nio/src/main/java/ch/cyberduck/core/nio/LocalMoveFeature.java +++ b/nio/src/main/java/ch/cyberduck/core/nio/LocalMoveFeature.java @@ -15,6 +15,7 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.CaseInsensitivePathPredicate; import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.DisabledPasswordCallback; import ch.cyberduck.core.Path; @@ -24,11 +25,15 @@ import ch.cyberduck.core.features.Move; import ch.cyberduck.core.transfer.TransferStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.nio.file.NoSuchFileException; import java.util.Collections; import java.util.EnumSet; public class LocalMoveFeature implements Move { + private static final Logger log = LogManager.getLogger(LocalMoveFeature.class); private final LocalSession session; @@ -42,7 +47,16 @@ public Path move(final Path file, final Path renamed, final TransferStatus statu throw new NotfoundException(file.getAbsolute()); } if(status.isExists()) { - new LocalDeleteFeature(session).delete(Collections.singletonMap(renamed, status), new DisabledPasswordCallback(), callback); + switch(session.getCaseSensitivity()) { + case insensitive: + if(new CaseInsensitivePathPredicate(file).test(renamed)) { + break; + } + // Break through + case sensitive: + log.warn("Delete file {} to be replaced with {}", renamed, file); + new LocalDeleteFeature(session).delete(Collections.singletonMap(renamed, status), new DisabledPasswordCallback(), callback); + } } if(!session.toPath(file).toFile().renameTo(session.toPath(renamed).toFile())) { throw new LocalExceptionMappingService().map("Cannot rename {0}", new NoSuchFileException(file.getName()), file); diff --git a/nio/src/main/java/ch/cyberduck/core/nio/LocalProtocol.java b/nio/src/main/java/ch/cyberduck/core/nio/LocalProtocol.java index 78217ebd3ab..3ff89973adb 100644 --- a/nio/src/main/java/ch/cyberduck/core/nio/LocalProtocol.java +++ b/nio/src/main/java/ch/cyberduck/core/nio/LocalProtocol.java @@ -21,8 +21,6 @@ import ch.cyberduck.core.Protocol; import ch.cyberduck.core.Scheme; -import org.apache.commons.io.IOCase; - import java.net.InetAddress; import java.net.UnknownHostException; @@ -113,7 +111,7 @@ public String disk() { @Override public Case getCaseSensitivity() { - return IOCase.SYSTEM.isCaseSensitive() ? Case.sensitive : Case.insensitive; + return Case.insensitive; } @Override diff --git a/nio/src/test/java/ch/cyberduck/core/nio/LocalMoveFeatureTest.java b/nio/src/test/java/ch/cyberduck/core/nio/LocalMoveFeatureTest.java index 08a528f5a10..7481f1c4080 100644 --- a/nio/src/test/java/ch/cyberduck/core/nio/LocalMoveFeatureTest.java +++ b/nio/src/test/java/ch/cyberduck/core/nio/LocalMoveFeatureTest.java @@ -78,7 +78,7 @@ public void testRenameCaseOnly() throws Exception { final Path test = new Path(workdir, StringUtils.lowerCase(new AsciiRandomStringService().random()), EnumSet.of(Path.Type.file)); new LocalTouchFeature(session).touch(test, new TransferStatus()); final Path target = new Path(workdir, StringUtils.capitalize(test.getName()), EnumSet.of(Path.Type.file)); - new LocalMoveFeature(session).move(test, target, new TransferStatus(), new Delete.DisabledCallback(), new DisabledConnectionCallback()); + new LocalMoveFeature(session).move(test, target, new TransferStatus().exists(true), new Delete.DisabledCallback(), new DisabledConnectionCallback()); assertFalse(new LocalFindFeature(session).find(test)); assertTrue(new LocalFindFeature(session).find(target)); new LocalDeleteFeature(session).delete(Collections.singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback()); diff --git a/onedrive/src/main/java/ch/cyberduck/core/onedrive/SharepointListService.java b/onedrive/src/main/java/ch/cyberduck/core/onedrive/SharepointListService.java index d46bb30e21b..9fb75bbdb32 100644 --- a/onedrive/src/main/java/ch/cyberduck/core/onedrive/SharepointListService.java +++ b/onedrive/src/main/java/ch/cyberduck/core/onedrive/SharepointListService.java @@ -40,10 +40,10 @@ public class SharepointListService extends AbstractSharepointListService { public static final String GROUPS_CONTAINER = "Groups"; public static final String SITES_CONTAINER = "Sites"; - public static final Path DEFAULT_NAME = new Path(Path.DELIMITER + DEFAULT_SITE, EnumSet.of(Path.Type.volume, Path.Type.placeholder, Path.Type.directory, Path.Type.symboliclink)); - public static final Path DRIVES_NAME = new Path(Path.DELIMITER + DRIVES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory)); - public static final Path GROUPS_NAME = new Path(Path.DELIMITER + GROUPS_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory)); - public static final Path SITES_NAME = new Path(Path.DELIMITER + SITES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory)); + public static final Path DEFAULT_NAME = new Path(DEFAULT_SITE, EnumSet.of(Path.Type.volume, Path.Type.placeholder, Path.Type.directory, Path.Type.symboliclink)); + public static final Path DRIVES_NAME = new Path(DRIVES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory)); + public static final Path GROUPS_NAME = new Path(GROUPS_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory)); + public static final Path SITES_NAME = new Path(SITES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory)); private final SharepointSession session; private final GraphFileIdProvider fileid; diff --git a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphCopyFeature.java b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphCopyFeature.java index 11b97aec99f..53f5bfb144e 100644 --- a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphCopyFeature.java +++ b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphCopyFeature.java @@ -42,6 +42,8 @@ import java.util.Collections; import java.util.EnumSet; +import static ch.cyberduck.core.features.Copy.validate; + public class GraphCopyFeature implements Copy { private static final Logger log = LogManager.getLogger(GraphCopyFeature.class); @@ -106,5 +108,6 @@ public void preflight(final Path source, final Path target) throws BackgroundExc if(source.getType().contains(Path.Type.shared)) { throw new UnsupportedException(MessageFormat.format(LocaleFactory.localizedString("Cannot copy {0}", "Error"), source.getName())).withFile(source); } + validate(session.getCaseSensitivity(), source, target); } } diff --git a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphFileIdProvider.java b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphFileIdProvider.java index db8089fe8fb..224bf66a6fe 100644 --- a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphFileIdProvider.java +++ b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphFileIdProvider.java @@ -17,10 +17,10 @@ import ch.cyberduck.core.AttributedList; import ch.cyberduck.core.CachingFileIdProvider; +import ch.cyberduck.core.CaseInsensitivePathPredicate; import ch.cyberduck.core.DisabledListProgressListener; import ch.cyberduck.core.ListService; import ch.cyberduck.core.Path; -import ch.cyberduck.core.SimplePathPredicate; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.FileIdProvider; @@ -59,7 +59,7 @@ public String getFileId(final Path file) throws BackgroundException { return this.cache(file, found.attributes().getFileId()); } - private final static class SymlinkUnawarePathPredicate extends SimplePathPredicate { + private final static class SymlinkUnawarePathPredicate extends CaseInsensitivePathPredicate { public SymlinkUnawarePathPredicate(final Path file) { super(file.isFile() ? Path.Type.file : Path.Type.directory, file.getAbsolute()); } diff --git a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphMoveFeature.java b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphMoveFeature.java index 9d3a507d442..73f062b77b7 100644 --- a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphMoveFeature.java +++ b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphMoveFeature.java @@ -62,8 +62,10 @@ public GraphMoveFeature(final GraphSession session, final GraphFileIdProvider fi @Override public Path move(final Path file, final Path renamed, final TransferStatus status, final Delete.Callback callback, final ConnectionCallback connectionCallback) throws BackgroundException { if(status.isExists()) { - log.warn("Delete file {} to be replaced with {}", renamed, file); - delete.delete(Collections.singletonMap(renamed, status), connectionCallback, callback); + if(!fileid.getFileId(file).equals(fileid.getFileId(renamed))) { + log.warn("Delete file {} to be replaced with {}", renamed, file); + delete.delete(Collections.singletonMap(renamed, status), connectionCallback, callback); + } } final PatchOperation patchOperation = new PatchOperation(); if(!StringUtils.equals(file.getName(), renamed.getName())) { diff --git a/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphFileIdProviderTest.java b/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphFileIdProviderTest.java index 861a281839b..a2d9fece5f4 100644 --- a/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphFileIdProviderTest.java +++ b/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphFileIdProviderTest.java @@ -1,16 +1,21 @@ package ch.cyberduck.core.onedrive; +import ch.cyberduck.core.AlphanumericRandomStringService; +import ch.cyberduck.core.DisabledLoginCallback; import ch.cyberduck.core.DisabledPasswordCallback; import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.Delete; import ch.cyberduck.core.features.Directory; import ch.cyberduck.core.onedrive.features.GraphDeleteFeature; import ch.cyberduck.core.onedrive.features.GraphDirectoryFeature; import ch.cyberduck.core.onedrive.features.GraphFileIdProvider; +import ch.cyberduck.core.onedrive.features.GraphTouchFeature; import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; +import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -55,4 +60,28 @@ public void testFileIdCollision() throws Exception { new GraphDeleteFeature(session, fileid).delete(Arrays.asList(path2RWithId, path33WithId), new DisabledPasswordCallback(), new Delete.DisabledCallback()); } + + @Test + public void getFileIdFile() throws Exception { + final GraphFileIdProvider nodeid = new GraphFileIdProvider(session); + final Path home = new OneDriveHomeFinderService().find(); + final String name = String.format("%s", new AlphanumericRandomStringService().random()); + final Path file = new GraphTouchFeature(session, nodeid).touch(new Path(home, name, EnumSet.of(Path.Type.file)), new TransferStatus()); + nodeid.clear(); + final String nodeId = nodeid.getFileId(new Path(home, name, EnumSet.of(Path.Type.file))); + assertNotNull(nodeId); + assertEquals(nodeId, nodeid.getFileId(new Path(home.withAttributes(PathAttributes.EMPTY), name, EnumSet.of(Path.Type.file)))); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home, StringUtils.upperCase(name), EnumSet.of(Path.Type.file)))); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home, StringUtils.lowerCase(name), EnumSet.of(Path.Type.file)))); + try { + assertNull(nodeid.getFileId(new Path(home, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)))); + fail(); + } + catch(NotfoundException e) { + // Expected + } + new GraphDeleteFeature(session, nodeid).delete(Collections.singletonList(file), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } } diff --git a/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphMoveFeatureTest.java b/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphMoveFeatureTest.java index 5bb570d420d..5e7ca829e4a 100644 --- a/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphMoveFeatureTest.java +++ b/onedrive/src/test/java/ch/cyberduck/core/onedrive/GraphMoveFeatureTest.java @@ -39,6 +39,7 @@ import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; +import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -176,4 +177,14 @@ public void testMoveToExistingFile() throws Exception { assertTrue(find.find(test)); new GraphDeleteFeature(session, fileid).delete(Collections.singletonList(folder), new DisabledLoginCallback(), new Delete.DisabledCallback()); } + + @Test + public void testRenameCaseOnly() throws Exception { + final Path home = new OneDriveHomeFinderService().find(); + final String name = new AlphanumericRandomStringService().random(); + final Path file = new GraphTouchFeature(session, fileid).touch(new Path(home, StringUtils.capitalize(name), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Path rename = new Path(home, StringUtils.lowerCase(name), EnumSet.of(Path.Type.file)); + new GraphMoveFeature(session, fileid).move(file, rename, new TransferStatus().exists(true), new Delete.DisabledCallback(), new DisabledConnectionCallback()); + new GraphDeleteFeature(session, fileid).delete(Collections.singletonList(rename), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } } diff --git a/onedrive/src/test/java/ch/cyberduck/core/onedrive/OfflineGraphCopyMoveFeatureTest.java b/onedrive/src/test/java/ch/cyberduck/core/onedrive/OfflineGraphCopyMoveFeatureTest.java index 9d5f167663a..757b7871b4a 100644 --- a/onedrive/src/test/java/ch/cyberduck/core/onedrive/OfflineGraphCopyMoveFeatureTest.java +++ b/onedrive/src/test/java/ch/cyberduck/core/onedrive/OfflineGraphCopyMoveFeatureTest.java @@ -57,47 +57,44 @@ public void testSharepoint() { final List cases = new ArrayList<>(); // Rename Tests - // Assumption: Cyberduck always uses same source and target path to indicate rename. - cases.add(new TestCase("/Default", directory, false)); - cases.add(new TestCase("/Default/Drives/Drive", directory, false)); - cases.add(new TestCase("/Default/Drives/Drive/Folder/Test", file, true)); - cases.add(new TestCase("/Default/Drives/Drive/Test", file, true)); - cases.add(new TestCase("/Default/Sites", directory, false)); - cases.add(new TestCase("/Default/Sites/Site", directory, false)); - cases.add(new TestCase("/Default/Sites/Site/Drives", directory, false)); - cases.add(new TestCase("/Default/Sites/Site/Sites", directory, false)); - cases.add(new TestCase("/Groups", directory, false)); - cases.add(new TestCase("/Groups/Group", directory, false)); - cases.add(new TestCase("/Groups/Group/Drive", directory, false)); - cases.add(new TestCase("/Groups/Group/Drive/Folder/Test", file, true)); - cases.add(new TestCase("/Groups/Group/Drive/Test", file, true)); - cases.add(new TestCase("/Invalid", directory, false)); - cases.add(new TestCase("/Sites", directory, false)); - cases.add(new TestCase("/Sites/Site", directory, false)); - cases.add(new TestCase("/Sites/Site/Drives", directory, false)); - cases.add(new TestCase("/Sites/Site/Drives/Drive", directory, false)); - cases.add(new TestCase("/Sites/Site/Drives/Drive/Folder/Test", file, true)); - cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, true)); - cases.add(new TestCase("/Sites/Site/Sites", directory, false)); - cases.add(new TestCase("/Sites/Site/Sites/Site", directory, false)); + cases.add(new TestCase("/Default", directory, "/Target", false)); + cases.add(new TestCase("/Default/Drives/Drive", directory, "/Default/Drives/Target", false)); + cases.add(new TestCase("/Default/Drives/Drive/Folder/Test", file, "/Default/Drives/Drive/Folder/Target", true)); + cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Default/Drives/Drive/Target", true)); + cases.add(new TestCase("/Default/Sites", directory, "/Default/Target", false)); + cases.add(new TestCase("/Default/Sites/Site", directory, "/Default/Sites/Target", false)); + cases.add(new TestCase("/Default/Sites/Site/Drives", directory, "/Default/Sites/Site/Target", false)); + cases.add(new TestCase("/Default/Sites/Site/Sites", directory, "/Default/Sites/Site/Target", false)); + cases.add(new TestCase("/Groups", directory, "/Target", false)); + cases.add(new TestCase("/Groups/Group", directory, "/Groups/Target", false)); + cases.add(new TestCase("/Groups/Group/Drive", directory, "/Groups/Group/Target", false)); + cases.add(new TestCase("/Groups/Group/Drive/Folder/Test", file, "/Groups/Group/Drive/Folder/Target", true)); + cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Group/Drive/Target", true)); + cases.add(new TestCase("/Invalid", directory, "/Target", false)); + cases.add(new TestCase("/Sites", directory, "/Target", false)); + cases.add(new TestCase("/Sites/Site", directory, "/Sites/Target", false)); + cases.add(new TestCase("/Sites/Site/Drives", directory, "/Sites/Site/Target", false)); + cases.add(new TestCase("/Sites/Site/Drives/Drive", directory, "/Sites/Site/Drives/Target", false)); + cases.add(new TestCase("/Sites/Site/Drives/Drive/Folder/Test", file, "/Sites/Site/Drives/Drive/Folder/Target", true)); + cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Site/Drives/Drive/Target", true)); + cases.add(new TestCase("/Sites/Site/Sites", directory, "//Sites/Site/Target", false)); + cases.add(new TestCase("/Sites/Site/Sites/Site", directory, "/Sites/Site/Sites/Target", false)); // Move/Copy-Tests - // Assumption: Cyberduck always uses target directory without name to indicate drag. - // Target always is folder. - cases.add(new TestCase("/Default/Drives/Drive/Nested/Test", file, "/Default/Drives/Drive", true)); - cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Default/Drives/Drive/Nested", true)); - cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Default/Drives/Other Drive", false)); - cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Groups/Group/Drive", false)); - cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Sites/Site/Drives/Drive", false)); - cases.add(new TestCase("/Groups/Group/Drive/Nested/Test", file, "/Groups/Group/Drive", true)); - cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Group/Drive/Test/Nested", true)); - cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Group/Other Drive", false)); - cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Other Group/Drive", false)); - cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Sites/Site/Drives/Drive", false)); - cases.add(new TestCase("/Sites/Site/Drives/Drive/Nested/Test", file, "/Sites/Site/Drives/Drive", true)); - cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Other Site/Drives/Drive", false)); - cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Site/Drives/Drive/Nested", true)); - cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Site/Drives/Other Drive", false)); + cases.add(new TestCase("/Default/Drives/Drive/Nested/Test", file, "/Default/Drives/Drive/Target", true)); + cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Default/Drives/Drive/Nested/Target", true)); + cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Default/Drives/Other Drive//Target", false)); + cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Groups/Group/Drive//Target", false)); + cases.add(new TestCase("/Default/Drives/Drive/Test", file, "/Sites/Site/Drives/Drive//Target", false)); + cases.add(new TestCase("/Groups/Group/Drive/Nested/Test", file, "/Groups/Group/Drive//Target", true)); + cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Group/Drive/Test/Nested/Target", true)); + cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Group/Other Drive/Target", false)); + cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Groups/Other Group/Drive/Target", false)); + cases.add(new TestCase("/Groups/Group/Drive/Test", file, "/Sites/Site/Drives/Drive/Target", false)); + cases.add(new TestCase("/Sites/Site/Drives/Drive/Nested/Test", file, "/Sites/Site/Drives/Drive/Target", true)); + cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Other Site/Drives/Drive/Target", false)); + cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Site/Drives/Drive/Nested/Target", true)); + cases.add(new TestCase("/Sites/Site/Drives/Drive/Test", file, "/Sites/Site/Drives/Other Drive/Target", false)); test(feature, cases); } @@ -109,18 +106,15 @@ public void testOneDrive() { final List cases = new ArrayList<>(); // Rename Tests - // Assumption: Cyberduck always uses same source and target path to indicate rename. - cases.add(new TestCase("/Test", directory, false)); - cases.add(new TestCase("/My Files", directory, false)); - cases.add(new TestCase("/Shared", directory, false)); - cases.add(new TestCase("/Shared/Element", file, false)); - cases.add(new TestCase("/My Files/Element", file, true)); - cases.add(new TestCase("/My Files/Folder/Element", file, true)); - cases.add(new TestCase("/Shared/Folder/Element", file, true)); + cases.add(new TestCase("/Test", directory, "/Target", false)); + cases.add(new TestCase("/My Files", directory, "/Target", false)); + cases.add(new TestCase("/Shared", directory, "/Target", false)); + cases.add(new TestCase("/Shared/Element", file, "/Target", false)); + cases.add(new TestCase("/My Files/Element", file, "/My Files/Target", true)); + cases.add(new TestCase("/My Files/Folder/Element", file, "/My Files/Folder/Target", true)); + cases.add(new TestCase("/Shared/Folder/Element", file, "/Shared/Folder/Target", true)); // Move/Copy-Tests - // Assumption: Cyberduck always uses target directory without name to indicate drag. - // Target always is folder. cases.add(new TestCase("/My Files", directory, "/Shared", false)); cases.add(new TestCase("/Shared", directory, "/My Files", false)); cases.add(new TestCase("/Shared/Element", file, "/My Files", false)); @@ -132,8 +126,8 @@ public void testOneDrive() { cases.add(new TestCase("/Shared/Folder/Element", file, "/Shared/Test", true)); cases.add(new TestCase("/Shared/Folder/Element", file, "/My Files", false)); cases.add(new TestCase("/Shared/Folder/Element", file, "/My Files/Element", false)); - cases.add(new TestCase("/My Files/Element", file, "/My Files/Folder", true)); - cases.add(new TestCase("/My Files/Folder/Element", file, "/My Files", true)); + cases.add(new TestCase("/My Files/Element", file, "/My Files/Folder/Target", true)); + cases.add(new TestCase("/My Files/Folder/Element", file, "/My Files/Target", true)); cases.add(new TestCase("/Shared/Folder/Element", file, "/Shared/Folder", true)); cases.add(new TestCase("/Shared/Folder/Element", file, "/Shared/Folder/Folder", true)); @@ -142,8 +136,8 @@ public void testOneDrive() { void test(final Object feature, final List test) { for(TestCase testCase : test) { - final Path from = new Path(testCase.source, EnumSet.of(testCase.sourceType)); - final Path to = new Path(testCase.target, EnumSet.of(testCase.targetType)); + final Path from = new Path(testCase.source, EnumSet.of(testCase.type)); + final Path to = new Path(testCase.target, EnumSet.of(testCase.type)); if(feature instanceof Move) { final Move move = (Move) feature; @@ -162,23 +156,13 @@ else if(feature instanceof Copy) { static class TestCase { final String source; final String target; - final AbstractPath.Type sourceType; - final AbstractPath.Type targetType; + final AbstractPath.Type type; final boolean isValid; - public TestCase(final String rename, final AbstractPath.Type type, final boolean isValid) { - this(rename, type, rename, type, isValid); - } - - public TestCase(final String source, final AbstractPath.Type sourceType, final String target, final boolean isValid) { - this(source, sourceType, target, directory, isValid); - } - - TestCase(final String source, final AbstractPath.Type sourceType, final String target, final AbstractPath.Type targetType, final boolean isValid) { + public TestCase(final String source, final Path.Type type, final String target, final boolean isValid) { this.source = source; this.target = target; - this.sourceType = sourceType; - this.targetType = targetType; + this.type = type; this.isValid = isValid; } } diff --git a/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BrowserTableDataSource.java b/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BrowserTableDataSource.java index e9d516ee913..a8d60c97dfc 100644 --- a/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BrowserTableDataSource.java +++ b/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BrowserTableDataSource.java @@ -505,7 +505,7 @@ public NSUInteger validateDrop(final NSTableView view, final Path destination, f if(info.draggingSourceOperationMask().intValue() == NSDraggingInfo.NSDragOperationCopy.intValue()) { // Explicit copy requested if drag operation is already NSDragOperationCopy. User is pressing the option key. for(Path file : pasteboard) { - if(!controller.getSession().getFeature(Copy.class).isSupported(file, destination)) { + if(!controller.getSession().getFeature(Copy.class).isSupported(file, new Path(destination, file.getName(), file.getType()))) { return NSDraggingInfo.NSDragOperationNone; } } @@ -513,7 +513,7 @@ public NSUInteger validateDrop(final NSTableView view, final Path destination, f } else { for(Path file : pasteboard) { - if(!controller.getSession().getFeature(Move.class).isSupported(file, destination)) { + if(!controller.getSession().getFeature(Move.class).isSupported(file, new Path(destination, file.getName(), file.getType()))) { return NSDraggingInfo.NSDragOperationNone; } } diff --git a/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateCopyFeature.java b/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateCopyFeature.java index e2f21861c5e..84eb1b2f500 100644 --- a/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateCopyFeature.java +++ b/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateCopyFeature.java @@ -28,6 +28,8 @@ import java.util.EnumSet; +import static ch.cyberduck.core.features.Copy.validate; + public class StoregateCopyFeature implements Copy { private final StoregateSession session; @@ -56,6 +58,12 @@ public Path copy(final Path source, final Path target, final TransferStatus stat } } + @Override + public void preflight(final Path source, final Path target) throws BackgroundException { + Copy.super.preflight(source, target); + validate(session.getCaseSensitivity(), source, target); + } + @Override public EnumSet features(final Path source, final Path target) { return EnumSet.of(Flags.recursive); diff --git a/storegate/src/test/java/ch/cyberduck/core/storegate/StoregateIdProviderTest.java b/storegate/src/test/java/ch/cyberduck/core/storegate/StoregateIdProviderTest.java new file mode 100644 index 00000000000..e2089fd0949 --- /dev/null +++ b/storegate/src/test/java/ch/cyberduck/core/storegate/StoregateIdProviderTest.java @@ -0,0 +1,62 @@ +package ch.cyberduck.core.storegate; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.AlphanumericRandomStringService; +import ch.cyberduck.core.DisabledLoginCallback; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; +import ch.cyberduck.core.exception.NotfoundException; +import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.transfer.TransferStatus; +import ch.cyberduck.test.IntegrationTest; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.util.Collections; +import java.util.EnumSet; + +import static org.junit.Assert.*; + +@Category(IntegrationTest.class) +public class StoregateIdProviderTest extends AbstractStoregateTest { + + @Test + public void getFileIdFile() throws Exception { + final StoregateIdProvider nodeid = new StoregateIdProvider(session); + final Path home = new Path("/My files", EnumSet.of(Path.Type.directory, Path.Type.volume)); + final String name = String.format("%s", new AlphanumericRandomStringService().random()); + final Path file = new StoregateTouchFeature(session, nodeid).touch(new Path(home, name, EnumSet.of(Path.Type.file)), new TransferStatus()); + nodeid.clear(); + final String nodeId = nodeid.getFileId(new Path(home, name, EnumSet.of(Path.Type.file))); + assertNotNull(nodeId); + assertEquals(nodeId, nodeid.getFileId(new Path(home.withAttributes(PathAttributes.EMPTY), name, EnumSet.of(Path.Type.file)))); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home, StringUtils.upperCase(name), EnumSet.of(Path.Type.file)))); + nodeid.clear(); + assertEquals(nodeId, nodeid.getFileId(new Path(home, StringUtils.lowerCase(name), EnumSet.of(Path.Type.file)))); + try { + assertNull(nodeid.getFileId(new Path(home, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)))); + fail(); + } + catch(NotfoundException e) { + // Expected + } + new StoregateDeleteFeature(session, nodeid).delete(Collections.singletonList(file), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } +} \ No newline at end of file diff --git a/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs b/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs index 2fac945aafc..ae7733c6748 100644 --- a/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs +++ b/windows/src/main/csharp/ch/cyberduck/ui/controller/BrowserController.cs @@ -1048,7 +1048,7 @@ private void View_BrowserModelCanDrop(ModelDropEventArgs args) Move move = (Move)Session.getFeature(typeof(Move)); foreach (Path sourcePath in args.SourceModels) { - if (!move.isSupported(sourcePath, destination)) + if (!move.isSupported(sourcePath, new Path(destination, sourcePath.getName(), sourcePath.getType()))) { args.Effect = DragDropEffects.None; args.DropTargetLocation = DropTargetLocation.None;