Skip to content

Commit

Permalink
fixes #264
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed May 7, 2016
1 parent 91646dd commit 78645ec
Show file tree
Hide file tree
Showing 14 changed files with 541 additions and 4 deletions.
45 changes: 45 additions & 0 deletions main/filesystem-charsets/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2015 Sebastian Stenzel
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - initial API and implementation
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>filesystem-charsets</artifactId>
<name>Cryptomator filesystem: Filename charset compatibility layer</name>

<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>filesystem-api</artifactId>
</dependency>

<!-- Tests -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons-test</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>filesystem-inmemory</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.filesystem.charsets;

import java.io.UncheckedIOException;
import java.text.Normalizer;
import java.text.Normalizer.Form;

import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.delegating.DelegatingFile;

class NormalizedNameFile extends DelegatingFile<NormalizedNameFolder> {

private final Form displayForm;

public NormalizedNameFile(NormalizedNameFolder parent, File delegate, Form displayForm) {
super(parent, delegate);
this.displayForm = displayForm;
}

@Override
public String name() throws UncheckedIOException {
return Normalizer.normalize(super.name(), displayForm);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.filesystem.charsets;

import java.text.Normalizer.Form;

import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.delegating.DelegatingFileSystem;

public class NormalizedNameFileSystem extends NormalizedNameFolder implements DelegatingFileSystem {

public NormalizedNameFileSystem(Folder delegate, Form displayForm) {
super(null, delegate, displayForm);
}

@Override
public Folder getDelegate() {
return delegate;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.filesystem.charsets;

import java.io.UncheckedIOException;
import java.text.Normalizer;
import java.text.Normalizer.Form;

import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.delegating.DelegatingFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class NormalizedNameFolder extends DelegatingFolder<NormalizedNameFolder, NormalizedNameFile> {

private static final Logger LOG = LoggerFactory.getLogger(NormalizedNameFolder.class);
private final Form displayForm;

public NormalizedNameFolder(NormalizedNameFolder parent, Folder delegate, Form displayForm) {
super(parent, delegate);
this.displayForm = displayForm;
}

@Override
public String name() throws UncheckedIOException {
return Normalizer.normalize(super.name(), displayForm);
}

@Override
public NormalizedNameFile file(String name) throws UncheckedIOException {
String nfcName = Normalizer.normalize(name, Form.NFC);
String nfdName = Normalizer.normalize(name, Form.NFD);
NormalizedNameFile nfcFile = super.file(nfcName);
NormalizedNameFile nfdFile = super.file(nfdName);
if (!nfcName.equals(nfdName) && nfcFile.exists() && nfdFile.exists()) {
LOG.warn("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
} else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) {
LOG.info("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
nfdFile.moveTo(nfcFile);
}
return nfcFile;
}

@Override
protected NormalizedNameFile newFile(File delegate) {
return new NormalizedNameFile(this, delegate, displayForm);
}

@Override
public NormalizedNameFolder folder(String name) throws UncheckedIOException {
String nfcName = Normalizer.normalize(name, Form.NFC);
String nfdName = Normalizer.normalize(name, Form.NFD);
NormalizedNameFolder nfcFolder = super.folder(nfcName);
NormalizedNameFolder nfdFolder = super.folder(nfdName);
if (!nfcName.equals(nfdName) && nfcFolder.exists() && nfdFolder.exists()) {
LOG.warn("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
} else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) {
LOG.info("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
nfdFolder.moveTo(nfcFolder);
}
return nfcFolder;
}

@Override
protected NormalizedNameFolder newFolder(Folder delegate) {
return new NormalizedNameFolder(this, delegate, displayForm);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
/**
* Makes sure, the filesystems wrapped by this filesystem work only on UTF-8 encoded file paths using Normalization Form C.
* Filesystems wrapping this file system, on the other hand, will get filenames reported in a specified Normalization Form.
* It is recommended to use NFD for OS X and NFC for other operating systems.
* When looking for a file or folder with a name given in either form, both possibilities are considered
* and files/folders stored in NFD are automatically migrated to NFC.
*/
package org.cryptomator.filesystem.charsets;
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.filesystem.charsets;

import java.nio.ByteBuffer;
import java.text.Normalizer.Form;

import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.WritableFile;
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
import org.junit.Assert;
import org.junit.Test;

public class NormalizedNameFileSystemTest {

@Test
public void testFileMigration() {
FileSystem inMemoryFs = new InMemoryFileSystem();
try (WritableFile writable = inMemoryFs.file("\u006F\u0302").openWritable()) {
writable.write(ByteBuffer.allocate(0));
}
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
Assert.assertTrue(normalizationFs.file("\u00F4").exists());
Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
}

@Test
public void testNoFileMigration() {
FileSystem inMemoryFs = new InMemoryFileSystem();
try (WritableFile writable = inMemoryFs.file("\u00F4").openWritable()) {
writable.write(ByteBuffer.allocate(0));
}
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
Assert.assertTrue(normalizationFs.file("\u00F4").exists());
Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
}

@Test
public void testFolderMigration() {
FileSystem inMemoryFs = new InMemoryFileSystem();
inMemoryFs.folder("\u006F\u0302").create();
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
}

@Test
public void testNoFolderMigration() {
FileSystem inMemoryFs = new InMemoryFileSystem();
inMemoryFs.folder("\u00F4").create();
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
}

@Test
public void testNfcDisplayNames() {
FileSystem inMemoryFs = new InMemoryFileSystem();
inMemoryFs.folder("a\u00F4").create();
inMemoryFs.folder("b\u006F\u0302").create();
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
Assert.assertEquals("a\u00F4", normalizationFs.folder("a\u00F4").name());
Assert.assertEquals("b\u00F4", normalizationFs.folder("b\u006F\u0302").name());
}

@Test
public void testNfdDisplayNames() {
FileSystem inMemoryFs = new InMemoryFileSystem();
inMemoryFs.folder("a\u00F4").create();
inMemoryFs.folder("b\u006F\u0302").create();
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFD);
Assert.assertEquals("a\u006F\u0302", normalizationFs.folder("a\u00F4").name());
Assert.assertEquals("b\u006F\u0302", normalizationFs.folder("b\u006F\u0302").name());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.filesystem.charsets;

import java.text.Normalizer.Form;

import org.cryptomator.filesystem.File;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

public class NormalizedNameFileTest {

private File delegateNfc;
private File delegateNfd;

@Before
public void setup() {
delegateNfc = Mockito.mock(File.class);
delegateNfd = Mockito.mock(File.class);
Mockito.when(delegateNfc.name()).thenReturn("\u00C5");
Mockito.when(delegateNfd.name()).thenReturn("\u0041\u030A");
}

@Test
public void testDisplayNameInNfc() {
File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFC);
File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFC);
Assert.assertEquals("\u00C5", file1.name());
Assert.assertEquals("\u00C5", file2.name());
}

@Test
public void testDisplayNameInNfd() {
File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFD);
File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFD);
Assert.assertEquals("\u0041\u030A", file1.name());
Assert.assertEquals("\u0041\u030A", file2.name());
}

}
Loading

0 comments on commit 78645ec

Please sign in to comment.