diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..4c233250ae
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,3 @@
+* @ahadas
+mucommander-viewer-binary/ @ahadas @hajdam
+mucommander-viewer-text/ @ahadas @pskowronek
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 0000000000..34612ec40d
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,207 @@
+name: Nightly Build
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+ workflow_dispatch:
+
+jobs:
+ create-release:
+
+ runs-on: ubuntu-latest
+ outputs:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ release_id: ${{ steps.create_release.outputs.id }}
+ version: ${{ steps.mucommander_version.outputs.VERSION }}
+ full_version: ${{ steps.mucommander_version.outputs.FULL_VERSION }}
+
+ steps:
+ - name: Remove previous tag and release
+ uses: dev-drprasad/delete-tag-and-release@v0.2.0
+ with:
+ delete_release: true
+ tag_name: nightly
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: actions/checkout@v3
+
+ - name : Get version
+ id: mucommander_version
+ run: |
+ echo "FULL_VERSION=$(${{github.workspace}}/gradlew -q printFullVersionName)" >> $GITHUB_OUTPUT
+ echo "VERSION=$(${{github.workspace}}/gradlew -q printVersionName)" >> $GITHUB_OUTPUT
+
+ - name: Create a new release
+ uses: softprops/action-gh-release@v1
+ id: create_release
+ with:
+ name: Nightly
+ tag_name: nightly
+ prerelease: true
+ draft: true
+ body: "Snapshot of v${{ steps.mucommander_version.outputs.VERSION }}"
+
+ upload-macos-artifacts:
+
+ runs-on: macos-latest
+ needs: create-release
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Checkout 'release'
+ uses: actions/checkout@v3
+ with:
+ repository: mucommander/release
+ path: release
+ token: ${{ secrets.RELEASE_REPO_TOKEN }}
+
+ - name: Apply 'release' patches
+ run: |
+ git config --global user.name gh-action
+ git config --global user.email gh-action
+ git am release/0001-set-credentials-to-Google-Drive.patch
+ git am release/0002-set-credentials-to-Dropbox.patch
+
+ - uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+
+ - name: Build dmg
+ uses: gradle/gradle-build-action@v2
+ with:
+ arguments: dmg
+
+ - name: Upload dmg
+ uses: shogo82148/actions-upload-release-asset@v1
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: "./build/distributions/mucommander-${{ needs.create-release.outputs.full_version }}.dmg"
+ asset_name: mucommander-snapshot.dmg
+ asset_content_type: application/octet-stream
+
+ upload-linux-artifacts:
+
+ runs-on: ubuntu-latest
+ needs: create-release
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+
+ - name: Checkout 'release'
+ uses: actions/checkout@v3
+ with:
+ repository: mucommander/release
+ path: release
+ token: ${{ secrets.RELEASE_REPO_TOKEN }}
+
+ - name: Apply 'release' patches
+ run: |
+ git config --global user.name gh-action
+ git config --global user.email gh-action
+ git am release/0001-set-credentials-to-Google-Drive.patch
+ git am release/0002-set-credentials-to-Dropbox.patch
+
+ - name: Build portable, tgz, deb, and rpm
+ uses: gradle/gradle-build-action@v2
+ with:
+ arguments: tgz portable deb rpm
+
+ - name: Upload portable
+ uses: shogo82148/actions-upload-release-asset@v1
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: "./build/distributions/mucommander-${{ needs.create-release.outputs.full_version }}-portable.zip"
+ asset_name: mucommander-snapshot-portable.zip
+ asset_content_type: application/zip
+
+ - name: Upload tgz
+ uses: shogo82148/actions-upload-release-asset@v1
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: "./build/distributions/mucommander-${{ needs.create-release.outputs.full_version }}.tgz"
+ asset_name: mucommander-snapshot.tgz
+ asset_content_type: application/gzip
+
+ - name: Upload deb
+ uses: shogo82148/actions-upload-release-asset@v1
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: "./build/distributions/mucommander_${{ needs.create-release.outputs.version }}-1_amd64.deb"
+ asset_name: mucommander-snapshot_amd64.deb
+ asset_content_type: application/octet-stream
+
+ - name: Upload rpm
+ uses: shogo82148/actions-upload-release-asset@v1
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: "./build/distributions/mucommander-${{ needs.create-release.outputs.version }}-1.x86_64.rpm"
+ asset_name: mucommander-snapshot.x86_64.rpm
+ asset_content_type: application/octet-stream
+
+
+ upload-windows-artifacts:
+
+ runs-on: windows-latest
+ needs: create-release
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+
+ - name: Checkout 'release'
+ uses: actions/checkout@v3
+ with:
+ repository: mucommander/release
+ path: release
+ token: ${{ secrets.RELEASE_REPO_TOKEN }}
+
+ - name: Apply 'release' patches
+ run: |
+ git config --global user.name gh-action
+ git config --global user.email gh-action
+ git am release/0001-set-credentials-to-Google-Drive.patch
+ git am release/0002-set-credentials-to-Dropbox.patch
+
+ - name: Build msi
+ uses: gradle/gradle-build-action@v2
+ with:
+ arguments: msi
+
+ - name: Upload msi
+ uses: shogo82148/actions-upload-release-asset@v1
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: "./build/distributions/mucommander-${{ needs.create-release.outputs.version }}.msi"
+ asset_name: mucommander-snapshot.msi
+ asset_content_type: application/octet-stream
+
+ publish-release:
+
+ runs-on: ubuntu-latest
+ needs: [ create-release, upload-linux-artifacts, upload-macos-artifacts, upload-windows-artifacts ]
+
+ steps:
+ - name: Publish the new release
+ uses: eregon/publish-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ release_id: ${{ needs.create-release.outputs.release_id }}
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
new file mode 100644
index 0000000000..c9cb0f0978
--- /dev/null
+++ b/.github/workflows/tests.yaml
@@ -0,0 +1,19 @@
+name: Tests
+
+on: [pull_request, workflow_dispatch]
+
+jobs:
+ unit-tests:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'adopt'
+ - uses: gradle/wrapper-validation-action@v1
+ - uses: gradle/gradle-build-action@v2
+ with:
+ arguments: test
diff --git a/LICENSE b/LICENSE
index f3ac4097a9..1ee30bea2a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -19,9 +19,11 @@ Additionally, muCommander uses the following third party works:
- the J7Zip library under the terms of the GNU Lesser General Public License
- the jCIFS library under the terms of the GNU Lesser General Public License
- the JetS3t library under the terms of the Apache License
+- the JediTerm library under the terms of dual GNU Lesser General Public License and Apache License
- the JmDNS library under the terms of the GNU Lesser General Public License
- the JNA library under the terms of the GNU Lesser General Public License
- the JUnRar library under the terms of the GNU Lesser General Public License
+- the RSyntaxTextArea library under the terms of BSD 3-Clause "New" or "Revised" License
- the Yanfs library under the terms of the Berkeley Software Distribution License
- the JCommander library under the terms of the Apache License
- the ICEpdf library under the terms of the Apache License
diff --git a/README.md b/README.md
index 49a576e1cf..5367c4e036 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# muCommander
-[](https://github.com/mucommander/mucommander/releases/tag/nightly)
+[](https://github.com/mucommander/mucommander/releases/tag/nightly)
[](http://www.gnu.org/copyleft/gpl.html)
[](https://travis-ci.org/mucommander/mucommander)
[](https://scan.coverity.com/projects/3642)
diff --git a/apache-bzip2/build.gradle b/apache-bzip2/build.gradle
index ed02d76674..b6697b70b9 100644
--- a/apache-bzip2/build.gradle
+++ b/apache-bzip2/build.gradle
@@ -14,6 +14,6 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/build.gradle b/build.gradle
index 6ef16bcf69..ea1b925df2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,15 +3,15 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:6.1.0'
+ classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0'
}
}
plugins {
id 'com.athaydes.osgi-run' version '1.6.0'
id 'java'
- id 'org.ajoberstar.grgit' version '3.1.1'
- id 'edu.sc.seis.launch4j' version '2.5.3'
+ id 'org.ajoberstar.grgit' version '5.0.0'
+ id 'edu.sc.seis.launch4j' version '2.5.4'
}
allprojects {
@@ -20,13 +20,21 @@ allprojects {
}
group = 'org.mucommander'
- version = '1.1.0'
- ext.release = 'snapshot'
+ version = '1.2.0'
+ ext.release = '1'
configurations {
compileOnly.extendsFrom comprise
}
}
+task printFullVersionName {
+ doLast { println project.version + '-' + project.ext.release }
+}
+
+task printVersionName {
+ doLast { println project.version }
+}
+
repositories.mavenCentral()
subprojects {
@@ -37,7 +45,7 @@ subprojects {
compileTestJava.options.encoding = 'UTF-8'
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.36'
- implementation 'org.osgi:osgi.core:7.0.0'
+ implementation 'org.osgi:osgi.core:8.0.0'
}
}
@@ -46,9 +54,9 @@ compileTestJava.options.encoding = 'UTF-8'
compileJava.options.compilerArgs += ['--release', '11']
dependencies {
- comprise 'org.apache.felix:org.apache.felix.main:7.0.3'
- comprise 'com.beust:jcommander:1.78'
- comprise 'com.formdev:flatlaf:2.2'
+ comprise 'org.apache.felix:org.apache.felix.main:7.0.5'
+ comprise 'com.beust:jcommander:1.82'
+ comprise 'com.formdev:flatlaf:2.6'
comprise 'org.violetlib:vaqua:10'
osgiRuntime project('mucommander-core')
@@ -173,7 +181,7 @@ jar {
"Implementation-Vendor": "Arik Hadas",
"Implementation-Version": revision.substring(0, 7),
"Build-Date": new Date().format('yyyyMMdd'),
- "Build-URL": "https://www.mucommander.com/version/nightly.xml")
+ "Build-URL": "https://www.mucommander.com/version/version.xml")
}
}
diff --git a/jetbrains-jediterm/build.gradle b/jetbrains-jediterm/build.gradle
index 0103be3d8c..607b46596e 100644
--- a/jetbrains-jediterm/build.gradle
+++ b/jetbrains-jediterm/build.gradle
@@ -54,7 +54,7 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml",
+ 'Build-Url': "https://www.mucommander.com/version/version.xml",
'Import-Package': '!javax.annotation.*,!com.google.appengine*,!com.google.apphosting.*,!kotlin.internal.*,!com.ibm.uvm.tools,!kotlin.reflect.jvm.internal,*',
'Export-Package':
'com.jediterm.terminal,' +
diff --git a/mucommander-archiver/build.gradle b/mucommander-archiver/build.gradle
index 0f9280e577..3350725485 100644
--- a/mucommander-archiver/build.gradle
+++ b/mucommander-archiver/build.gradle
@@ -24,6 +24,6 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-command/build.gradle b/mucommander-command/build.gradle
index afb93cdbe4..54989fa47c 100644
--- a/mucommander-command/build.gradle
+++ b/mucommander-command/build.gradle
@@ -33,6 +33,6 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-commons-collections/build.gradle b/mucommander-commons-collections/build.gradle
index cd3d2599d1..40aa08ac24 100644
--- a/mucommander-commons-collections/build.gradle
+++ b/mucommander-commons-collections/build.gradle
@@ -17,5 +17,5 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-commons-conf/build.gradle b/mucommander-commons-conf/build.gradle
index ae27b9e446..fb0d8c11b2 100644
--- a/mucommander-commons-conf/build.gradle
+++ b/mucommander-commons-conf/build.gradle
@@ -17,5 +17,5 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-commons-conf/src/main/java/com/mucommander/commons/conf/Configuration.java b/mucommander-commons-conf/src/main/java/com/mucommander/commons/conf/Configuration.java
index 901913a006..1e40094667 100644
--- a/mucommander-commons-conf/src/main/java/com/mucommander/commons/conf/Configuration.java
+++ b/mucommander-commons-conf/src/main/java/com/mucommander/commons/conf/Configuration.java
@@ -216,7 +216,7 @@ public void setReaderFactory(ConfigurationReaderFactory f) {
*/
public ConfigurationReaderFactory getReaderFactory() {
synchronized(readerLock) {
- if(readerFactory == null)
+ if (readerFactory == null)
return XmlConfigurationReader.FACTORY;
return readerFactory;
}
@@ -250,7 +250,7 @@ public void setWriterFactory(ConfigurationWriterFactory f) {
*/
public ConfigurationWriterFactory getWriterFactory() {
synchronized(writerLock) {
- if(writerFactory == null)
+ if (writerFactory == null)
return XmlConfigurationWriter.FACTORY;
return writerFactory;
}
@@ -336,19 +336,17 @@ public void read(Reader in) throws ConfigurationException, IOException {
* @see #read(Reader,ConfigurationReader)
*/
public void read(ConfigurationReader reader) throws IOException, ConfigurationException {
- Reader in; // Input stream on the configuration source.
- ConfigurationSource source; // Configuration source.
-
- in = null;
+ Reader in = null; // Input stream on the configuration source.
+ ConfigurationSource source = getSource();
// Makes sure the configuration source has been properly set.
- if((source = getSource()) == null)
+ if (source == null)
throw new SourceConfigurationException("Configuration source hasn't been set.");
// Reads the configuration data.
try {read(in = source.getReader(), reader);}
finally {
- if(in != null) {
+ if (in != null) {
try {in.close();}
catch(Exception e) {}
}
@@ -417,19 +415,17 @@ public void write(Writer out) throws ConfigurationException {
* @see #write()
*/
public void write() throws IOException, ConfigurationException {
- Writer out; // Where to write the configuration data.
- ConfigurationSource source; // Configuration source.
-
- out = null;
+ Writer out = null; // Where to write the configuration data.
+ ConfigurationSource source = getSource();
// Makes sure the source has been set.
- if((source = getSource()) == null)
+ if (source == null)
throw new SourceConfigurationException("No configuration source has been set");
// Writes the configuration data.
try {write(out = source.getWriter());}
finally {
- if(out != null) {
+ if (out != null) {
try {out.close();}
catch(Exception e) {
// Ignores errors here, nothing we can do about them.
@@ -460,23 +456,21 @@ public void write(ConfigurationBuilder builder) throws ConfigurationException {
* @throws ConfigurationException if any error occurs.
*/
private synchronized void build(ConfigurationBuilder builder, ConfigurationSection root) throws ConfigurationException {
- Iteratornull
if it wasn't set.
*/
public synchronized String removeVariable(String name) {
- BufferedConfigurationExplorer explorer; // Used to navigate to the variable's parent section.
- String buffer; // Buffer for the variable's name trimmed of section information.
+ BufferedConfigurationExplorer explorer = new BufferedConfigurationExplorer(root);
+ String buffer = moveToParent(explorer , name, false);
// If the variable's 'path' doesn't exist, return null.
- if((buffer = moveToParent(explorer = new BufferedConfigurationExplorer(root), name, false)) == null)
+ if (buffer == null)
return null;
// If the variable was actually set, triggers an event.
- if((buffer = explorer.getSection().removeVariable(buffer)) != null) {
+ if ((buffer = explorer.getSection().removeVariable(buffer)) != null) {
prune(explorer);
triggerEvent(new ConfigurationEvent(this, name, null));
}
@@ -929,16 +919,17 @@ public void clear() {
* @see #getVariable(String)
*/
public synchronized String getVariable(String name, String defaultValue) {
- ConfigurationExplorer explorer; // Used to navigate to the variable's parent section.
- String value; // Buffer for the variable's value.
- String buffer; // Buffer for the variable's name trimmed of section information.
+ // Used to navigate to the variable's parent section.
+ ConfigurationExplorer explorer = new ConfigurationExplorer(root);
// Navigates to the parent section. We do not have to check for null values here,
// as the section will be created if it doesn't exist.
- buffer = moveToParent(explorer = new ConfigurationExplorer(root), name, true);
+ String buffer = moveToParent(explorer, name, true);
+ // Buffer for the variable's value.
+ String value = explorer.getSection().getVariable(buffer);
// If the variable isn't set, set it to defaultValue and triggers an event.
- if((value = explorer.getSection().getVariable(buffer)) == null) {
+ if (value == null) {
explorer.getSection().setVariable(buffer, defaultValue);
triggerEvent(new ConfigurationEvent(this, name, defaultValue));
return defaultValue;
diff --git a/mucommander-commons-file/build.gradle b/mucommander-commons-file/build.gradle
index 63310cdbd1..7a6a360e0e 100644
--- a/mucommander-commons-file/build.gradle
+++ b/mucommander-commons-file/build.gradle
@@ -56,6 +56,6 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-commons-file/src/main/java/com/mucommander/commons/file/FileFactory.java b/mucommander-commons-file/src/main/java/com/mucommander/commons/file/FileFactory.java
index deb187a694..75a097a54e 100644
--- a/mucommander-commons-file/src/main/java/com/mucommander/commons/file/FileFactory.java
+++ b/mucommander-commons-file/src/main/java/com/mucommander/commons/file/FileFactory.java
@@ -489,8 +489,9 @@ public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Authent
currentFile = filePool.get(clonedURL);
if (currentFile==null) {
currentFile = wrapArchive(createRawFile(clonedURL, authenticator, instantiationParams));
- // Add the intermediate file instance to the cache
- filePool.put(clonedURL, currentFile);
+ // Add the intermediate file instance to the cache if it exists
+ if (currentFile.exists())
+ filePool.put(clonedURL, currentFile);
}
lastFileResolved = true;
diff --git a/mucommander-commons-file/src/main/java/com/mucommander/commons/file/archive/AbstractArchiveFile.java b/mucommander-commons-file/src/main/java/com/mucommander/commons/file/archive/AbstractArchiveFile.java
index 69653f92ea..c9575d7bc0 100644
--- a/mucommander-commons-file/src/main/java/com/mucommander/commons/file/archive/AbstractArchiveFile.java
+++ b/mucommander-commons-file/src/main/java/com/mucommander/commons/file/archive/AbstractArchiveFile.java
@@ -93,6 +93,9 @@ public abstract class AbstractArchiveFile extends ProxyFile {
* need to be reloaded */
protected long entryTreeDate;
+ /** The password to use for a password-protected archive */
+ protected String password;
+
/** Caches {@link AbstractArchiveEntryFile} instances so that there is only one AbstractArchiveEntryFile
* corresponding to the same entry at any given time, to avoid attribute inconsistencies. The key is the
* corresponding ArchiveEntry. */
@@ -121,7 +124,7 @@ protected void createEntriesTree() throws IOException, UnsupportedFileOperationE
archiveEntryFiles = new WeakHashMapVector
.
+ * Resolves the root folders returned by {@link FileSystem#getRootDirectories()} and adds them to the given Vector
.
*
* @param v
* the Vector
to add root folders to
@@ -336,7 +333,7 @@ private static void addJavaIoFileRoots(VectorOutputStream
to write the encoded data to
- * @param bom the byte-order mark to write at the beginning of the stream.
* @param encoding character encoding to use for encoding characters.
+ * @param bom the byte-order mark to write at the beginning of the stream.
* @throws UnsupportedEncodingException if the specified encoding is not a character encoding supported by the Java runtime.
*/
protected BOMWriter(OutputStream out, String encoding, BOM bom) throws UnsupportedEncodingException {
diff --git a/mucommander-commons-runtime/build.gradle b/mucommander-commons-runtime/build.gradle
index f90bf0b037..a79e1a7832 100644
--- a/mucommander-commons-runtime/build.gradle
+++ b/mucommander-commons-runtime/build.gradle
@@ -17,6 +17,6 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-commons-util/build.gradle b/mucommander-commons-util/build.gradle
index 862e24acb2..c34b7f303e 100644
--- a/mucommander-commons-util/build.gradle
+++ b/mucommander-commons-util/build.gradle
@@ -19,5 +19,5 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml")
+ 'Build-Url': "https://www.mucommander.com/version/version.xml")
}
diff --git a/mucommander-commons-util/src/main/java/com/mucommander/commons/util/ui/spinner/IntEditor.java b/mucommander-commons-util/src/main/java/com/mucommander/commons/util/ui/spinner/IntEditor.java
index 3c643ca60c..db3a1548f3 100644
--- a/mucommander-commons-util/src/main/java/com/mucommander/commons/util/ui/spinner/IntEditor.java
+++ b/mucommander-commons-util/src/main/java/com/mucommander/commons/util/ui/spinner/IntEditor.java
@@ -27,6 +27,7 @@
import javax.swing.JSpinner.NumberEditor;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
+import javax.swing.SwingConstants;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
@@ -41,10 +42,14 @@ public IntEditor(JSpinner spinner, String decimalFormatPattern) {
}
public IntEditor(JSpinner spinner, String decimalFormatPattern, String defaultStr) {
- this(spinner, new DecimalFormat(decimalFormatPattern), defaultStr);
+ this(spinner, new DecimalFormat(decimalFormatPattern), defaultStr, SwingConstants.LEADING);
}
- private IntEditor(JSpinner spinner, DecimalFormat format, String defaultStr) {
+ public IntEditor(JSpinner spinner, String decimalFormatPattern, String defaultStr, int alignment) {
+ this(spinner, new DecimalFormat(decimalFormatPattern), defaultStr, alignment);
+ }
+
+ private IntEditor(JSpinner spinner, DecimalFormat format, String defaultStr, int alignment) {
super(spinner);
if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
throw new IllegalArgumentException(
@@ -77,7 +82,7 @@ public String valueToString(Object value) throws ParseException {
ftf.setEditable(true);
ftf.setFormatterFactory(factory);
- ftf.setHorizontalAlignment(JTextField.LEADING);
+ ftf.setHorizontalAlignment(alignment);
/* TBD - initializing the column width of the text field
* is imprecise and doing it here is tricky because
diff --git a/mucommander-core/build.gradle b/mucommander-core/build.gradle
index 3e30d12e11..b0126491ae 100644
--- a/mucommander-core/build.gradle
+++ b/mucommander-core/build.gradle
@@ -21,10 +21,12 @@ dependencies {
compileOnly project(':mucommander-protocol-api')
compileOnly project(':mucommander-os-api')
compileOnly project(':mucommander-viewer-api')
- api project(':jetbrains-jediterm')
compileOnly 'com.formdev:flatlaf:2.2'
compileOnly 'org.violetlib:vaqua:10'
+ compileOnly 'org.jetbrains.jediterm:jediterm-pty:2.69'
+ compileOnly 'org.jetbrains.pty4j:pty4j:0.12.3'
+ compileOnly 'org.jetbrains:annotations:23.0.0'
implementation 'ch.qos.logback:logback-core:1.2.3'
implementation 'ch.qos.logback:logback-classic:1.2.3'
@@ -59,7 +61,7 @@ jar {
'Implementation-Vendor': "Arik Hadas",
'Implementation-Version': revision.substring(0, 7),
'Build-Date': new Date().format('yyyyMMdd'),
- 'Build-Url': "https://www.mucommander.com/version/nightly.xml",
+ 'Build-Url': "https://www.mucommander.com/version/version.xml",
'Import-Package': 'org.violetlib.aqua;resolution:=dynamic,com.formdev.flatlaf;resolution:=dynamic,com.apple.*;resolution:=dynamic,sun.security.action;resolution:=dynamic,*',
'Export-Package':
'com.mucommander.core.desktop,' +
diff --git a/mucommander-core/src/main/java/com/mucommander/Activator.java b/mucommander-core/src/main/java/com/mucommander/Activator.java
index 0d78dfa7cd..130d91e2ba 100644
--- a/mucommander-core/src/main/java/com/mucommander/Activator.java
+++ b/mucommander-core/src/main/java/com/mucommander/Activator.java
@@ -20,6 +20,8 @@
import java.util.Collections;
import java.util.List;
+import com.mucommander.ui.viewer.EditorSnapshot;
+import com.mucommander.ui.viewer.ViewerSnapshot;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
@@ -39,6 +41,8 @@
import com.mucommander.osgi.FileEditorServiceTracker;
import com.mucommander.osgi.FileViewerServiceTracker;
import com.mucommander.osgi.OperatingSystemServiceTracker;
+import com.mucommander.search.SearchSnapshot;
+import com.mucommander.snapshot.MuSnapshot;
import com.mucommander.text.TranslationTracker;
import com.mucommander.ui.action.ActionManager;
import com.mucommander.ui.dialog.about.AboutDialog;
@@ -80,6 +84,9 @@ public void start(BundleContext context) throws Exception {
LOGGER.debug("starting");
this.context = context;
portable = "portable".equals(context.getProperty("app_mode"));
+ MuSnapshot.registerHandler(new SearchSnapshot());
+ MuSnapshot.registerHandler(new ViewerSnapshot());
+ MuSnapshot.registerHandler(new EditorSnapshot());
// Register the application-specific 'bookmark' protocol.
FileProtocolService bookmarksService = createBookmarkProtocolService();
bookmarksRegistration = context.registerService(FileProtocolService.class, bookmarksService, null);
diff --git a/mucommander-core/src/main/java/com/mucommander/job/impl/MoveJob.java b/mucommander-core/src/main/java/com/mucommander/job/impl/MoveJob.java
index beec17f1bb..4a29d1323a 100644
--- a/mucommander-core/src/main/java/com/mucommander/job/impl/MoveJob.java
+++ b/mucommander-core/src/main/java/com/mucommander/job/impl/MoveJob.java
@@ -142,12 +142,11 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
// - if the 'rename' operation is not supported
// Note: we want to avoid calling AbstractFile#renameTo when we know it will fail, as it performs some costly
// I/O bound checks and ends up throwing an exception which also comes at a cost.
- if(!append && file.getURL().schemeEquals(destFile.getURL()) && file.isFileOperationSupported(FileOperation.RENAME)) {
+ if (!append && file.getURL().schemeEquals(destFile.getURL()) && file.isFileOperationSupported(FileOperation.RENAME)) {
try {
file.renameTo(destFile);
return true;
- }
- catch(IOException e) {
+ } catch(IOException e) {
// Fail silently: renameTo might fail under normal conditions, for instance for local files which are
// not located on the same volume.
LOGGER.debug("Failed to rename "+file+" into "+destFile+" (not necessarily an error)", e);
@@ -156,14 +155,13 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
// Rename couldn't be used or didn't succeed: move the file manually
// Move the directory and all its children recursively, by copying files to the destination and then deleting them.
- if(file.isDirectory()) {
+ if (file.isDirectory()) {
// create the destination folder if it doesn't exist
- if(!(destFile.exists() && destFile.isDirectory())) {
+ if (!(destFile.exists() && destFile.isDirectory())) {
do { // Loop for retry
try {
destFile.mkdir();
- }
- catch(IOException e) {
+ } catch(IOException e) {
DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_create_folder", destFile.getAbsolutePath()));
// Retry loops
if(ret==FileJobAction.RETRY)
@@ -174,39 +172,39 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
break;
} while(true);
}
-
+
+ // save the source folder's date before its children are removed
+ long originalDate = file.getDate();
+
// move each file in this folder recursively
do { // Loop for retry
try {
- AbstractFile[] subFiles = file.ls();
boolean isFolderEmpty = true;
- for (AbstractFile subFile : subFiles) {
+ for (AbstractFile child : file.ls()) {
// Return now if the job was interrupted, so that we do not attempt to delete this folder
if (getState() == FileJobState.INTERRUPTED)
return false;
// Notify job that we're starting to process this file (needed for recursive calls to processFile)
- nextFile(subFile);
- if (!processFile(subFile, destFile))
+ nextFile(child);
+ if (!processFile(child, destFile))
isFolderEmpty = false;
}
// Only when finished with folder, set destination folder's date to match the original folder one
- if(destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {
+ if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {
try {
- destFile.changeDate(file.getDate());
- }
- catch (IOException e) {
+ destFile.changeDate(originalDate);
+ } catch (IOException e) {
LOGGER.debug("failed to change the date of "+destFile, e);
// Fail silently
}
}
// If one file failed to be moved, return false (failure) since this folder could not be moved totally
- if(!isFolderEmpty)
+ if (!isFolderEmpty)
return false;
- }
- catch(IOException e) {
+ } catch(IOException e) {
// file.ls() failed
DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_read_folder", file.getName()));
// Retry loops
@@ -227,8 +225,7 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
try {
file.delete();
return true;
- }
- catch(IOException e) {
+ } catch(IOException e) {
DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_delete_folder", file.getAbsolutePath()));
// Retry loops
if(ret==FileJobAction.RETRY)
@@ -243,15 +240,14 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
// if renameTo() was not supported or failed, or if it wasn't possible because of 'append',
// try the hard way by copying the file first, and then deleting the source file.
- if(tryCopyFile(file, destFile, append, errorDialogTitle) && getState() != FileJobState.INTERRUPTED) {
+ if (tryCopyFile(file, destFile, append, errorDialogTitle) && getState() != FileJobState.INTERRUPTED) {
// Delete the source file
do { // Loop for retry
try {
file.delete();
// All OK
return true;
- }
- catch(IOException e) {
+ } catch(IOException e) {
LOGGER.debug("IOException caught", e);
DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_delete_file", file.getAbsolutePath()));
diff --git a/mucommander-core/src/main/java/com/mucommander/job/impl/PropertiesJob.java b/mucommander-core/src/main/java/com/mucommander/job/impl/PropertiesJob.java
index 3795c0ca93..99a9929dae 100644
--- a/mucommander-core/src/main/java/com/mucommander/job/impl/PropertiesJob.java
+++ b/mucommander-core/src/main/java/com/mucommander/job/impl/PropertiesJob.java
@@ -20,6 +20,9 @@
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.util.FileSet;
+import com.mucommander.conf.MuConfigurations;
+import com.mucommander.conf.MuPreference;
+import com.mucommander.conf.MuPreferences;
import com.mucommander.job.FileJobState;
import com.mucommander.ui.main.MainFrame;
@@ -85,6 +88,9 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
if (getState() == FileJobState.INTERRUPTED)
return false;
+ if (file.isHidden() && !MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_HIDDEN_FILES, MuPreferences.DEFAULT_SHOW_HIDDEN_FILES))
+ return true;
+
// If file is a directory, increase folder counter and recurse
if (file.isDirectory() && !file.isSymlink()) {
nbFolders++;
diff --git a/mucommander-core/src/main/java/com/mucommander/job/impl/SearchJob.java b/mucommander-core/src/main/java/com/mucommander/job/impl/SearchJob.java
index b71fb6c3ff..3233914a32 100644
--- a/mucommander-core/src/main/java/com/mucommander/job/impl/SearchJob.java
+++ b/mucommander-core/src/main/java/com/mucommander/job/impl/SearchJob.java
@@ -64,7 +64,7 @@ public SearchJob(MainFrame mainFrame, FileSet files) {
}
public void setDepth(int depth) {
- this.depth = depth;
+ this.depth = depth == 0 ? Integer.MAX_VALUE : depth;
}
public void setThreads(int threads) {
diff --git a/mucommander-core/src/main/java/com/mucommander/job/impl/UnpackJob.java b/mucommander-core/src/main/java/com/mucommander/job/impl/UnpackJob.java
index 4557a4e753..80cfbae64e 100644
--- a/mucommander-core/src/main/java/com/mucommander/job/impl/UnpackJob.java
+++ b/mucommander-core/src/main/java/com/mucommander/job/impl/UnpackJob.java
@@ -31,6 +31,7 @@
import com.mucommander.text.Translator;
import com.mucommander.ui.action.ActionManager;
import com.mucommander.ui.dialog.DialogAction;
+import com.mucommander.ui.dialog.file.ArchivePasswordDialog;
import com.mucommander.ui.dialog.file.FileCollisionDialog;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.MainFrame;
@@ -187,136 +188,155 @@ protected boolean processFile(AbstractFile file, Object recurseParams) {
String destSeparator = destFolder.getSeparator();
- // Unpack the archive, copying entries one by one, in the iterator's order
- try (ArchiveEntryIterator iterator = archiveFile.getEntryIterator()) {
- ArchiveEntry entry;
- while ((entry = iterator.nextEntry()) != null && getState() != FileJobState.INTERRUPTED) {
- String entryPath = entry.getPath();
-
- boolean processEntry = false;
- if (selectedEntries == null) { // Entries are processed
- processEntry = true;
- } else { // We need to determine if the entry should be processed or not
- // Process this entry if the selectedEntries set contains this entry, or a parent of this entry
- int nbSelectedEntries = selectedEntries.size();
- for (int i = 0; i < nbSelectedEntries; i++) {
- ArchiveEntry selectedEntry = selectedEntries.get(i);
- // Note: paths of directory entries must end with '/', so this compares whether
- // selectedEntry is a parent of the current entry.
- if (selectedEntry.isDirectory()) {
- if (entryPath.startsWith(selectedEntry.getPath())) {
+ do {
+ // Unpack the archive, copying entries one by one, in the iterator's order
+ try (ArchiveEntryIterator iterator = archiveFile.getEntryIterator()) {
+ ArchiveEntry entry;
+ while ((entry = iterator.nextEntry()) != null && getState() != FileJobState.INTERRUPTED) {
+ String entryPath = entry.getPath();
+
+ boolean processEntry = false;
+ if (selectedEntries == null) { // Entries are processed
+ processEntry = true;
+ } else { // We need to determine if the entry should be processed or not
+ // Process this entry if the selectedEntries set contains this entry, or a parent of this entry
+ int nbSelectedEntries = selectedEntries.size();
+ for (int i = 0; i < nbSelectedEntries; i++) {
+ ArchiveEntry selectedEntry = selectedEntries.get(i);
+ // Note: paths of directory entries must end with '/', so this compares whether
+ // selectedEntry is a parent of the current entry.
+ if (selectedEntry.isDirectory()) {
+ if (entryPath.startsWith(selectedEntry.getPath())) {
+ processEntry = true;
+ break;
+ // Note: we can't remove selectedEntryPath from the set, we still need it
+ }
+ } else if (entryPath.equals(selectedEntry.getPath())) {
+ // If the (regular file) entry is in the set, remove it as we no longer need it (will speed up
+ // subsequent searches)
processEntry = true;
+ selectedEntries.remove(i);
break;
- // Note: we can't remove selectedEntryPath from the set, we still need it
}
- } else if (entryPath.equals(selectedEntry.getPath())) {
- // If the (regular file) entry is in the set, remove it as we no longer need it (will speed up
- // subsequent searches)
- processEntry = true;
- selectedEntries.remove(i);
- break;
}
}
- }
- if (!processEntry)
- continue;
+ if (!processEntry)
+ continue;
- // Resolve the entry file
- AbstractFile entryFile = archiveFile.getArchiveEntryFile(entryPath);
+ // Resolve the entry file
+ AbstractFile entryFile = archiveFile.getArchiveEntryFile(entryPath);
- // Notify the job that we're starting to process this file
- nextFile(entryFile);
+ // Notify the job that we're starting to process this file
+ nextFile(entryFile);
- // Figure out the destination file's path, relatively to the base destination folder
- String relDestPath = baseArchiveDepth == 0
- ? entry.getPath()
- : PathUtils.removeLeadingFragments(entry.getPath(), "/", baseArchiveDepth);
+ // Figure out the destination file's path, relatively to the base destination folder
+ String relDestPath = baseArchiveDepth == 0
+ ? entry.getPath()
+ : PathUtils.removeLeadingFragments(entry.getPath(), "/", baseArchiveDepth);
- if (newName != null)
- relDestPath = newName + (PathUtils.getDepth(relDestPath, "/") <= 1 ? "" : "/" + PathUtils.removeLeadingFragments(relDestPath, "/", 1));
+ if (newName != null)
+ relDestPath = newName + (PathUtils.getDepth(relDestPath, "/") <= 1 ? "" : "/" + PathUtils.removeLeadingFragments(relDestPath, "/", 1));
- if (!"/".equals(destSeparator))
- relDestPath = relDestPath.replace("/", destSeparator);
+ if (!"/".equals(destSeparator))
+ relDestPath = relDestPath.replace("/", destSeparator);
- // Create destination AbstractFile instance
- AbstractFile destFile = destFolder.getChild(relDestPath);
+ // Create destination AbstractFile instance
+ AbstractFile destFile = destFolder.getChild(relDestPath);
- // Check for ZipSlip (see https://snyk.io/research/zip-slip-vulnerability)
- do {
- if (destFolder.isParentOf(destFile))
- break;
+ // Check for ZipSlip (see https://snyk.io/research/zip-slip-vulnerability)
+ do {
+ if (destFolder.isParentOf(destFile))
+ break;
- DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("unpack.entry_out_of_target_dir", destFile.getName()));
- // Retry loops
- if (ret == FileJobAction.RETRY)
+ DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("unpack.entry_out_of_target_dir", destFile.getName()));
+ // Retry loops
+ if (ret == FileJobAction.RETRY)
+ continue;
+ // Cancel, skip or close dialog returns false
+ return false;
+ } while (true);
+
+ // Check if the file does not already exist in the destination
+ destFile = checkForCollision(entryFile, destFolder, destFile, false);
+ if (destFile == null) {
+ // A collision occurred and either the file was skipped, or the user cancelled the job
continue;
- // Cancel, skip or close dialog returns false
- return false;
- } while (true);
+ }
- // Check if the file does not already exist in the destination
- destFile = checkForCollision(entryFile, destFolder, destFile, false);
- if (destFile == null) {
- // A collision occurred and either the file was skipped, or the user cancelled the job
- continue;
- }
+ // It is noteworthy that the iterator returns entries in no particular order (consider it random).
+ // For that reason, we cannot assume that the parent directory of an entry will be processed
+ // before the entry itself.
+
+ // If the entry is a directory ...
+ if (entryFile.isDirectory()) {
+ // Create the directory in the destination, if it doesn't already exist
+ if (!(destFile.exists() && destFile.isDirectory())) {
+ // Loop for retry
+ do {
+ try {
+ // Use mkdirs() instead of mkdir() to create any parent folder that doesn't exist yet
+ destFile.mkdirs();
+ } catch (IOException e) {
+ // Unable to create folder
+ DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_create_folder", entryFile.getName()));
+ // Retry loops
+ if (ret == FileJobAction.RETRY)
+ continue;
+ // Cancel or close dialog return false
+ return false;
+ // Skip continues
+ }
+ break;
+ } while (true);
+ }
+ }
+ // The entry is a regular file, copy it
+ else {
+ // Create the file's parent directory(s) if it doesn't already exist
+ AbstractFile destParentFile = destFile.getParent();
+ if (!destParentFile.exists()) {
+ // Use mkdirs() instead of mkdir() to create any parent folder that doesn't exist yet
+ destParentFile.mkdirs();
+ }
- // It is noteworthy that the iterator returns entries in no particular order (consider it random).
- // For that reason, we cannot assume that the parent directory of an entry will be processed
- // before the entry itself.
-
- // If the entry is a directory ...
- if (entryFile.isDirectory()) {
- // Create the directory in the destination, if it doesn't already exist
- if (!(destFile.exists() && destFile.isDirectory())) {
- // Loop for retry
- do {
- try {
- // Use mkdirs() instead of mkdir() to create any parent folder that doesn't exist yet
- destFile.mkdirs();
- } catch (IOException e) {
- // Unable to create folder
- DialogAction ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_create_folder", entryFile.getName()));
- // Retry loops
- if (ret == FileJobAction.RETRY)
- continue;
- // Cancel or close dialog return false
- return false;
- // Skip continues
- }
- break;
- } while (true);
+ if (entry.isSymlink()) {
+ Files.createSymbolicLink(
+ FileSystems.getDefault().getPath(destFile.getAbsolutePath()),
+ FileSystems.getDefault().getPath(entry.getLinkTarget()));
+ continue;
+ }
+
+ // The entry is wrapped in a ProxyFile to override #getInputStream() and delegate it to
+ // ArchiveFile#getEntryInputStream in order to take advantage of the ArchiveEntryIterator, which for
+ // some archive file implementations (such as TAR) can speed things by an order of magnitude.
+ if (!tryCopyFile(new ProxiedEntryFile(entryFile, entry, archiveFile, iterator), destFile, append, errorDialogTitle))
+ return false;
}
}
- // The entry is a regular file, copy it
- else {
- // Create the file's parent directory(s) if it doesn't already exist
- AbstractFile destParentFile = destFile.getParent();
- if (!destParentFile.exists()) {
- // Use mkdirs() instead of mkdir() to create any parent folder that doesn't exist yet
- destParentFile.mkdirs();
- }
- if (entry.isSymlink()) {
- Files.createSymbolicLink(
- FileSystems.getDefault().getPath(destFile.getAbsolutePath()),
- FileSystems.getDefault().getPath(entry.getLinkTarget()));
- continue;
+ return true;
+ } catch (IOException e) {
+ DialogAction action = null;
+ if (archiveFile.getPassword() == null) {
+ ArchivePasswordDialog dialog = new ArchivePasswordDialog(getMainFrame());
+ String password = (String) dialog.getUserInput();
+ if (password != null) {
+ archiveFile.setPassword(password);
+ action = FileJobAction.RETRY;
}
+ }
- // The entry is wrapped in a ProxyFile to override #getInputStream() and delegate it to
- // ArchiveFile#getEntryInputStream in order to take advantage of the ArchiveEntryIterator, which for
- // some archive file implementations (such as TAR) can speed things by an order of magnitude.
- if (!tryCopyFile(new ProxiedEntryFile(entryFile, entry, archiveFile, iterator), destFile, append, errorDialogTitle))
- return false;
+ if (action != FileJobAction.RETRY) {
+ action = showErrorDialog(errorDialogTitle, Translator.get("cannot_read_file", archiveFile.getName()));
+ archiveFile.setPassword(null);
}
- }
- return true;
- } catch (IOException e) {
- showErrorDialog(errorDialogTitle, Translator.get("cannot_read_file", archiveFile.getName()));
- }
+ if (action == FileJobAction.RETRY)
+ continue;
+ }
+ break;
+ } while(true);
return false;
}
diff --git a/mucommander-core/src/main/java/com/mucommander/osgi/FileEditorServiceTracker.java b/mucommander-core/src/main/java/com/mucommander/osgi/FileEditorServiceTracker.java
index 790959bc9d..1472414be2 100644
--- a/mucommander-core/src/main/java/com/mucommander/osgi/FileEditorServiceTracker.java
+++ b/mucommander-core/src/main/java/com/mucommander/osgi/FileEditorServiceTracker.java
@@ -55,7 +55,7 @@ public FileEditorService addingService(ServiceReferenceFilePanel
.
* @param parent dialog containing the panel
- * @param isActive whether the color values should be taken from the active or inactive state.
+ * @param active whether the color values should be taken from the active or inactive state.
* @param data theme to edit.
* @param fontChooser File table font chooser.
*/
- public FilePanel(PreferencesDialog parent, boolean isActive, ThemeData data, FontChooser fontChooser) {
- super(parent, Translator.get(isActive ? "theme_editor.active_panel" : "theme_editor.inactive_panel"), data);
- initUI(isActive, fontChooser);
+ public FilePanel(PreferencesDialog parent, boolean active, ThemeData data, FontChooser fontChooser) {
+ super(parent, Translator.get(active ? "theme_editor.active_panel" : "theme_editor.inactive_panel"), data);
+ initUI(active, fontChooser);
}
@@ -50,27 +53,18 @@ public FilePanel(PreferencesDialog parent, boolean isActive, ThemeData data, Fon
// - UI initialisation ---------------------------------------------------------------
// -----------------------------------------------------------------------------------
private void addForegroundColor(JPanel to, int colorId, ColorButton background, FontChooser fontChooser, FilePreviewPanel previewPanel) {
- PreviewLabel preview;
- ColorButton button;
-
- preview = new PreviewLabel();
+ PreviewLabel preview = new PreviewLabel();
preview.setTextPainted(true);
background.addUpdatedPreviewComponent(preview);
addFontChooserListener(fontChooser, preview);
- to.add(button = new ColorButton(parent, themeData, colorId, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, preview));
+ ColorButton button = new ColorButton(parent, themeData, colorId, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, preview);
+ to.add(button);
button.addUpdatedPreviewComponent(previewPanel);
}
- private void initUI(boolean isActive, FontChooser fontChooser) {
- JPanel gridPanel;
- ColorButton backgroundButton;
- ColorButton selectedBackgroundButton;
- ColorButton borderButton;
- FilePreviewPanel preview;
-
-
- gridPanel = new ProportionalGridPanel(3);
- preview = new FilePreviewPanel(themeData, isActive);
+ private void initUI(boolean active, FontChooser fontChooser) {
+ JPanel gridPanel = new ProportionalGridPanel(3);
+ FilePreviewPanel preview = new FilePreviewPanel(themeData, active);
addFontChooserListener(fontChooser, preview);
// Header
@@ -80,70 +74,87 @@ private void initUI(boolean isActive, FontChooser fontChooser) {
// Background
gridPanel.add(createCaptionLabel("theme_editor.background"));
- gridPanel.add(backgroundButton = new ColorButton(parent, themeData, isActive ? ThemeData.FILE_TABLE_BACKGROUND_COLOR :
- ThemeData.FILE_TABLE_INACTIVE_BACKGROUND_COLOR,
- PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview));
- gridPanel.add(selectedBackgroundButton = new ColorButton(parent, themeData,
- isActive ? ThemeData.FILE_TABLE_SELECTED_BACKGROUND_COLOR :
- ThemeData.FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR,
- PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview));
+ ColorButton backgroundButton = new ColorButton(parent, themeData,
+ active ? ThemeData.FILE_TABLE_BACKGROUND_COLOR :
+ ThemeData.FILE_TABLE_INACTIVE_BACKGROUND_COLOR,
+ PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview);
+ gridPanel.add(backgroundButton);
+ ColorButton selectedBackgroundButton = new ColorButton(parent, themeData,
+ active ? ThemeData.FILE_TABLE_SELECTED_BACKGROUND_COLOR :
+ ThemeData.FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR,
+ PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview);
+ gridPanel.add(selectedBackgroundButton);
// Alternate background
gridPanel.add(createCaptionLabel("theme_editor.alternate_background"));
gridPanel.add(new ColorButton(parent, themeData,
- isActive ? ThemeData.FILE_TABLE_ALTERNATE_BACKGROUND_COLOR : ThemeData.FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR,
+ active ? ThemeData.FILE_TABLE_ALTERNATE_BACKGROUND_COLOR : ThemeData.FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR,
PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview));
gridPanel.add(new JLabel());
// Folders.
gridPanel.add(createCaptionLabel("theme_editor.folder"));
- addForegroundColor(gridPanel, isActive ? ThemeData.FOLDER_FOREGROUND_COLOR : ThemeData.FOLDER_INACTIVE_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.FOLDER_FOREGROUND_COLOR : ThemeData.FOLDER_INACTIVE_FOREGROUND_COLOR,
backgroundButton, fontChooser, preview);
- addForegroundColor(gridPanel, isActive ? ThemeData.FOLDER_SELECTED_FOREGROUND_COLOR : ThemeData.FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.FOLDER_SELECTED_FOREGROUND_COLOR : ThemeData.FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR,
selectedBackgroundButton, fontChooser, preview);
// Plain files.
gridPanel.add(createCaptionLabel("theme_editor.plain_file"));
- addForegroundColor(gridPanel, isActive ? ThemeData.FILE_FOREGROUND_COLOR : ThemeData.FILE_INACTIVE_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.FILE_FOREGROUND_COLOR : ThemeData.FILE_INACTIVE_FOREGROUND_COLOR,
backgroundButton, fontChooser, preview);
- addForegroundColor(gridPanel, isActive ? ThemeData.FILE_SELECTED_FOREGROUND_COLOR : ThemeData.FILE_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.FILE_SELECTED_FOREGROUND_COLOR : ThemeData.FILE_INACTIVE_SELECTED_FOREGROUND_COLOR,
selectedBackgroundButton, fontChooser, preview);
// Archives.
gridPanel.add(createCaptionLabel("theme_editor.archive_file"));
- addForegroundColor(gridPanel, isActive ? ThemeData.ARCHIVE_FOREGROUND_COLOR : ThemeData.ARCHIVE_INACTIVE_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.ARCHIVE_FOREGROUND_COLOR : ThemeData.ARCHIVE_INACTIVE_FOREGROUND_COLOR,
backgroundButton, fontChooser, preview);
- addForegroundColor(gridPanel, isActive ? ThemeData.ARCHIVE_SELECTED_FOREGROUND_COLOR : ThemeData.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.ARCHIVE_SELECTED_FOREGROUND_COLOR : ThemeData.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR,
selectedBackgroundButton, fontChooser, preview);
// Hidden files.
gridPanel.add(createCaptionLabel("theme_editor.hidden_file"));
- addForegroundColor(gridPanel, isActive ? ThemeData.HIDDEN_FILE_FOREGROUND_COLOR : ThemeData.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.HIDDEN_FILE_FOREGROUND_COLOR : ThemeData.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR,
backgroundButton, fontChooser, preview);
- addForegroundColor(gridPanel, isActive ? ThemeData.HIDDEN_FILE_SELECTED_FOREGROUND_COLOR : ThemeData.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.HIDDEN_FILE_SELECTED_FOREGROUND_COLOR : ThemeData.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR,
selectedBackgroundButton, fontChooser, preview);
// Symlinks.
gridPanel.add(createCaptionLabel("theme_editor.symbolic_link"));
- addForegroundColor(gridPanel, isActive ? ThemeData.SYMLINK_FOREGROUND_COLOR : ThemeData.SYMLINK_INACTIVE_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.SYMLINK_FOREGROUND_COLOR : ThemeData.SYMLINK_INACTIVE_FOREGROUND_COLOR,
backgroundButton, fontChooser, preview);
- addForegroundColor(gridPanel, isActive ? ThemeData.SYMLINK_SELECTED_FOREGROUND_COLOR : ThemeData.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.SYMLINK_SELECTED_FOREGROUND_COLOR : ThemeData.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR,
selectedBackgroundButton, fontChooser, preview);
// Marked files.
gridPanel.add(createCaptionLabel("theme_editor.marked_file"));
- addForegroundColor(gridPanel, isActive ? ThemeData.MARKED_FOREGROUND_COLOR : ThemeData.MARKED_INACTIVE_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.MARKED_FOREGROUND_COLOR : ThemeData.MARKED_INACTIVE_FOREGROUND_COLOR,
backgroundButton, fontChooser, preview);
- addForegroundColor(gridPanel, isActive ? ThemeData.MARKED_SELECTED_FOREGROUND_COLOR : ThemeData.MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ addForegroundColor(gridPanel, active ? ThemeData.MARKED_SELECTED_FOREGROUND_COLOR : ThemeData.MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR,
selectedBackgroundButton, fontChooser, preview);
+ // Read-only
+ gridPanel.add(createCaptionLabel("theme_editor.read_only_file"));
+ addForegroundColor(gridPanel,
+ active ? ThemeData.READ_ONLY_FOREGROUND_COLOR : ThemeData.READ_ONLY_INACTIVE_FOREGROUND_COLOR,
+ backgroundButton,
+ fontChooser,
+ preview);
+ addForegroundColor(gridPanel,
+ active ? ThemeData.READ_ONLY_SELECTED_FOREGROUND_COLOR
+ : ThemeData.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR,
+ selectedBackgroundButton, fontChooser, preview);
+
// Border.
gridPanel.add(createCaptionLabel("theme_editor.border"));
- gridPanel.add(borderButton = new ColorButton(parent, themeData, isActive ? ThemeData.FILE_TABLE_BORDER_COLOR :
- ThemeData.FILE_TABLE_INACTIVE_BORDER_COLOR, PreviewLabel.BORDER_COLOR_PROPERTY_NAME));
+ ColorButton borderButton = new ColorButton(parent, themeData, active ? ThemeData.FILE_TABLE_BORDER_COLOR :
+ ThemeData.FILE_TABLE_INACTIVE_BORDER_COLOR, PreviewLabel.BORDER_COLOR_PROPERTY_NAME);
+ gridPanel.add(borderButton);
borderButton.addUpdatedPreviewComponent(preview);
- gridPanel.add(borderButton = new ColorButton(parent, themeData, isActive ? ThemeData.FILE_TABLE_SELECTED_OUTLINE_COLOR :
- ThemeData.FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR, PreviewLabel.BORDER_COLOR_PROPERTY_NAME));
+ borderButton = new ColorButton(parent, themeData, active ? ThemeData.FILE_TABLE_SELECTED_OUTLINE_COLOR :
+ ThemeData.FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR, PreviewLabel.BORDER_COLOR_PROPERTY_NAME);
+ gridPanel.add(borderButton);
borderButton.addUpdatedPreviewComponent(preview);
setLayout(new BorderLayout());
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FilePreviewPanel.java b/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FilePreviewPanel.java
index 25de30b263..4de7cc3502 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FilePreviewPanel.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FilePreviewPanel.java
@@ -40,12 +40,13 @@
class FilePreviewPanel extends JScrollPane implements PropertyChangeListener {
// - Row identifiers ------------------------------------------------------------------
// -----------------------------------------------------------------------------------
- private static final int FOLDER = 0;
- private static final int PLAIN_FILE = 1;
- private static final int ARCHIVE = 2;
- private static final int HIDDEN_FILE = 3;
- private static final int SYMLINK = 4;
- private static final int MARKED_FILE = 5;
+ private static final int FOLDER = 0;
+ private static final int PLAIN_FILE = 1;
+ private static final int ARCHIVE = 2;
+ private static final int HIDDEN_FILE = 3;
+ private static final int SYMLINK = 4;
+ private static final int READ_ONLY_FILE = 5;
+ private static final int MARKED_FILE = 6;
@@ -136,6 +137,7 @@ public PreviewTable() {
{"", Translator.get("theme_editor.archive_file")},
{"", Translator.get("theme_editor.hidden_file")},
{"", Translator.get("theme_editor.symbolic_link")},
+ {"", Translator.get("theme_editor.read_only_file")},
{"", Translator.get("theme_editor.marked_file")}},
new String[] {"", Translator.get("preview")});
@@ -291,6 +293,13 @@ private Color getForegroundColor(int row, boolean isSelected) {
return FilePreviewPanel.this.data.getColor(isSelected ? ThemeData.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR :
ThemeData.SYMLINK_INACTIVE_FOREGROUND_COLOR);
+ // Read-only
+ case READ_ONLY_FILE:
+ if (FilePreviewPanel.this.isActive)
+ return FilePreviewPanel.this.data.getColor(isSelected ? ThemeData.READ_ONLY_SELECTED_FOREGROUND_COLOR :
+ ThemeData.READ_ONLY_FOREGROUND_COLOR);
+ return FilePreviewPanel.this.data.getColor(isSelected ? ThemeData.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR :
+ ThemeData.READ_ONLY_INACTIVE_FOREGROUND_COLOR);
// Marked files.
case MARKED_FILE:
if(FilePreviewPanel.this.isActive)
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FolderPanePanel.java b/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FolderPanePanel.java
index 097755992c..ede6629751 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FolderPanePanel.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/FolderPanePanel.java
@@ -17,6 +17,13 @@
package com.mucommander.ui.dialog.pref.theme;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
import com.mucommander.commons.util.ui.layout.ProportionalGridPanel;
import com.mucommander.commons.util.ui.layout.YBoxPanel;
import com.mucommander.text.Translator;
@@ -24,9 +31,6 @@
import com.mucommander.ui.dialog.pref.PreferencesDialog;
import com.mucommander.ui.theme.ThemeData;
-import javax.swing.*;
-import java.awt.*;
-
/**
* @author Nicolas Rinaudo, Maxence Bernard
*/
@@ -52,18 +56,15 @@ public FolderPanePanel(PreferencesDialog parent, ThemeData themeData) {
* Initialises the panel's UI.
*/
private void initUI() {
- JTabbedPane tabbedPane;
- FontChooser fontChooser;
- FilePanel filePanel;
-
- tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+ JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);
// Adds the general panel.
+ FontChooser fontChooser = createFontChooser(ThemeData.FILE_TABLE_FONT);
tabbedPane.add(Translator.get("theme_editor.general"),
- createScrollPane(createGeneralPanel(fontChooser = createFontChooser(ThemeData.FILE_TABLE_FONT))));
+ createScrollPane(createGeneralPanel(fontChooser)));
// Adds the active panel.
- filePanel = new FilePanel(parent, true, themeData, fontChooser);
+ FilePanel filePanel = new FilePanel(parent, true, themeData, fontChooser);
tabbedPane.add(filePanel.getTitle(), createScrollPane(filePanel));
// Adds the inactive panel.
@@ -79,28 +80,23 @@ private void initUI() {
* Creates the 'general' theme.
*/
private JPanel createGeneralPanel(FontChooser chooser) {
- YBoxPanel mainPanel;
- JPanel quickSearchPanel;
- ProportionalGridPanel panel;
- JPanel wrapper;
-
// Initialises the quicksearch panel.
- panel = new ProportionalGridPanel(4);
+ ProportionalGridPanel panel = new ProportionalGridPanel(4);
addLabelRow(panel);
panel.add(addColorButtons(panel, chooser, "theme_editor.quick_search.unmatched_file", ThemeData.FILE_TABLE_UNMATCHED_FOREGROUND_COLOR,
ThemeData.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR));
- quickSearchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ JPanel quickSearchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
quickSearchPanel.add(panel);
quickSearchPanel.setBorder(BorderFactory.createTitledBorder(Translator.get("theme_editor.quick_search")));
// Initialises the panel.
- mainPanel = new YBoxPanel();
+ YBoxPanel mainPanel = new YBoxPanel();
mainPanel.add(chooser);
mainPanel.addSpace(10);
mainPanel.add(quickSearchPanel);
// Wraps everything in a border layout.
- wrapper = new JPanel(new BorderLayout());
+ JPanel wrapper = new JPanel(new BorderLayout());
wrapper.add(mainPanel, BorderLayout.NORTH);
return wrapper;
}
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/ThemeEditorPanel.java b/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/ThemeEditorPanel.java
index 8918b74e24..97082d6c82 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/ThemeEditorPanel.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/dialog/pref/theme/ThemeEditorPanel.java
@@ -85,9 +85,7 @@ public ThemeEditorPanel(PreferencesDialog parent, String title, ThemeData themeD
* @return a caption label containing the specified localised entry.
*/
protected JLabel createCaptionLabel(String dictionaryKey) {
- JLabel captionLabel;
-
- captionLabel = new JLabel(Translator.get(dictionaryKey));
+ JLabel captionLabel = new JLabel(Translator.get(dictionaryKey));
captionLabel.setFont(captionLabelFont);
captionLabel.setForeground(captionTextColor);
@@ -125,7 +123,7 @@ protected void addLabelRow(ProportionalGridPanel panel, boolean includePreview)
panel.add(createCaptionLabel("theme_editor.background"));
// Adds the preview label if requested.
- if(includePreview)
+ if (includePreview)
panel.add(createCaptionLabel("preview"));
}
@@ -138,13 +136,11 @@ protected void addLabelRow(ProportionalGridPanel panel, boolean includePreview)
* @param fontId identifier of the font this chooser will be editing.
*/
protected FontChooser createFontChooser(int fontId) {
- FontChooser fontChooser; // Font chooser that will be returned.
- ChangeListener listener; // Internal listener.
-
// Initialises the font chooser.
- fontChooser = new FontChooser(themeData.getFont(fontId));
+ FontChooser fontChooser = new FontChooser(themeData.getFont(fontId));
fontChooser.setBorder(BorderFactory.createTitledBorder(Translator.get("theme_editor.font")));
- fontChooser.addChangeListener(listener = new ThemeFontChooserListener(themeData, fontId, parent));
+ ChangeListener listener = new ThemeFontChooserListener(themeData, fontId, parent);
+ fontChooser.addChangeListener(listener);
// Hold a reference to this listener to prevent garbage collection
listenerReferences.add(listener);
@@ -163,7 +159,7 @@ protected FontChooser createFontChooser(int fontId) {
*/
protected void addFontChooserListener(FontChooser fontChooser, JComponent previewComponent) {
// Update button font when a new font has been chosen in the FontChooser
- if(fontChooser!=null) {
+ if (fontChooser!=null) {
ChangeListener listener;
fontChooser.addChangeListener(listener = new PreviewFontChooserListener(previewComponent));
previewComponent.setFont(fontChooser.getCurrentFont());
@@ -185,9 +181,7 @@ protected void addFontChooserListener(FontChooser fontChooser, JComponent previe
* @param panel panel to wrap in a JScrollPane
.
*/
protected JComponent createScrollPane(JPanel panel) {
- JScrollPane scrollPane;
-
- scrollPane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ JScrollPane scrollPane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(null);
return scrollPane;
@@ -228,25 +222,24 @@ protected PreviewLabel addColorButtons(ProportionalGridPanel gridPanel, FontChoo
* @param comp component to register as a listener on the color buttons.
*/
protected PreviewLabel addColorButtons(ProportionalGridPanel gridPanel, FontChooser fontChooser, String label, int foregroundId, int backgroundId, JComponent comp) {
- ColorButton colorButton;
- PreviewLabel previewLabel;
-
// Adds the row's caption label.
gridPanel.add(createCaptionLabel(label));
// Initialises the color buttons' preview label.
- previewLabel = new PreviewLabel();
+ PreviewLabel previewLabel = new PreviewLabel();
previewLabel.setTextPainted(true);
addFontChooserListener(fontChooser, previewLabel);
// Creates the foreground color button.
- gridPanel.add(colorButton = new ColorButton(parent, themeData, foregroundId, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, previewLabel));
- if(comp != null)
+ ColorButton colorButton = new ColorButton(parent, themeData, foregroundId, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, previewLabel);
+ gridPanel.add(colorButton);
+ if (comp != null)
colorButton.addUpdatedPreviewComponent(comp);
// Creates the background color button.
- gridPanel.add(colorButton = new ColorButton(parent, themeData, backgroundId, PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, previewLabel));
- if(comp != null)
+ colorButton = new ColorButton(parent, themeData, backgroundId, PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, previewLabel);
+ gridPanel.add(colorButton);
+ if (comp != null)
colorButton.addUpdatedPreviewComponent(comp);
return previewLabel;
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/event/LocationManager.java b/mucommander-core/src/main/java/com/mucommander/ui/event/LocationManager.java
index 3c75e8cfbf..99c5af4129 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/event/LocationManager.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/event/LocationManager.java
@@ -26,8 +26,10 @@
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileURL;
import com.mucommander.commons.file.MonitoredFile;
+import com.mucommander.commons.file.archive.AbstractArchiveFile;
import com.mucommander.core.FolderChangeMonitor;
import com.mucommander.core.GlobalLocationHistory;
+import com.mucommander.ui.dialog.file.ArchivePasswordDialog;
import com.mucommander.ui.main.ConfigurableFolderFilter;
import com.mucommander.ui.main.FolderPanel;
@@ -83,16 +85,27 @@ public void setCurrentFolder(AbstractFile folder, AbstractFile fileToSelect, boo
MonitoredFile newCurrentFile = folder.toMonitoredFile();
newCurrentFile.startWatch();
- AbstractFile[] children = emptyAbstractFilesArray;
- try {
- children = folder.ls(configurableFolderFilter);
- firstRun = false;
- } catch (Exception e) {
- LOGGER.debug("Couldn't ls children of " + folder.getAbsolutePath() + ", error: " + e.getMessage());
- if (!firstRun) {
- throw new RuntimeException(e.getMessage());
- }
- }
+ AbstractFile[] children = emptyAbstractFilesArray;
+ do {
+ try {
+ children = folder.ls(configurableFolderFilter);
+ firstRun = false;
+ } catch (Exception e) {
+ LOGGER.debug("Couldn't ls children of " + folder.getAbsolutePath() + ", error: " + e.getMessage());
+ if (folder.isArchive()) {
+ ArchivePasswordDialog dialog = new ArchivePasswordDialog(folderPanel.getMainFrame());
+ String password = (String) dialog.getUserInput();
+ if (password != null) {
+ ((AbstractArchiveFile) folder).setPassword(password);
+ continue;
+ }
+ }
+ if (!firstRun) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ break;
+ } while (true);
folderPanel.setCurrentFolder(folder, children, fileToSelect, changeLockedTab);
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/main/ConfigurableFolderFilter.java b/mucommander-core/src/main/java/com/mucommander/ui/main/ConfigurableFolderFilter.java
index 038414c371..04afc65296 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/main/ConfigurableFolderFilter.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/main/ConfigurableFolderFilter.java
@@ -54,17 +54,17 @@ public ConfigurableFolderFilter() {
private void configureFilters() {
// Filters out hidden files, null when 'show hidden files' option is enabled
- if(!MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_HIDDEN_FILES, MuPreferences.DEFAULT_SHOW_HIDDEN_FILES)) {
+ if (!MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_HIDDEN_FILES, MuPreferences.DEFAULT_SHOW_HIDDEN_FILES)) {
// This filter is inverted and matches non-hidden files
addFileFilter(hiddenFileFilter);
}
// Filters out Mac OS X .DS_Store files, null when 'show DS_Store files' option is enabled
- if(!MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_DS_STORE_FILES, MuPreferences.DEFAULT_SHOW_DS_STORE_FILES))
+ if (!MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_DS_STORE_FILES, MuPreferences.DEFAULT_SHOW_DS_STORE_FILES))
addFileFilter(dsFileFilter);
/** Filters out Mac OS X system folders, null when 'show system folders' option is enabled */
- if(!MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_SYSTEM_FOLDERS, MuPreferences.DEFAULT_SHOW_SYSTEM_FOLDERS))
+ if (!MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_SYSTEM_FOLDERS, MuPreferences.DEFAULT_SHOW_SYSTEM_FOLDERS))
addFileFilter(systemFileFilter);
}
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/main/MainFrame.java b/mucommander-core/src/main/java/com/mucommander/ui/main/MainFrame.java
index a1fe0c78d5..c73d01891a 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/main/MainFrame.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/main/MainFrame.java
@@ -49,7 +49,6 @@
import com.mucommander.snapshot.MuSnapshot;
import com.mucommander.ui.action.ActionKeymap;
import com.mucommander.ui.action.ActionManager;
-import com.mucommander.ui.action.impl.CloseWindowAction;
import com.mucommander.ui.button.ToolbarMoreButton;
import com.mucommander.ui.event.ActivePanelListener;
import com.mucommander.ui.event.LocationEvent;
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/main/StatusBar.java b/mucommander-core/src/main/java/com/mucommander/ui/main/StatusBar.java
index 281d908e81..75a668bfbf 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/main/StatusBar.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/main/StatusBar.java
@@ -17,20 +17,21 @@
package com.mucommander.ui.main;
+import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
+import java.awt.FlowLayout;
import java.awt.Graphics;
+import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
+import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.Box;
-import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
@@ -44,8 +45,6 @@
import com.mucommander.commons.conf.ConfigurationEvent;
import com.mucommander.commons.conf.ConfigurationListener;
import com.mucommander.commons.file.AbstractFile;
-import com.mucommander.commons.util.cache.FastLRUCache;
-import com.mucommander.commons.util.cache.LRUCache;
import com.mucommander.commons.util.ui.border.MutableLineBorder;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
@@ -87,7 +86,7 @@
*
* @author Maxence Bernard
*/
-public class StatusBar extends JPanel implements Runnable, MouseListener, ActivePanelListener, TableSelectionListener, LocationListener, ComponentListener, ThemeListener {
+public class StatusBar extends JPanel {
private static final Logger LOGGER = LoggerFactory.getLogger(StatusBar.class);
private MainFrame mainFrame;
@@ -128,6 +127,12 @@ public class StatusBar extends JPanel implements Runnable, MouseListener, Active
/** Holds the path of the volume for which free/total space was last retrieved by {@link #autoUpdateThread} */
private String volumePath;
+ /** hold references to listeners that are stored with weak references to prevent them from being collected by the garbage collector */
+ private LocationListener locationListener;
+ private TableSelectionListener tableSelectionListener;
+ private ActivePanelListener activePanelListener;
+ private ThemeListener themeListener;
+
static {
// Initialize the size column format based on the configuration
setSelectedFileSizeFormat(MuConfigurations.getPreferences().getVariable(MuPreference.DISPLAY_COMPACT_FILE_SIZE,
@@ -167,52 +172,93 @@ private static void setSelectedFileSizeFormat(boolean compactSize) {
*/
public StatusBar(MainFrame mainFrame) {
// Create and add status bar
- setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ setLayout(new BorderLayout());
this.mainFrame = mainFrame;
selectedFilesLabel = new JLabel("");
dial = new SpinningDial();
- add(selectedFilesLabel);
+ add(selectedFilesLabel, BorderLayout.CENTER);
- add(Box.createHorizontalGlue());
+ JPanel eastPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
JobsPopupButton jobsButton = new JobsPopupButton();
jobsButton.setPopupMenuLocation(SwingConstants.TOP);
- add(jobsButton);
- add(Box.createRigidArea(new Dimension(2, 0)));
+ eastPanel.add(jobsButton);
+ eastPanel.add(Box.createRigidArea(new Dimension(2, 0)));
// Add a button for interacting with the trash, only if the current platform has a trash implementation
- if(DesktopManager.getTrash()!=null) {
+ if (DesktopManager.getTrash() != null) {
TrashPopupButton trashButton = new TrashPopupButton(mainFrame);
trashButton.setPopupMenuLocation(SwingConstants.TOP);
- add(trashButton);
- add(Box.createRigidArea(new Dimension(2, 0)));
+ eastPanel.add(trashButton);
+ eastPanel.add(Box.createRigidArea(new Dimension(2, 0)));
}
volumeSpaceLabel = new VolumeSpaceLabel();
- add(volumeSpaceLabel);
+ eastPanel.add(volumeSpaceLabel);
+
+ add(eastPanel, BorderLayout.EAST);
// Show/hide this status bar based on user preferences
// Note: setVisible has to be called even with true for the auto-update thread to be initialized
setVisible(MuConfigurations.getPreferences().getVariable(MuPreference.STATUS_BAR_VISIBLE, MuPreferences.DEFAULT_STATUS_BAR_VISIBLE));
// Catch location events to update status bar info when folder is changed
+ locationListener = new LocationListener() {
+ @Override
+ public void locationChanged(LocationEvent e) {
+ dial.setAnimated(false);
+ updateStatusInfo();
+ }
+ @Override
+ public void locationChanging(LocationEvent e) {
+ // Show a message in the status bar saying that folder is being changed
+ setStatusInfo(Translator.get("status_bar.connecting_to_folder"), dial, true);
+ dial.setAnimated(true);
+ }
+ @Override
+ public void locationCancelled(LocationEvent e) {
+ dial.setAnimated(false);
+ updateStatusInfo();
+ }
+ @Override
+ public void locationFailed(LocationEvent e) {
+ dial.setAnimated(false);
+ updateStatusInfo();
+ }
+ };
+
FolderPanel leftPanel = mainFrame.getLeftPanel();
- leftPanel.getLocationManager().addLocationListener(this);
+ leftPanel.getLocationManager().addLocationListener(locationListener);
FolderPanel rightPanel = mainFrame.getRightPanel();
- rightPanel.getLocationManager().addLocationListener(this);
+ rightPanel.getLocationManager().addLocationListener(locationListener);
// Catch table selection change events to update the selected files info when the selected files have changed on
// one of the file tables
- leftPanel.getFileTable().addTableSelectionListener(this);
- rightPanel.getFileTable().addTableSelectionListener(this);
+ tableSelectionListener = new TableSelectionListener() {
+ @Override
+ public void selectedFileChanged(FileTable source) {
+ // No need to update if the originating FileTable is not the currently active one
+ if(source==mainFrame.getActiveTable() && mainFrame.isForegroundActive())
+ updateSelectedFilesInfo();
+ }
+ @Override
+ public void markedFilesChanged(FileTable source) {
+ // No need to update if the originating FileTable is not the currently active one
+ if(source==mainFrame.getActiveTable() && mainFrame.isForegroundActive())
+ updateSelectedFilesInfo();
+ }
+ };
+ leftPanel.getFileTable().addTableSelectionListener(tableSelectionListener);
+ rightPanel.getFileTable().addTableSelectionListener(tableSelectionListener);
// Catch active panel change events to update status bar info when current table has changed
- mainFrame.addActivePanelListener(this);
+ activePanelListener = folderPanel -> updateStatusInfo();
+ mainFrame.addActivePanelListener(activePanelListener);
// Catch main frame close events to make sure autoUpdateThread is finished
mainFrame.addWindowListener(new WindowAdapter() {
@@ -232,20 +278,62 @@ public void windowGainedFocus(WindowEvent e) {
});
// Catch mouse events to pop up a menu on right-click
- selectedFilesLabel.addMouseListener(this);
- volumeSpaceLabel.addMouseListener(this);
- addMouseListener(this);
+ MouseAdapter mouseAdapter = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ // Discard mouse events while in 'no events mode'
+ if (mainFrame.getNoEventsMode())
+ return;
+
+ // Right clicking on the toolbar brings up a popup menu that allows the user to hide this status bar
+ if (DesktopManager.isRightMouseButton(e)) {
+ // if (e.isPopupTrigger()) { // Doesn't work under Mac OS X (CTRL+click doesn't return true)
+ JPopupMenu popupMenu = new JPopupMenu();
+ popupMenu.add(ActionManager.getActionInstance(ActionType.ToggleStatusBar, mainFrame));
+ popupMenu.show(StatusBar.this, e.getX(), e.getY());
+ popupMenu.setVisible(true);
+ }
+ };
+ };
+ selectedFilesLabel.addMouseListener(mouseAdapter);
+ volumeSpaceLabel.addMouseListener(mouseAdapter);
+ addMouseListener(mouseAdapter);
// Catch component events to be notified when this component is made visible
// and update status info
- addComponentListener(this);
+ addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentShown(ComponentEvent e) {
+ // Invoked when the component has been made visible (apparently not called when just created)
+ // Status bar needs to be updated since it is not updated when not visible
+ updateStatusInfo();
+ };
+ });
// Initialises theme.
selectedFilesLabel.setFont(ThemeManager.getCurrentFont(Theme.STATUS_BAR_FONT));
selectedFilesLabel.setForeground(ThemeManager.getCurrentColor(Theme.STATUS_BAR_FOREGROUND_COLOR));
volumeSpaceLabel.setFont(ThemeManager.getCurrentFont(Theme.STATUS_BAR_FONT));
volumeSpaceLabel.setForeground(ThemeManager.getCurrentColor(Theme.STATUS_BAR_FOREGROUND_COLOR));
- ThemeManager.addCurrentThemeListener(this);
+ themeListener = new ThemeListener() {
+ @Override
+ public void fontChanged(FontChangedEvent event) {
+ if(event.getFontId() == Theme.STATUS_BAR_FONT) {
+ selectedFilesLabel.setFont(event.getFont());
+ volumeSpaceLabel.setFont(event.getFont());
+ repaint();
+ }
+ }
+ @Override
+ public void colorChanged(ColorChangedEvent event) {
+ if(event.getColorId() == Theme.STATUS_BAR_FOREGROUND_COLOR) {
+ selectedFilesLabel.setForeground(event.getColor());
+ volumeSpaceLabel.setForeground(event.getColor());
+ repaint();
+ }
+ }
+ };
+ ThemeManager.addCurrentThemeListener(themeListener);
}
@@ -291,25 +379,27 @@ public void updateSelectedFilesInfo() {
else
nbSelectedFiles = nbMarkedFiles;
- String filesInfo;
+ StringBuilder filesInfo = new StringBuilder();
+ String tooltip = null;
- if(fileCount==0) {
+ if (fileCount==0) {
// Set status bar to a space character, not an empty string
// otherwise it will disappear
- filesInfo = " ";
- }
- else {
- filesInfo = Translator.get("status_bar.selected_files", ""+nbSelectedFiles, ""+fileCount);
+ filesInfo.append(" ");
+ } else {
+ filesInfo.append(Translator.get("status_bar.selected_files", nbSelectedFiles, fileCount));
- if(nbMarkedFiles>0)
- filesInfo += " - " + SizeFormat.format(markedTotalSize, selectedFileSizeFormat);
+ if (nbMarkedFiles > 0)
+ filesInfo.append(String.format(" - %s", SizeFormat.format(markedTotalSize, selectedFileSizeFormat)));
- if(selectedFile!=null)
- filesInfo += " - "+selectedFile.getName();
+ if (selectedFile != null) {
+ filesInfo.append(String.format(" - %s", selectedFile.getName()));
+ tooltip = selectedFile.getName();
+ }
}
// Update label
- setStatusInfo(filesInfo);
+ setStatusInfo(filesInfo.toString(), tooltip, null, false);
}
@@ -322,7 +412,12 @@ public void updateSelectedFilesInfo() {
* @param iconBeforeText if true, icon will be placed on the left side of the text, if not on the right side
*/
public void setStatusInfo(String text, Icon icon, boolean iconBeforeText) {
+ setStatusInfo(text, null, icon, iconBeforeText);
+ }
+
+ private void setStatusInfo(String text, String tooltip, Icon icon, boolean iconBeforeText) {
selectedFilesLabel.setText(text);
+ selectedFilesLabel.setToolTipText(tooltip);
if(icon==null) {
// What we don't want here is the label's height to change depending on whether it has an icon or not.
@@ -357,7 +452,37 @@ public void setStatusInfo(String infoMessage) {
private synchronized void startAutoUpdate() {
if (autoUpdateThread==null) {
// Start volume info auto-update thread
- autoUpdateThread = new Thread(this, "StatusBar autoUpdateThread");
+ autoUpdateThread = new Thread(() -> {
+ // Periodically updates volume info (free / total space).
+ while (!mainFrameDisposed) { // Stop when MainFrame is disposed
+ // Update volume info if:
+ // - status bar is visible
+ // - MainFrame is active and in the foreground
+ // Volume info update will potentially hit the LRU cache and not actually update volume info
+ if (isVisible() && mainFrame.isForegroundActive()) {
+ final AbstractFile currentFolder = getCurrentFolder();
+ volumePath = getVolumePath(currentFolder);
+
+ // Retrieves free and total volume space.
+ long volumeFree = getFreeSpace(currentFolder);
+ long volumeTotal = getTotalSpace(currentFolder);
+
+ volumeSpaceLabel.setVolumeSpace(volumeTotal, volumeFree);
+ }
+
+ // Sleep for a while
+ if (!autoUpdateThreadNotified) {
+ synchronized(autoUpdateThread) {
+ if (!autoUpdateThreadNotified) {
+ try { autoUpdateThread.wait(AUTO_UPDATE_PERIOD); }
+ catch (InterruptedException e) {}
+ }
+ }
+ }
+ autoUpdateThreadNotified = false;
+ }
+ });
+ autoUpdateThread.setName("StatusBar autoUpdateThread");
// Set the thread as a daemon thread
autoUpdateThread.setDaemon(true);
autoUpdateThread.start();
@@ -380,35 +505,6 @@ public void setVisible(boolean visible) {
}
- //////////////////////
- // Runnable methods //
- //////////////////////
-
- /**
- * Periodically updates volume info (free / total space).
- */
- public void run() {
- while (!mainFrameDisposed) { // Stop when MainFrame is disposed
- // Update volume info if:
- // - status bar is visible
- // - MainFrame is active and in the foreground
- // Volume info update will potentially hit the LRU cache and not actually update volume info
- if (isVisible() && mainFrame.isForegroundActive()) {
- final AbstractFile currentFolder = getCurrentFolder();
- volumePath = getVolumePath(currentFolder);
-
- // Retrieves free and total volume space.
- long volumeFree = getFreeSpace(currentFolder);
- long volumeTotal = getTotalSpace(currentFolder);
-
- volumeSpaceLabel.setVolumeSpace(volumeTotal, volumeFree);
- }
-
- // Sleep for a while
- sleep();
- }
- }
-
private AbstractFile getCurrentFolder() {
return mainFrame.getActivePanel().getCurrentFolder();
}
@@ -421,20 +517,8 @@ private boolean isVolumeChanged() {
return volumePath == null || !volumePath.equals(getVolumePath(getCurrentFolder()));
}
- private void sleep() {
- if (!autoUpdateThreadNotified) {
- synchronized(autoUpdateThread) {
- if (!autoUpdateThreadNotified) {
- try { autoUpdateThread.wait(AUTO_UPDATE_PERIOD); }
- catch (InterruptedException e) {}
- }
- }
- }
- autoUpdateThreadNotified = false;
- }
-
private void triggerVolumeInfoUpdate() {
- if (!autoUpdateThreadNotified) {
+ if (!autoUpdateThreadNotified && autoUpdateThread != null) {
synchronized(autoUpdateThread) {
if (!autoUpdateThreadNotified) {
autoUpdateThreadNotified = true;
@@ -460,127 +544,6 @@ private long getTotalSpace(AbstractFile currentFolder) {
catch(IOException e) { return -1; }
}
- ////////////////////////////////////////
- // ActivePanelListener implementation //
- ////////////////////////////////////////
-
- public void activePanelChanged(FolderPanel folderPanel) {
- updateStatusInfo();
- }
-
-
- ///////////////////////////////////////////
- // TableSelectionListener implementation //
- ///////////////////////////////////////////
-
- public void selectedFileChanged(FileTable source) {
- // No need to update if the originating FileTable is not the currently active one
- if(source==mainFrame.getActiveTable() && mainFrame.isForegroundActive())
- updateSelectedFilesInfo();
- }
-
- public void markedFilesChanged(FileTable source) {
- // No need to update if the originating FileTable is not the currently active one
- if(source==mainFrame.getActiveTable() && mainFrame.isForegroundActive())
- updateSelectedFilesInfo();
- }
-
-
- /////////////////////////////////////
- // LocationListener implementation //
- /////////////////////////////////////
-
- public void locationChanged(LocationEvent e) {
- dial.setAnimated(false);
- updateStatusInfo();
- }
-
- public void locationChanging(LocationEvent e) {
- // Show a message in the status bar saying that folder is being changed
- setStatusInfo(Translator.get("status_bar.connecting_to_folder"), dial, true);
- dial.setAnimated(true);
- }
-
- public void locationCancelled(LocationEvent e) {
- dial.setAnimated(false);
- updateStatusInfo();
- }
-
- public void locationFailed(LocationEvent e) {
- dial.setAnimated(false);
- updateStatusInfo();
- }
-
-
- //////////////////////////////////
- // MouseListener implementation //
- //////////////////////////////////
-
- public void mouseClicked(MouseEvent e) {
- // Discard mouse events while in 'no events mode'
- if(mainFrame.getNoEventsMode())
- return;
-
- // Right clicking on the toolbar brings up a popup menu that allows the user to hide this status bar
- if (DesktopManager.isRightMouseButton(e)) {
- // if (e.isPopupTrigger()) { // Doesn't work under Mac OS X (CTRL+click doesn't return true)
- JPopupMenu popupMenu = new JPopupMenu();
- popupMenu.add(ActionManager.getActionInstance(ActionType.ToggleStatusBar, mainFrame));
- popupMenu.show(this, e.getX(), e.getY());
- popupMenu.setVisible(true);
- }
- }
-
- public void mouseReleased(MouseEvent e) {
- }
-
- public void mousePressed(MouseEvent e) {
- }
-
- public void mouseEntered(MouseEvent e) {
- }
-
- public void mouseExited(MouseEvent e) {
- }
-
-
- //////////////////////////////////////
- // ComponentListener implementation //
- //////////////////////////////////////
-
- public void componentShown(ComponentEvent e) {
- // Invoked when the component has been made visible (apparently not called when just created)
- // Status bar needs to be updated since it is not updated when not visible
- updateStatusInfo();
- }
-
- public void componentHidden(ComponentEvent e) {
- }
-
- public void componentMoved(ComponentEvent e) {
- }
-
- public void componentResized(ComponentEvent e) {
- }
-
-
- public void fontChanged(FontChangedEvent event) {
- if(event.getFontId() == Theme.STATUS_BAR_FONT) {
- selectedFilesLabel.setFont(event.getFont());
- volumeSpaceLabel.setFont(event.getFont());
- repaint();
- }
- }
-
- public void colorChanged(ColorChangedEvent event) {
- if(event.getColorId() == Theme.STATUS_BAR_FOREGROUND_COLOR) {
- selectedFilesLabel.setForeground(event.getColor());
- volumeSpaceLabel.setForeground(event.getColor());
- repaint();
- }
- }
-
-
///////////////////
// Inner classes //
///////////////////
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/main/commandbar/CommandBar.java b/mucommander-core/src/main/java/com/mucommander/ui/main/commandbar/CommandBar.java
index b28a3f20cf..2679b7d52a 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/main/commandbar/CommandBar.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/main/commandbar/CommandBar.java
@@ -17,25 +17,34 @@
package com.mucommander.ui.main.commandbar;
+import java.awt.GridLayout;
+import java.awt.Point;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Arrays;
+
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+
import com.mucommander.core.desktop.DesktopManager;
import com.mucommander.desktop.ActionType;
import com.mucommander.ui.action.ActionManager;
import com.mucommander.ui.main.MainFrame;
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-
/**
* CommandBar is the button bar that sits at the bottom of the main window and provides access to
* main commander actions (view, edit, copy, move...).
*
* @author Maxence Bernard, Arik Hadas
*/
-public class CommandBar extends JPanel implements KeyListener, MouseListener, CommandBarAttributesListener {
+public class CommandBar extends JPanel {
/** Parent MainFrame instance */
private MainFrame mainFrame;
@@ -62,19 +71,65 @@ public CommandBar(MainFrame mainFrame) {
this.mainFrame = mainFrame;
// Listen to modifier key events to display alternate actions
- mainFrame.getLeftPanel().getFileTable().addKeyListener(this);
- mainFrame.getRightPanel().getFileTable().addKeyListener(this);
+ KeyAdapter keyAdapter = new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ // Display alternate actions when the modifier key is pressed
+ if (e.getKeyCode() == modifier.getKeyCode())
+ setAlternateActionsMode(true);
+ }
+ @Override
+ public void keyReleased(KeyEvent e) {
+ // Display regular actions when the modifier key is released
+ if (e.getKeyCode() == modifier.getKeyCode())
+ setAlternateActionsMode(false);
+ }
+ };
+ mainFrame.getLeftPanel().getFileTable().addKeyListener(keyAdapter);
+ mainFrame.getRightPanel().getFileTable().addKeyListener(keyAdapter);
// Listen to mouse events to popup a menu when command bar is right clicked
- addMouseListener(this);
+ MouseAdapter mouseAdapter = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ // Right clicking on the toolbar brings up a popup menu
+ if (DesktopManager.isRightMouseButton(e)) {
+ // if (e.isPopupTrigger()) { // Doesn't work under Mac OS X (CTRL+click doesn't return true)
+ JPopupMenu popupMenu = new JPopupMenu();
+ popupMenu.add(ActionManager.getActionInstance(ActionType.ToggleCommandBar, mainFrame));
+ popupMenu.add(ActionManager.getActionInstance(ActionType.CustomizeCommandBar, mainFrame));
+ // Get the click location in the CommandBar's coordinate system.
+ // The location returned by the MouseEvent is in the source component (button) coordinate system. it's converted using SwingUtilities to the CommandBar's coordinate system.
+ Point clickLocation = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), CommandBar.this);
+ popupMenu.show(CommandBar.this, clickLocation.x, clickLocation.y);
+ popupMenu.setVisible(true);
+ }
+ }
+ };
+ addMouseListener(mouseAdapter);
actionIds = CommandBarAttributes.getActions();
alternateActionIds = CommandBarAttributes.getAlternateActions();
modifier = CommandBarAttributes.getModifier();
- addButtons();
+ addButtons(mouseAdapter);
- CommandBarAttributes.addCommandBarAttributesListener(this);
+ CommandBarAttributesListener commandBarAttributesListener = () -> {
+ actionIds = CommandBarAttributes.getActions();
+ alternateActionIds = CommandBarAttributes.getAlternateActions();
+ modifier = CommandBarAttributes.getModifier();
+ removeAll();
+ addButtons(mouseAdapter);
+ doLayout();
+ };
+ CommandBarAttributes.addCommandBarAttributesListener(commandBarAttributesListener);
+
+ mainFrame.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ CommandBarAttributes.removeCommandBarAttributesListener(commandBarAttributesListener);
+ }
+ });
}
/**
@@ -82,17 +137,19 @@ public CommandBar(MainFrame mainFrame) {
*
* actions array must be initialized before this function is called.
*/
- private void addButtons() {
+ private void addButtons(MouseListener mouseListener) {
setLayout(new GridLayout(0,actionIds.length));
- // Create buttons and add them to this command bar
- int nbButtons = actionIds.length;
- buttons = new CommandBarButton[nbButtons];
- for(int i=0; i
true
if the theme is modifiable, false
otherwise.
*/
- public boolean canModify() {return type == USER_THEME;}
+ public boolean canModify() {return type == ThemeType.USER_THEME;}
/**
* Returns the theme's type.
* @return the theme's type.
*/
- public int getType() {return type;}
+ public ThemeType getType() {return type;}
/**
* Returns the theme's name.
@@ -145,10 +143,10 @@ private void init(ThemeListener listener, int type, String name) {
@Override
public boolean setFont(int id, Font font) {
// Makes sure we're not trying to modify a non-user theme.
- if(type != USER_THEME)
+ if (type != ThemeType.USER_THEME)
throw new IllegalStateException("Trying to modify a non user theme.");
- if(super.setFont(id, font)) {
+ if (super.setFont(id, font)) {
// We're using getFont here to make sure that no event is propagated with a null value.
triggerFontEvent(new FontChangedEvent(this, id, getFont(id)));
return true;
@@ -170,10 +168,10 @@ public boolean setFont(int id, Font font) {
@Override
public boolean setColor(int id, Color color) {
// Makes sure we're not trying to modify a non-user theme.
- if(type != USER_THEME)
+ if (type != ThemeType.USER_THEME)
throw new IllegalStateException("Trying to modify a non user theme.");
- if(super.setColor(id, color)) {
+ if (super.setColor(id, color)) {
// We're using getColor here to make sure that no event is propagated with a null value.
triggerColorEvent(new ColorChangedEvent(this, id, getColor(id)));
return true;
@@ -189,11 +187,11 @@ public boolean setColor(int id, Color color) {
*
* @param type theme's type.
*/
- void setType(int type) {
+ void setType(ThemeType type) {
checkType(type);
this.type = type;
- if(type == USER_THEME)
+ if (type == ThemeType.USER_THEME)
setName(Translator.get("theme.custom_theme"));
}
@@ -207,8 +205,8 @@ void setType(int type) {
// - Misc. ---------------------------------------------------------------------------
// -----------------------------------------------------------------------------------
- static void checkType(int type) {
- if(type != USER_THEME && type != PREDEFINED_THEME && type != CUSTOM_THEME)
+ static void checkType(ThemeType type) {
+ if (type != ThemeType.USER_THEME && type != ThemeType.PREDEFINED_THEME && type != ThemeType.CUSTOM_THEME)
throw new IllegalArgumentException("Illegal theme type: " + type);
}
@@ -219,14 +217,13 @@ static void checkType(int type) {
public String toString() {return getName();}
private static void addThemeListener(ThemeListener listener) {listeners.put(listener, null);}
private static void removeThemeListener(ThemeListener listener) {listeners.remove(listener);}
+
private static void triggerFontEvent(FontChangedEvent event) {
- for(ThemeListener listener : listeners.keySet())
- listener.fontChanged(event);
+ listeners.keySet().forEach(listener -> listener.fontChanged(event));
}
private static void triggerColorEvent(ColorChangedEvent event) {
- for(ThemeListener listener : listeners.keySet())
- listener.colorChanged(event);
+ listeners.keySet().forEach(listener -> listener.colorChanged(event));
}
private class DefaultValuesListener implements ThemeListener {
@@ -237,12 +234,12 @@ public DefaultValuesListener() {}
public void setTheme(Theme theme) {this.theme = theme;}
public void colorChanged(ColorChangedEvent event) {
- if(!theme.isColorSet(event.getColorId()))
+ if (!theme.isColorSet(event.getColorId()))
Theme.triggerColorEvent(new ColorChangedEvent(theme, event.getColorId(), getColor(event.getColorId())));
}
public void fontChanged(FontChangedEvent event) {
- if(!theme.isFontSet(event.getFontId()))
+ if (!theme.isFontSet(event.getFontId()))
Theme.triggerFontEvent(new FontChangedEvent(theme, event.getFontId(), getFont(event.getFontId())));
}
}
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeCache.java b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeCache.java
index b92f9ea10e..1f0c650de2 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeCache.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeCache.java
@@ -48,6 +48,7 @@ public class ThemeCache implements ThemeListener {
public static final int SYMLINK = 3;
public static final int MARKED = 4;
public static final int PLAIN_FILE = 5;
+ public static final int READ_ONLY = 6;
// - Font definitions ------------------------------------------------------------
// -------------------------------------------------------------------------------
@@ -59,7 +60,7 @@ public class ThemeCache implements ThemeListener {
// - Initialisation --------------------------------------------------------------
// -------------------------------------------------------------------------------
static {
- foregroundColors = new Color[2][2][6];
+ foregroundColors = new Color[2][2][7];
backgroundColors = new Color[2][4];
// Active background colors.
@@ -76,6 +77,7 @@ public class ThemeCache implements ThemeListener {
// Normal foreground foregroundColors.
foregroundColors[ACTIVE][NORMAL][HIDDEN_FILE] = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_FOREGROUND_COLOR);
+ foregroundColors[ACTIVE][NORMAL][READ_ONLY] = ThemeManager.getCurrentColor(Theme.READ_ONLY_FOREGROUND_COLOR);
foregroundColors[ACTIVE][NORMAL][FOLDER] = ThemeManager.getCurrentColor(Theme.FOLDER_FOREGROUND_COLOR);
foregroundColors[ACTIVE][NORMAL][ARCHIVE] = ThemeManager.getCurrentColor(Theme.ARCHIVE_FOREGROUND_COLOR);
foregroundColors[ACTIVE][NORMAL][SYMLINK] = ThemeManager.getCurrentColor(Theme.SYMLINK_FOREGROUND_COLOR);
@@ -84,6 +86,7 @@ public class ThemeCache implements ThemeListener {
// Normal unfocused foreground foregroundColors.
foregroundColors[INACTIVE][NORMAL][HIDDEN_FILE] = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR);
+ foregroundColors[INACTIVE][NORMAL][READ_ONLY] = ThemeManager.getCurrentColor(Theme.READ_ONLY_INACTIVE_FOREGROUND_COLOR);
foregroundColors[INACTIVE][NORMAL][FOLDER] = ThemeManager.getCurrentColor(Theme.FOLDER_INACTIVE_FOREGROUND_COLOR);
foregroundColors[INACTIVE][NORMAL][ARCHIVE] = ThemeManager.getCurrentColor(Theme.ARCHIVE_INACTIVE_FOREGROUND_COLOR);
foregroundColors[INACTIVE][NORMAL][SYMLINK] = ThemeManager.getCurrentColor(Theme.SYMLINK_INACTIVE_FOREGROUND_COLOR);
@@ -92,6 +95,7 @@ public class ThemeCache implements ThemeListener {
// Selected foreground foregroundColors.
foregroundColors[ACTIVE][SELECTED][HIDDEN_FILE] = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_SELECTED_FOREGROUND_COLOR);
+ foregroundColors[ACTIVE][SELECTED][READ_ONLY] = ThemeManager.getCurrentColor(Theme.READ_ONLY_SELECTED_FOREGROUND_COLOR);
foregroundColors[ACTIVE][SELECTED][FOLDER] = ThemeManager.getCurrentColor(Theme.FOLDER_SELECTED_FOREGROUND_COLOR);
foregroundColors[ACTIVE][SELECTED][ARCHIVE] = ThemeManager.getCurrentColor(Theme.ARCHIVE_SELECTED_FOREGROUND_COLOR);
foregroundColors[ACTIVE][SELECTED][SYMLINK] = ThemeManager.getCurrentColor(Theme.SYMLINK_SELECTED_FOREGROUND_COLOR);
@@ -100,6 +104,7 @@ public class ThemeCache implements ThemeListener {
// Selected unfocused foreground foregroundColors.
foregroundColors[INACTIVE][SELECTED][HIDDEN_FILE] = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR);
+ foregroundColors[INACTIVE][SELECTED][READ_ONLY] = ThemeManager.getCurrentColor(Theme.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR);
foregroundColors[INACTIVE][SELECTED][FOLDER] = ThemeManager.getCurrentColor(Theme.FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR);
foregroundColors[INACTIVE][SELECTED][ARCHIVE] = ThemeManager.getCurrentColor(Theme.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR);
foregroundColors[INACTIVE][SELECTED][SYMLINK] = ThemeManager.getCurrentColor(Theme.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR);
@@ -198,6 +203,16 @@ public void colorChanged(ColorChangedEvent event) {
foregroundColors[ACTIVE][SELECTED][SYMLINK] = event.getColor();
break;
+ // Read-only
+ case Theme.READ_ONLY_FOREGROUND_COLOR:
+ foregroundColors[ACTIVE][NORMAL][READ_ONLY] = event.getColor();
+ break;
+
+ // Selected read-only
+ case Theme.READ_ONLY_SELECTED_FOREGROUND_COLOR:
+ foregroundColors[ACTIVE][SELECTED][READ_ONLY] = event.getColor();
+ break;
+
// Marked files.
case Theme.MARKED_FOREGROUND_COLOR:
foregroundColors[ACTIVE][NORMAL][MARKED] = event.getColor();
@@ -258,6 +273,16 @@ public void colorChanged(ColorChangedEvent event) {
foregroundColors[INACTIVE][SELECTED][SYMLINK] = event.getColor();
break;
+ // Read-only
+ case Theme.READ_ONLY_INACTIVE_FOREGROUND_COLOR:
+ foregroundColors[INACTIVE][NORMAL][READ_ONLY] = event.getColor();
+ break;
+
+ // Selected read-only
+ case Theme.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR:
+ foregroundColors[INACTIVE][SELECTED][READ_ONLY] = event.getColor();
+ break;
+
// Marked files.
case Theme.MARKED_INACTIVE_FOREGROUND_COLOR:
foregroundColors[INACTIVE][NORMAL][MARKED] = event.getColor();
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeData.java b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeData.java
index 1ca2c3f043..0b6e85ac41 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeData.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeData.java
@@ -17,12 +17,19 @@
package com.mucommander.ui.theme;
-import javax.swing.*;
-import java.awt.*;
+import java.awt.Color;
+import java.awt.Font;
import java.util.Hashtable;
import java.util.Map;
import java.util.WeakHashMap;
+import javax.swing.JComponent;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+
/**
* Base class for all things Theme.
* @@ -86,7 +93,7 @@ public class ThemeData { * by an instance of theme data by looping from 0 to this color. *
*/ - public static final int COLOR_COUNT = 68; + public static final int COLOR_COUNT = 72; @@ -676,7 +683,39 @@ public class ThemeData { */ public static final int QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR = 67; + /** + * Color used to paint read-only files text in the folder panels. + *
+ * This defaults to the current JTable
foreground color.
+ *
+ * This behaves in exactly the same fashion as {@link #READ_ONLY_FOREGROUND_COLOR}, and defaults + * to the same value. + *
+ */ + public static final int READ_ONLY_INACTIVE_FOREGROUND_COLOR = 69; + + /** + * Color used to paint selected read-only files text in the folder panels. + *
+ * This defaults to the current JTable
selection foreground color.
+ *
+ * This behaves in exactly the same fashion as {@link #READ_ONLY_SELECTED_FOREGROUND_COLOR}, and defaults + * to the same value. + *
+ */ + public static final int READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR = 71; // - Default fonts ------------------------------------------------------------------------------------------------- @@ -747,6 +786,8 @@ public class ThemeData { + + public static void registerDefaultColor(String name, DefaultColor color) { DEFAULT_COLORS.put(name, color); } @@ -917,20 +958,24 @@ public Color getColor(ThemeData data) { registerColor(FOLDER_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(ARCHIVE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(SYMLINK_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); + registerColor(READ_ONLY_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(FILE_INACTIVE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR, Color.GRAY); registerColor(FOLDER_INACTIVE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(ARCHIVE_INACTIVE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(SYMLINK_INACTIVE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); + registerColor(READ_ONLY_INACTIVE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(FILE_FOREGROUND_COLOR, DEFAULT_TABLE_FOREGROUND); registerColor(HIDDEN_FILE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(FOLDER_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(ARCHIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(SYMLINK_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); + registerColor(READ_ONLY_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); + registerColor(READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); registerColor(FILE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND); @@ -1045,17 +1090,14 @@ public ThemeData() { * @see #cloneData() */ public ThemeData cloneData(boolean freezeDefaults) { - ThemeData data; // New data. - int i; // Used to browse the fonts and colors. - - data = new ThemeData(); + ThemeData data = new ThemeData(); // Clones the theme's colors. - for(i = 0; i < COLOR_COUNT; i++) + for (int i = 0; i < COLOR_COUNT; i++) data.colors[i] = freezeDefaults ? getColor(i) : colors[i]; // Clones the theme's fonts. - for(i = 0; i < FONT_COUNT; i++) + for (int i = 0; i < FONT_COUNT; i++) data.fonts[i] = freezeDefaults ? getFont(i) : fonts[i]; return data; @@ -1088,14 +1130,12 @@ public ThemeData cloneData(boolean freezeDefaults) { * @param data data to import. */ public void importData(ThemeData data) { - int i; - // Imports the theme's colors. - for(i = 0; i < COLOR_COUNT; i++) + for (int i = 0; i < COLOR_COUNT; i++) setColor(i, data.colors[i]); // Imports the theme's fonts. - for(i = 0; i < FONT_COUNT; i++) + for (int i = 0; i < FONT_COUNT; i++) setFont(i, data.fonts[i]); } @@ -1122,9 +1162,7 @@ public void importData(ThemeData data) { * @returntrue
if the call actually changed the data, false
otherwise.
*/
public synchronized boolean setColor(int id, Color color) {
- boolean buffer; // Used to store the result of isColorDifferent.
-
- buffer = isColorDifferent(id, color);
+ boolean buffer = isColorDifferent(id, color);
colors[id] = color;
switch(id) {
case FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR:
@@ -1156,9 +1194,7 @@ public synchronized boolean setColor(int id, Color color) {
* @return true
if the call actually changed the data, false
otherwise.
*/
public synchronized boolean setFont(int id, Font font) {
- boolean buffer; // Used to store the result of isFontDifferent.
-
- buffer = isFontDifferent(id, font);
+ boolean buffer = isFontDifferent(id, font);
fonts[id] = font;
return buffer;
@@ -1269,16 +1305,14 @@ private static Font getDefaultFont(int id, ThemeData data) {
* @see #isColorDifferent(int,Color,boolean)
*/
public boolean isIdentical(ThemeData data, boolean ignoreDefaults) {
- int i;
-
// Compares the colors.
- for(i = 0; i < COLOR_COUNT; i++)
- if(isColorDifferent(i, data.colors[i] , ignoreDefaults))
+ for (int i = 0; i < COLOR_COUNT; i++)
+ if (isColorDifferent(i, data.colors[i] , ignoreDefaults))
return false;
// Compares the fonts.
- for(i = 0; i < FONT_COUNT; i++)
- if(isFontDifferent(i, data.fonts[i], ignoreDefaults))
+ for (int i = 0; i < FONT_COUNT; i++)
+ if (isFontDifferent(i, data.fonts[i], ignoreDefaults))
return false;
return true;
@@ -1327,12 +1361,12 @@ public synchronized boolean isFontDifferent(int id, Font font, boolean ignoreDef
// If the specified font is null, the only way for both fonts to be equal is for fonts[id]
// to be null as well.
- if(font == null)
+ if (font == null)
return fonts[id] != null;
// If fonts[id] is null and we're set to ignore defaults, both fonts are different.
// If we're set to use defaults, we must compare font and the default value for id.
- if(fonts[id] == null)
+ if (fonts[id] == null)
return ignoreDefaults || !getDefaultFont(id, this).equals(font);
// 'Standard' case: both fonts are set, compare them normally.
@@ -1372,12 +1406,12 @@ public synchronized boolean isColorDifferent(int id, Color color, boolean ignore
// If the specified color is null, the only way for both colors to be equal is for colors[id]
// to be null as well.
- if(color == null)
+ if (color == null)
return colors[id] != null;
// If colors[id] is null and we're set to ignore defaults, both colors are different.
// If we're set to use defaults, we must compare color and the default value for id.
- if(colors[id] == null)
+ if (colors[id] == null)
return ignoreDefaults || !getDefaultColor(id, this).equals(color);
// 'Standard' case: both colors are set, compare them normally.
@@ -1426,14 +1460,11 @@ public synchronized boolean isColorDifferent(int id, Color color, boolean ignore
* @param font new value for the font that changed.
*/
static void triggerFontEvent(int id, Font font) {
- FontChangedEvent event; // Event that will be dispatched.
-
// Creates the event.
- event = new FontChangedEvent(null, id, font);
+ FontChangedEvent event = new FontChangedEvent(null, id, font);
// Dispatches it.
- for(ThemeListener listener : listeners.keySet())
- listener.fontChanged(event);
+ listeners.keySet().forEach(listener -> listener.fontChanged(event));
}
/**
@@ -1442,14 +1473,11 @@ static void triggerFontEvent(int id, Font font) {
* @param color new value for the color that changed.
*/
static void triggerColorEvent(int id, Color color) {
- ColorChangedEvent event; // Event that will be dispatched.
-
// Creates the event.
- event = new ColorChangedEvent(null, id, color);
+ ColorChangedEvent event = new ColorChangedEvent(null, id, color);
// Dispatches it.
- for(ThemeListener listener : listeners.keySet())
- listener.colorChanged(event);
+ listeners.keySet().forEach(listener -> listener.colorChanged(event));
}
@@ -1462,7 +1490,7 @@ static void triggerColorEvent(int id, Color color) {
* @throws IllegalArgumentException if id
is not a legal color identifier.
*/
private static void checkColorIdentifier(int id) {
- if(id < 0 || id >= COLOR_COUNT)
+ if (id < 0 || id >= COLOR_COUNT)
throw new IllegalArgumentException("Illegal color identifier: " + id);
}
@@ -1472,7 +1500,7 @@ private static void checkColorIdentifier(int id) {
* @throws IllegalArgumentException if id
is not a legal font identifier.
*/
private static void checkFontIdentifier(int id) {
- if(id < 0 || id >= FONT_COUNT)
+ if (id < 0 || id >= FONT_COUNT)
throw new IllegalArgumentException("Illegal font identifier: " + id);
}
}
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeManager.java b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeManager.java
index 07f0ae999f..a2665cc007 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeManager.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeManager.java
@@ -50,6 +50,7 @@
import com.mucommander.io.backup.BackupInputStream;
import com.mucommander.io.backup.BackupOutputStream;
import com.mucommander.text.Translator;
+import com.mucommander.ui.theme.Theme.ThemeType;
/**
* Offers methods for accessing and modifying themes.
@@ -111,7 +112,7 @@ private ThemeManager() {}
*
*/
public static void loadCurrentTheme() {
- int type; // Current theme's type.
+ ThemeType type; // Current theme's type.
String name; // Current theme's name.
boolean wasUserThemeLoaded; // Whether we have tried loading the user theme or not.
@@ -120,7 +121,7 @@ public static void loadCurrentTheme() {
catch(Exception e) {type = getThemeTypeFromLabel(MuPreferences.DEFAULT_THEME_TYPE);}
// Loads the current theme name as defined in configuration.
- if(type != Theme.USER_THEME) {
+ if(type != ThemeType.USER_THEME) {
wasUserThemeLoaded = false;
name = MuConfigurations.getPreferences().getVariable(MuPreference.THEME_NAME, MuPreferences.DEFAULT_THEME_NAME);
}
@@ -136,7 +137,7 @@ public static void loadCurrentTheme() {
type = getThemeTypeFromLabel(MuPreferences.DEFAULT_THEME_TYPE);
name = MuPreferences.DEFAULT_THEME_NAME;
- if(type == Theme.USER_THEME)
+ if(type == ThemeType.USER_THEME)
wasUserThemeLoaded = true;
// If the default theme can be loaded, tries to load the user theme if we haven't done so yet.
@@ -144,7 +145,7 @@ public static void loadCurrentTheme() {
try {currentTheme = readTheme(type, name);}
catch(Exception e2) {
if(!wasUserThemeLoaded) {
- try {currentTheme = readTheme(Theme.USER_THEME, null);}
+ try {currentTheme = readTheme(ThemeType.USER_THEME, null);}
catch(Exception e3) {}
}
if(currentTheme == null) {
@@ -199,14 +200,14 @@ public static Vectortheme
is a predefined theme.
* @see #writeTheme(Theme)
*/
- public static void writeTheme(ThemeData data, int type, String name) throws IOException {
+ public static void writeTheme(ThemeData data, ThemeType type, String name) throws IOException {
OutputStream out;
out = null;
@@ -545,10 +546,10 @@ public static void writeTheme(ThemeData data, int type, String name) throws IOEx
* @param name name of the theme to export.
* @param out where to write the theme.
* @throws IOException if any I/O related error occurs.
- * @see #exportTheme(int,String,File)
+ * @see #exportTheme(ThemeType,String,File)
* @see #writeThemeData(ThemeData,OutputStream)
*/
- public static void exportTheme(int type, String name, OutputStream out) throws IOException {
+ public static void exportTheme(ThemeType type, String name, OutputStream out) throws IOException {
InputStream in; // Where to read the theme from.
in = null;
@@ -575,10 +576,10 @@ public static void exportTheme(int type, String name, OutputStream out) throws I
* @param name name of the theme to export.
* @param file where to write the theme.
* @throws IOException if any I/O related error occurs
- * @see #exportTheme(int,String,OutputStream)
+ * @see #exportTheme(ThemeType,String,OutputStream)
* @see #writeThemeData(ThemeData,File).
*/
- public static void exportTheme(int type, String name, File file) throws IOException {
+ public static void exportTheme(ThemeType type, String name, File file) throws IOException {
OutputStream out; // Where to write the data to.
out = null;
@@ -595,7 +596,7 @@ public static void exportTheme(int type, String name, File file) throws IOExcept
* Exports the specified theme to the specified output stream.
*
* This is a convenience method only and is strictly equivalent to calling
- * {@link #exportTheme(int,String,OutputStream) exportTheme(}theme.getType(), theme.getName(), out);
+ * {@link #exportTheme(ThemeType,String,OutputStream) exportTheme(}theme.getType(), theme.getName(), out);
*
type
is not a legal theme type.
*/
- private static InputStream getInputStream(int type, String name) throws IOException {
+ private static InputStream getInputStream(ThemeType type, String name) throws IOException {
switch(type) {
// User theme.
- case Theme.USER_THEME:
+ case USER_THEME:
return getUserThemeInputStream();
// Predefined theme.
- case Theme.PREDEFINED_THEME:
+ case PREDEFINED_THEME:
return getPredefinedThemeInputStream(name);
// Custom theme.
- case Theme.CUSTOM_THEME:
+ case CUSTOM_THEME:
return getCustomThemeInputStream(name);
}
@@ -765,7 +766,7 @@ private static InputStream getInputStream(int type, String name) throws IOExcept
* @param name name of the theme to retrieve.
* @return the requested theme.
*/
- public static Theme readTheme(int type, String name) throws Exception {
+ public static Theme readTheme(ThemeType type, String name) throws Exception {
ThemeData data; // Buffer for the theme data.
InputStream in; // Where to read the theme from.
@@ -830,23 +831,23 @@ public static ThemeData readThemeData(File file) throws Exception {
// - Current theme access ------------------------------------------------------------
// -----------------------------------------------------------------------------------
- private static void setConfigurationTheme(int type, String name) {
+ private static void setConfigurationTheme(ThemeType type, String name) {
// Sets configuration depending on the new theme's type.
switch(type) {
// User defined theme.
- case Theme.USER_THEME:
+ case USER_THEME:
MuConfigurations.getPreferences().setVariable(MuPreference.THEME_TYPE, MuPreferences.THEME_USER);
MuConfigurations.getPreferences().setVariable(MuPreference.THEME_NAME, null);
break;
// Predefined themes.
- case Theme.PREDEFINED_THEME:
+ case PREDEFINED_THEME:
MuConfigurations.getPreferences().setVariable(MuPreference.THEME_TYPE, MuPreferences.THEME_PREDEFINED);
MuConfigurations.getPreferences().setVariable(MuPreference.THEME_NAME, name);
break;
// Custom themes.
- case Theme.CUSTOM_THEME:
+ case CUSTOM_THEME:
MuConfigurations.getPreferences().setVariable(MuPreference.THEME_TYPE, MuPreferences.THEME_CUSTOM);
MuConfigurations.getPreferences().setVariable(MuPreference.THEME_NAME, name);
break;
@@ -874,7 +875,7 @@ public static void saveCurrentTheme() throws IOException {
return;
// Saves the user theme if it's the current one.
- if(currentTheme.getType() == Theme.USER_THEME && wasUserThemeModified) {
+ if(currentTheme.getType() == ThemeType.USER_THEME && wasUserThemeModified) {
writeTheme(currentTheme);
wasUserThemeModified = false;
}
@@ -917,14 +918,14 @@ public synchronized static void setCurrentTheme(Theme theme) {
public synchronized static Theme overwriteUserTheme(ThemeData themeData) throws IOException {
// If the current theme is the user one, we just need to import the new data.
- if(currentTheme.getType() == Theme.USER_THEME) {
+ if(currentTheme.getType() == ThemeType.USER_THEME) {
currentTheme.importData(themeData);
writeTheme(currentTheme);
return currentTheme;
}
else {
- writeTheme(themeData, Theme.USER_THEME, null);
+ writeTheme(themeData, ThemeType.USER_THEME, null);
return new Theme(listener, themeData);
}
}
@@ -938,7 +939,7 @@ public synchronized static Theme overwriteUserTheme(ThemeData themeData) throws
*/
public synchronized static boolean willOverwriteUserTheme(int fontId, Font font) {
if(currentTheme.isFontDifferent(fontId, font))
- return currentTheme.getType() != Theme.USER_THEME;
+ return currentTheme.getType() != ThemeType.USER_THEME;
return false;
}
@@ -951,7 +952,7 @@ public synchronized static boolean willOverwriteUserTheme(int fontId, Font font)
*/
public synchronized static boolean willOverwriteUserTheme(int colorId, Color color) {
if(currentTheme.isColorDifferent(colorId, color))
- return currentTheme.getType() != Theme.USER_THEME;
+ return currentTheme.getType() != ThemeType.USER_THEME;
return false;
}
@@ -970,8 +971,8 @@ public synchronized static boolean setCurrentFont(int id, Font font) {
// Only updates if necessary.
if(currentTheme.isFontDifferent(id, font)) {
// Checks whether we need to overwrite the user theme to perform this action.
- if(currentTheme.getType() != Theme.USER_THEME) {
- currentTheme.setType(Theme.USER_THEME);
+ if(currentTheme.getType() != ThemeType.USER_THEME) {
+ currentTheme.setType(ThemeType.USER_THEME);
setConfigurationTheme(currentTheme);
}
@@ -996,8 +997,8 @@ public synchronized static boolean setCurrentColor(int id, Color color) {
// Only updates if necessary.
if(currentTheme.isColorDifferent(id, color)) {
// Checks whether we need to overwrite the user theme to perform this action.
- if(currentTheme.getType() != Theme.USER_THEME) {
- currentTheme.setType(Theme.USER_THEME);
+ if(currentTheme.getType() != ThemeType.USER_THEME) {
+ currentTheme.setType(ThemeType.USER_THEME);
setConfigurationTheme(currentTheme);
}
@@ -1015,10 +1016,10 @@ public synchronized static boolean setCurrentColor(int id, Color color) {
*/
public static boolean isCurrentTheme(Theme theme) {return theme == currentTheme;}
- private static boolean isCurrentTheme(int type, String name) {
+ private static boolean isCurrentTheme(ThemeType type, String name) {
if(type != currentTheme.getType())
return false;
- if(type == Theme.USER_THEME)
+ if(type == ThemeType.USER_THEME)
return true;
return name.equals(currentTheme.getName());
}
@@ -1118,15 +1119,15 @@ private static void triggerColorEvent(ColorChangedEvent event) {
/**
* Returns a valid type identifier from the specified configuration type definition.
* @param label label of the theme type as defined in {@link MuPreferences}.
- * @return a valid theme type identifier.
+ * @return a valid theme type identifier.
*/
- private static int getThemeTypeFromLabel(String label) {
+ private static ThemeType getThemeTypeFromLabel(String label) {
if(label.equals(MuPreferences.THEME_USER))
- return Theme.USER_THEME;
+ return ThemeType.USER_THEME;
else if(label.equals(MuPreferences.THEME_PREDEFINED))
- return Theme.PREDEFINED_THEME;
+ return ThemeType.PREDEFINED_THEME;
else if(label.equals(MuPreferences.THEME_CUSTOM))
- return Theme.CUSTOM_THEME;
+ return ThemeType.CUSTOM_THEME;
throw new IllegalStateException("Unknown theme type: " + label);
}
@@ -1143,7 +1144,7 @@ private static String getThemeName(AbstractFile themeFile) {
*/
private static class CurrentThemeListener implements ThemeListener {
public void fontChanged(FontChangedEvent event) {
- if(event.getSource().getType() == Theme.USER_THEME)
+ if(event.getSource().getType() == ThemeType.USER_THEME)
wasUserThemeModified = true;
if(event.getSource() == currentTheme)
@@ -1151,7 +1152,7 @@ public void fontChanged(FontChangedEvent event) {
}
public void colorChanged(ColorChangedEvent event) {
- if(event.getSource().getType() == Theme.USER_THEME)
+ if(event.getSource().getType() == ThemeType.USER_THEME)
wasUserThemeModified = true;
if(event.getSource() == currentTheme)
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeReader.java b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeReader.java
index 4336f63ba7..5a69739028 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeReader.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeReader.java
@@ -102,6 +102,9 @@ class ThemeReader extends DefaultHandler implements ThemeXmlConstants {
private static final int STATE_QUICK_LIST_ITEM = 40;
private static final int STATE_QUICK_LIST_ITEM_NORMAL = 41;
private static final int STATE_QUICK_LIST_ITEM_SELECTED = 42;
+ private static final int STATE_READ_ONLY = 43;
+ private static final int STATE_READ_ONLY_NORMAL = 44;
+ private static final int STATE_READ_ONLY_SELECTED = 45;
// - Instance variables --------------------------------------------------------------
@@ -227,6 +230,12 @@ else if(qName.equals(ELEMENT_SYMLINK)) {
state = STATE_SYMLINK;
}
+ else if(qName.equals(ELEMENT_READ_ONLY)) {
+ if(state != STATE_TABLE)
+ traceIllegalDeclaration(qName);
+ state = STATE_READ_ONLY;
+ }
+
else if(qName.equals(ELEMENT_MARKED)) {
if(state != STATE_TABLE)
traceIllegalDeclaration(qName);
@@ -279,6 +288,8 @@ else if(state == STATE_ARCHIVE)
state = STATE_ARCHIVE_NORMAL;
else if(state == STATE_SYMLINK)
state = STATE_SYMLINK_NORMAL;
+ else if(state == STATE_READ_ONLY)
+ state = STATE_READ_ONLY_NORMAL;
else if(state == STATE_MARKED)
state = STATE_MARKED_NORMAL;
else if(state == STATE_FILE)
@@ -309,6 +320,8 @@ else if(state == STATE_ARCHIVE)
state = STATE_ARCHIVE_SELECTED;
else if(state == STATE_SYMLINK)
state = STATE_SYMLINK_SELECTED;
+ else if(state == STATE_READ_ONLY)
+ state = STATE_READ_ONLY_SELECTED;
else if(state == STATE_MARKED)
state = STATE_MARKED_SELECTED;
else if(state == STATE_FILE)
@@ -383,6 +396,8 @@ else if(state == STATE_ARCHIVE_NORMAL)
template.setColor(ThemeData.ARCHIVE_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_SYMLINK_NORMAL)
template.setColor(ThemeData.SYMLINK_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
+ else if(state == STATE_READ_ONLY_NORMAL)
+ template.setColor(ThemeData.READ_ONLY_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_HIDDEN_NORMAL)
template.setColor(ThemeData.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_MARKED_NORMAL)
@@ -395,6 +410,8 @@ else if(state == STATE_ARCHIVE_SELECTED)
template.setColor(ThemeData.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_SYMLINK_SELECTED)
template.setColor(ThemeData.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
+ else if(state == STATE_READ_ONLY_SELECTED)
+ template.setColor(ThemeData.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_HIDDEN_SELECTED)
template.setColor(ThemeData.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_MARKED_SELECTED)
@@ -549,6 +566,11 @@ else if(state == STATE_SYMLINK_NORMAL)
else if(state == STATE_SYMLINK_SELECTED)
template.setColor(ThemeData.SYMLINK_SELECTED_FOREGROUND_COLOR, createColor(attributes));
+ else if(state == STATE_READ_ONLY_NORMAL)
+ template.setColor(ThemeData.READ_ONLY_FOREGROUND_COLOR, createColor(attributes));
+ else if(state == STATE_READ_ONLY_SELECTED)
+ template.setColor(ThemeData.READ_ONLY_SELECTED_FOREGROUND_COLOR, createColor(attributes));
+
else if(state == STATE_MARKED_NORMAL)
template.setColor(ThemeData.MARKED_FOREGROUND_COLOR, createColor(attributes));
else if(state == STATE_MARKED_SELECTED)
@@ -638,6 +660,9 @@ else if(qName.equals(ELEMENT_ARCHIVE))
else if(qName.equals(ELEMENT_SYMLINK))
state = STATE_TABLE;
+ else if(qName.equals(ELEMENT_READ_ONLY))
+ state = STATE_TABLE;
+
else if(qName.equals(ELEMENT_MARKED))
state = STATE_TABLE;
@@ -694,6 +719,8 @@ else if(state == STATE_ARCHIVE_NORMAL)
state = STATE_ARCHIVE;
else if(state == STATE_SYMLINK_NORMAL)
state = STATE_SYMLINK;
+ else if(state == STATE_READ_ONLY_NORMAL)
+ state = STATE_READ_ONLY;
else if(state == STATE_MARKED_NORMAL)
state = STATE_MARKED;
else if(state == STATE_FILE_NORMAL)
@@ -722,6 +749,8 @@ else if(state == STATE_ARCHIVE_SELECTED)
state = STATE_ARCHIVE;
else if(state == STATE_SYMLINK_SELECTED)
state = STATE_SYMLINK;
+ else if(state == STATE_READ_ONLY_SELECTED)
+ state = STATE_READ_ONLY;
else if(state == STATE_MARKED_SELECTED)
state = STATE_MARKED;
else if(state == STATE_FILE_SELECTED)
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeWriter.java b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeWriter.java
index 07b5ff6774..1ddcbd38aa 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeWriter.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeWriter.java
@@ -188,6 +188,25 @@ public static void write(ThemeData theme, OutputStream stream) throws IOExceptio
out.endElement(ELEMENT_SELECTED);
out.endElement(ELEMENT_SYMLINK);
+ // Read-only files
+ out.startElement(ELEMENT_READ_ONLY);
+ out.println();
+ out.startElement(ELEMENT_NORMAL);
+ out.println();
+ if(theme.isColorSet(Theme.READ_ONLY_INACTIVE_FOREGROUND_COLOR))
+ out.writeStandAloneElement(ELEMENT_INACTIVE_FOREGROUND, getColorAttributes(theme.getColor(Theme.READ_ONLY_INACTIVE_FOREGROUND_COLOR)));
+ if(theme.isColorSet(Theme.READ_ONLY_FOREGROUND_COLOR))
+ out.writeStandAloneElement(ELEMENT_FOREGROUND, getColorAttributes(theme.getColor(Theme.READ_ONLY_FOREGROUND_COLOR)));
+ out.endElement(ELEMENT_NORMAL);
+ out.startElement(ELEMENT_SELECTED);
+ out.println();
+ if(theme.isColorSet(Theme.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR))
+ out.writeStandAloneElement(ELEMENT_INACTIVE_FOREGROUND, getColorAttributes(theme.getColor(Theme.READ_ONLY_INACTIVE_SELECTED_FOREGROUND_COLOR)));
+ if(theme.isColorSet(Theme.READ_ONLY_SELECTED_FOREGROUND_COLOR))
+ out.writeStandAloneElement(ELEMENT_FOREGROUND, getColorAttributes(theme.getColor(Theme.READ_ONLY_SELECTED_FOREGROUND_COLOR)));
+ out.endElement(ELEMENT_SELECTED);
+ out.endElement(ELEMENT_READ_ONLY);
+
// Marked files.
out.startElement(ELEMENT_MARKED);
out.println();
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeXmlConstants.java b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeXmlConstants.java
index ccb60ac3ef..4317c87651 100644
--- a/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeXmlConstants.java
+++ b/mucommander-core/src/main/java/com/mucommander/ui/theme/ThemeXmlConstants.java
@@ -42,6 +42,8 @@ interface ThemeXmlConstants {
public static final String ELEMENT_QUICK_LIST = "quick_list";
+ // File table element
+ public static final String ELEMENT_READ_ONLY = "read_only";
// - Status element ------------------------------------------------------------------
// -----------------------------------------------------------------------------------
diff --git a/mucommander-core/src/main/java/com/mucommander/ui/viewer/EditorPreferences.java b/mucommander-core/src/main/java/com/mucommander/ui/viewer/EditorPreferences.java
new file mode 100644
index 0000000000..5e24baeafb
--- /dev/null
+++ b/mucommander-core/src/main/java/com/mucommander/ui/viewer/EditorPreferences.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of muCommander, http://www.mucommander.com
+ *
+ * muCommander 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.
+ *
+ * muCommander 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see - * Warning: the file viewer/editor API may soon receive a major overhaul. - *
- * * @author Maxence Bernard, Arik Hadas */ public class FileEditorPresenter extends FilePresenter implements EditorPresenter { - private FileEditor fileEditor = null; private final JMenuBar menuBar; private final JMenu editorMenu; private final ButtonGroup editorsButtonGroup; private final List- * Warning: the file viewer/editor API may soon receive a major overhaul. - *
- * * @author Maxence Bernard, Arik Hadas */ public class FileViewerPresenter extends FilePresenter implements ViewerPresenter { - private FileViewer fileViewer = null; private final JMenuBar menuBar; private final JMenu viewerMenu; private final ButtonGroup viewersButtonGroup; private final Listtrue
if the file specified by this object
+ * exists; false
otherwise.
+ */
+ public boolean exists() {
+ return file.exists();
+ }
+
+
+ /**
+ * Tests if the application can write to this file.
+ *
+ * @return true
if the application is allowed to
+ * write to a file whose name is specified by this
+ * object; false
otherwise.
+ */
+ public boolean canWrite() {
+ return file.canWrite();
+ }
+
+
+ /**
+ * Tests if the application can read from the specified file.
+ *
+ * @return true
if the file specified by this
+ * object exists and the application can read the file;
+ * false
otherwise.
+ */
+ public boolean canRead() {
+ return file.canRead();
+ }
+
+ /**
+ * Tests if the file represented by this
+ * object is a "normal" file.
+ *
+ * A file is "normal" if it is not a directory and, in
+ * addition, satisfies other system-dependent criteria. Any
+ * non-directory file created by a Java application is
+ * guaranteed to be a normal file.
+ *
+ * @return true
if the file specified by this
+ * XFile
object exists and is a "normal"
+ * file; false
otherwise.
+ */
+ public boolean isFile() {
+ return file.isFile();
+ }
+
+
+ /**
+ * Tests if the file represented by this XFileAccessor
+ * object is a directory.
+ *
+ * @return true
if this XFileAccessor object
+ * exists and is a directory; false
+ * otherwise.
+ */
+ public boolean isDirectory() {
+ return file.isDirectory();
+ }
+
+
+ /**
+ * Returns the time that the file represented by this
+ * XFile
object was last modified.
+ *
+ * The return value is system dependent and should only be
+ * used to compare with other values returned by last modified.
+ * It should not be interpreted as an absolute time.
+ *
+ * @return the time the file specified by this object was last
+ * modified, or 0L
if the specified file
+ * does not exist.
+ */
+ public long lastModified() {
+ return file.lastModified();
+ }
+
+
+ /**
+ * Returns the length of the file represented by this
+ * XFileAccessor object.
+ *
+ * @return the length, in bytes, of the file specified by
+ * this object, or 0L
if the specified
+ * file does not exist.
+ */
+ public long length() {
+ return file.length();
+ }
+
+
+ /**
+ * Creates a file whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the file could be created;
+ * false
otherwise.
+ */
+ public boolean mkfile() {
+ try {
+
+ // This little maneuver creates a zero length file
+
+ FileOutputStream of = new FileOutputStream(file);
+ of.getFD().sync();
+ of.close();
+ return true;
+
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Creates a directory whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the directory could be created;
+ * false
otherwise.
+ */
+ public boolean mkdir() {
+ return file.mkdir();
+ }
+
+
+ /**
+ * Renames the file specified by this XFileAccessor object to
+ * have the pathname given by the XFileAccessor object argument.
+ *
+ * @param dest the new filename.
+ * @return true
if the renaming succeeds;
+ * false
otherwise.
+ */
+ public boolean renameTo(XFile dest) {
+ return file.renameTo(new File(dest.getPath()));
+ }
+
+
+ /**
+ * Returns a list of the files in the directory specified by
+ * this XFileAccessor object.
+ *
+ * @return an array of file names in the specified directory.
+ * This list does not include the current directory or
+ * the parent directory (".
" and
+ * "..
" on Unix systems).
+ */
+ public String[] list() {
+ return file.list();
+ }
+
+
+ /**
+ * Deletes the file specified by this object. If the target
+ * file to be deleted is a directory, it must be empty for
+ * deletion to succeed.
+ *
+ * @return true
if the file is successfully deleted;
+ * false
otherwise.
+ */
+ public boolean delete() {
+ return file.delete();
+ }
+
+
+ /**
+ * Reads a subarray as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @return number of bytes read; -1 if EOF
+ * @exception IOException If an I/O error has occurred.
+ */
+ public int read(byte b[], int off, int len, long foff)
+ throws IOException {
+
+ if (raf == null)
+ raf = new RandomAccessFile(file, readOnly ? "r" : "rw");
+
+ if (foff != fp) {
+ fp = foff;
+ raf.seek(foff);
+ }
+
+ int c = raf.read(b, off, len);
+ if (c > 0)
+ fp += c;
+
+ return (c);
+ }
+
+
+ /**
+ * Writes a sub array as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @exception IOException If an I/O error has occurred.
+ */
+ public void write(byte b[], int off, int len, long foff)
+ throws IOException {
+
+ if (raf == null)
+ raf = new RandomAccessFile(file, readOnly ? "r" : "rw");
+
+ if (foff != fp) {
+ fp = foff;
+ raf.seek(foff);
+ }
+
+ raf.write(b, off, len);
+
+ fp += len;
+ }
+
+
+ /**
+ * Forces any buffered output bytes to be written out.
+ *
+ * Since RandomAccessFile has no corresponding method + * this does nothing. + * + * @exception IOException if an I/O error occurs. + */ + public void flush() throws IOException { + } + + + /** + * Close the file + * + * @exception IOException If an I/O error has occurred. + */ + public void close() throws IOException { + + if (raf != null) + raf.close(); + } + + + /** + * Returns a string representation of this object. + * + * @return a string giving the pathname of this object. + */ + public String toString() { + return file.toString(); + } +} diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/C018FE95.java b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/C018FE95.java new file mode 100644 index 0000000000..bb4b3bccf8 --- /dev/null +++ b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/C018FE95.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 1999, 2007 Sun Microsystems, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS + * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE + * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE + * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE + * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, + * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED + * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed,licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.sun.gssapi; + +import java.io.InputStream; +import java.io.OutputStream; + + +/** + * An object of this class implements the functionality of a GSSContext + * for a specific mechanism. + * A C018FE95 object can be thought of having 3 states: + * -before initialization + * -during initialization with its peer + * -after it is established + *
+ * The context options can only be requested in state 1. In state 3, + * the per message operations are available to the callers. The get + * methods for the context options will return the requested options + * while in state 1 and 2, and the established values in state 3. + * Some mechanisms may allow the access to the per-message operations + * and the context flags before the context is fully established. The + * isProtReady method is used to indicate that these services are + * available. + */ + +public interface C018FE95 { + + /** + * Sets the mechanism options to be used during context + * creation on the initiator's side. This is used to + * initialize a new C018FE95 object. + * + * @param myCred the principal's credentials; may be null + * @param targName the context peer + * @param desLifetime the requested lifetime; 0 indicates use + * default + * @param mechOptions ORed GSSContext options + * @exception GSSException may be thrown + */ + public void _S235D9C1 (GSSCredSpi myCred, GSSNameSpi targName, + int desLifetime, int ctxtOptions) throws GSSException; + + + /** + * Sets the mechanism options to be used during context + * creation on the acceptor's side. This is used to initialize + * a new C018FE95 object. + * + * @param myCred the principal's credentials; may be null + * @exception GSSException may be thrown + */ + public void _S90010CC (GSSCredSpi myCred) throws GSSException; + + + /** + * Sets the channel bindings to be used during context + * establishment. This method is only called if the application + * wishes to use channel bindings with this context. + * + * @param chb channel bindings to be set + * @exception GSSException may be thrown + */ + public void _S9B00AB2 (ChannelBinding chb) throws GSSException; + + + /** + * Retrieves the mechanism options. + * + * @return int GSSContext options ORed together + */ + public int _S00027C3 (); + + + /** + * Inquire the remaining lifetime. + * + * @return the lifetime in seconds. May return reserved + * value GSSContext.INDEFINITE for an indefinite lifetime. + */ + public int _S4080EED (); + + + /** + * Returns the mechanism oid. + * + * @return the Oid for this context + */ + public Oid _S0200735 (); + + + /** + * Returns the context initiator name. + * + * @return initiator name + * @exception GSSException may be thrown + */ + public GSSNameSpi _S000EEFF () throws GSSException; + + + /** + * Returns the context acceptor name. + * + * @return context acceptor(target) name + * @exception GSSException may be thrown + */ + public GSSNameSpi _S011CEF9 () throws GSSException; + + + /** + * Returns the delegated credential for the context. This + * is an optional feature of contexts which not all + * mechanisms will support. A context can be requested to + * support credential delegation by using the CRED_DELEG. + * This is only valid on the acceptor side of the context. + * @return GSSCredSpi object for the delegated credential + * @exception GSSException may be thrown + * @see GSSContext#getDelegCredState + */ + public GSSCredSpi _S0293FFA () throws GSSException; + + + /** + * Tests if this is the initiator side of the context. + * + * @return boolean indicating if this is initiator (true) + * or target (false) + */ + public boolean _S123049E (); + + + /** + * Tests if the context can be used for per-message service. + * Context may allow the calls to the per-message service + * functions before being fully established. + * + * @return boolean indicating if per-message methods can + * be called. + */ + public boolean _S1116FAA (); + + + /** + * Initiator context establishment call. This method may be + * required to be called several times. A CONTINUE_NEEDED return + * call indicates that more calls are needed after the next token + * is received from the peer. + * + * @param is contains the token received from the peer. On the + * first call it will be ignored. + * @param os to which any tokens required to be sent to the peer + * will be written. It is responsibility of the caller + * to send the token to its peer for processing. + * @return integer indicating if more calls are needed. Possible + * values are COMPLETE and CONTINUE_NEEDED. + * @exception GSSException may be thrown + */ + public int _S0E039DB (InputStream is, OutputStream os) + throws GSSException; + + + /** + * Acceptor's context establishment call. This method may be + * required to be called several times. A CONTINUE_NEEDED return + * call indicates that more calls are needed after the next token + * is received from the peer. + * + * @param is contains the token received from the peer. + * @param os to which any tokens required to be sent to the peer + * will be written. It is responsibility of the caller + * to send the token to its peer for processing. + * @return integer indicating if more calls are needed. Possible + * values are COMPLETE and CONTINUE_NEEDED. + * @exception GSSException may be thrown + */ + public int _S80A2F2C (InputStream is, OutputStream os) + throws GSSException; + + + /** + * Queries the context for largest data size to accomodate + * the specified protection and for the token to remain less then + * maxTokSize. + * + * @param qop the quality of protection that the context will be + * asked to provide. + * @param confReq a flag indicating whether confidentiality will be + * requested or not + * @param outputSize the maximum size of the output token + * @return the maximum size for the input message that can be + * provided to the wrap() method in order to guarantee that these + * requirements are met. + * @exception GSSException may be thrown + */ + public int _S808028B (int qop, boolean confReq, int maxTokSize) + throws GSSException; + + + /** + * Provides per-message token encapsulation. + * + * @param is the user-provided message to be protected + * @param os the token to be sent to the peer. It includes + * the message from is with the requested protection. + * @param msgPro on input it contains the requested qop and + * confidentiality state, on output, the applied values + * @exception GSSException may be thrown + * @see MessageInfo + * @see unwrap + */ + public void _S1309AFD (InputStream is, OutputStream os, MessageProp msgProp) + throws GSSException; + + + /** + * Retrieves the message token previously encapsulated in the wrap + * call. + * + * @param is the token from the peer + * @param os unprotected message data + * @param msgProp will contain the applied qop and confidentiality + * of the input token and any informatory status values + * @exception GSSException may be thrown + * @see MessageInfo + * @see wrap + */ + public void _S1576D09 (InputStream is, OutputStream os, + MessageProp msgProp) throws GSSException; + + + /** + * Applies per-message integrity services. + * + * @param is the user-provided message + * @param os the token to be sent to the peer along with the + * message token. The message token is not encapsulated. + * @param msgProp on input the desired QOP and output the applied QOP + * @exception GSSException + */ + public void _S1513DBA (InputStream is, OutputStream os, + MessageProp msgProp) + throws GSSException; + + + /** + * Checks the integrity of the supplied tokens. + * This token was previously generated by getMIC. + * + * @param is token generated by getMIC + * @param msgStr the message to check integrity for + * @param msgProp will contain the applied QOP and confidentiality + * states of the token as well as any informatory status codes + * @exception GSSException may be thrown + */ + public void _S00256CF (InputStream is, InputStream msgStr, + MessageProp mProp) throws GSSException; + + + /** + * Produces a token representing this context. After this call + * the context will no longer be usable until an import is + * performed on the returned token. + * + * @return exported context token + * @exception GSSException may be thrown + */ + public byte []_S725B2DA () throws GSSException; + + + /** + * Imports a previously exported context. This will be called + * for newly created objects. + * + * @param is the previously exported token + * @exception GSSException may be thrown + * @see export + */ + public void _S0AC8F9E (byte []token) throws GSSException; + + + /** + * Releases context resources and terminates the + * context between 2 peer. + * + * @exception GSSException may be thrown + */ + public void _S020B957 () throws GSSException; +} + + diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/ChannelBinding.java b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/ChannelBinding.java new file mode 100644 index 0000000000..b30c754dad --- /dev/null +++ b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/ChannelBinding.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 1999, 2007 Sun Microsystems, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS + * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE + * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE + * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE + * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, + * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED + * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed,licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.sun.gssapi; + +import java.net.InetAddress; + +/** + * The JGSS accommodates the concept of caller-provided channel + * binding information. Channel bindings are used to strengthen + * the quality with which peer entity authentication is provided + * during context establishment. They enable the JGSS callers to + * bind the establishment of the a security context to relevant + * characteristics like addresses or to application specific data. + *
+ * The caller initiating the security context must determine the + * appropriate channel binding values to set in the GSSContext + * object. The acceptor must provide identical binding in order + * to validate that received tokens possess correct + * channel-related characteristics. + *
+ * Use of channel bindings is optional in JGSS. Since channel- + * binding information may be transmitted in context establishment + * tokens, applications should therefore not use confidential data + * as channel-binding components. + * @see GSSContext#setChannelBinding + * @see java.net.InetAddress + */ + +public class ChannelBinding { + + private InetAddress m_initiator; + private InetAddress m_acceptor; + + private byte[] m_appData; + + /** + * Construct a channel bindings object that contains all the user + * specified tags. + * + * @param initAddr the address of the context initiator + * @param acceptAddr address of the context acceptor + * @param appData a byte array of application data to be used as + * part of the channel-binding + */ + public ChannelBinding(InetAddress initAddr, InetAddress acceptAddr, + byte[] appData) { + + m_initiator = initAddr; + m_acceptor = acceptAddr; + + if (appData != null) { + m_appData = new byte[appData.length]; + java.lang.System.arraycopy(appData, 0, m_appData, 0, + m_appData.length); + } + } + + + /** + * Construct a channel bindings object without any addressing + * information. + * + * @param appData a byte array of application data to be used as + * part of the channel-binding + */ + public ChannelBinding(byte[] appData) { + + m_initiator = null; + m_acceptor = null; + m_appData = new byte[appData.length]; + java.lang.System.arraycopy(appData, 0, m_appData, 0, + m_appData.length); + } + + /** + * Get the initiator's address for this channel binding. + * + * @return the initiator's address. null if no address + * information is contained + */ + public InetAddress getInitiatorAddress() { + + return m_initiator; + } + + /** + * Get the acceptor's address for this channel binding. + * + * @return the acceptor's address. null if no address + * information is contained + */ + public InetAddress getAcceptorAddress() { + + return m_acceptor; + } + + + /** + * Get the application specified data for this channel binding. + * The byte array is not copied. + * + * @return byte[] the application data that comprises this + * channel-binding + */ + public byte[] getApplicationData() { + + return m_appData; + } + + + /** + * Compares two instances of ChannelBinding + * + * @return true if objects are the same + * @overrides java.lang.Object#equals + */ + public boolean equals(Object obj) { + + if (! (obj instanceof ChannelBinding)) + return false; + + ChannelBinding cb = (ChannelBinding)obj; + + //check for application data being null in one but not the other + if ((getApplicationData() == null && + cb.getApplicationData() != null) || + (getApplicationData() != null && + cb.getApplicationData() == null)) + return (false); + + return (this.m_initiator.equals(cb.getInitiatorAddress()) && + this.m_acceptor.equals(cb.getAcceptorAddress()) && + (this.getApplicationData() == null || + this.m_appData.equals(cb.getApplicationData()))); + } +} diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/DERParser.java b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/DERParser.java new file mode 100644 index 0000000000..8f1f4092a6 --- /dev/null +++ b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/DERParser.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 1999, 2007 Sun Microsystems, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS + * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE + * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE + * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE + * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, + * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED + * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed,licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.sun.gssapi; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.util.Vector; + +/** + * This is a package private class used to decode/encode ASN.1 DER + * oids. + */ +class DERParser { + + /** + * Returns the DER encoded length from the InputStream. + */ + static int readLength(InputStream is) throws GSSException { + + int length, tmp; + + //get the length of Oid - check if short form + try { + if (((tmp = is.read()) & 0x080) == 0) + length = tmp; + else { + //must be long form + tmp &= 0x7f; + for (length = 0; tmp > 0; tmp--) { + length <<= 8; + length += (0xff & is.read()); + } + } + } catch (IOException e) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + + return (length); + } + + + /** + * Decodes a DER encoding of an Oid object into vector components. + */ + static Vector decodeOid(InputStream is) throws GSSException { + + //check the tag first + try { + if (is.read() != 0x06) + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } catch (IOException e) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + + return (decodeOidOctets(is, readLength(is))); + } + + + /** + * Decodes the specified number of oid octets. + * Returns a vector of integer components. + */ + + static Vector decodeOidOctets(InputStream is, int numOfOctets) + throws GSSException { + + Vector v = new Vector(9, 3); + + //first octet is combination of first two numbers + try { + int comp, tmp = is.read(); + if (tmp < 40) + comp = 0; + else if (tmp < 80) + comp = 1; + else + comp = 2; + + v.addElement(new Integer(comp)); + v.addElement(new Integer(tmp - (40 * comp))); + + //get the rest of the octets + for (int i = 1; i < numOfOctets; i++) { + comp = 0; + + //assume that at most 4 octets make up each component + for (int j=0; j < 4; j++) { + comp <<= 7; + tmp = is.read(); + comp |= (tmp & 0x7f); + if ((tmp & 0x80) == 0) + break; + i++; + } + v.addElement(new Integer(comp)); + } + + } catch (IOException e) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + + return (v); + } + + /** + * Encodes DER length. + */ + static void writeLength(OutputStream os, int len) throws IOException { + + //encode the length - for all practical purposes, the length + //should always be less then 0x80 (128) + if (len < 0x80) + os.write(len); + else if (len < 0x100) { + os.write(0x081); + os.write(len); + } else if (len < 0x80000) { + os.write(0x082); + os.write(len >> 8); + os.write(len & 0xff); + } else if (len < 0x1000000) { + os.write(0x083); + os.write(len >> 16); + os.write((len >> 8) & 0xff); + os.write(len & 0xff); + } else { + os.write(0x084); + os.write(len >>> 24); + os.write((len >> 16) & 0xff); + os.write((len >> 8) & 0xff); + os.write(len & 0xff); + } + } + + + /** + * Produces ASN.1 DER encoding for the object. + * @return byte[] DER encoding for the object + */ + static byte[] encodeOid(Vector v) throws GSSException { + + //use byte array output stream - 32 initial bytes should be enough + ByteArrayOutputStream o = new ByteArrayOutputStream(); + + int length = 1; + + try { + //start with Oid tag + o.write(0x06); + + //figure our the length - must have at least 2 elements X.208 + if (v.size() < 2) + throw new IllegalArgumentException(); + + for (int i = 2; i < v.size(); i++) { + int compLen = 0; + int nextComp = ((Integer)v.elementAt(i)).intValue(); + + //count # of 7 bit octets this will occupy + for (compLen = 0; nextComp > 0; nextComp >>= 7, compLen++) + ;//nothing to do + + //must have at least 1 octet + if (compLen == 0) + length += 1; + else + length += compLen; + } + + writeLength(o, length); + + //now write the components + writeOidOctets(o, v); + } catch (IOException e) { + + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + return (o.toByteArray()); + } + + + /** + * Encodes the oid octets onto the stream. + */ + static void writeOidOctets(OutputStream o, Vector v) throws IOException { + + //first 2 components occupy 1 octet + o.write(((Integer)v.elementAt(0)).intValue() * 40 + + ((Integer)v.elementAt(1)).intValue()); + + for (int i = 2; i < v.size(); i++) { + + int tmp, nextComp = ((Integer)v.elementAt(i)).intValue(); + + //each component may be at most 4 octets long + for (int c = 0; c < 4; c++) { + tmp = (nextComp & 0x7f); + nextComp >>>= 7; + + //every octet except for last has bit 8 on + if (nextComp > 0) + o.write(tmp | 0x80); + else { + o.write(tmp); + break; + } + } + } + } +} diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/GSSContext.java b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/GSSContext.java new file mode 100644 index 0000000000..cb1bba73b8 --- /dev/null +++ b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/GSSContext.java @@ -0,0 +1,1591 @@ +/* + * Copyright (c) 1999, 2007 Sun Microsystems, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS + * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE + * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE + * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE + * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, + * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED + * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed,licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.sun.gssapi; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + + +/** + * This class represents the JGSS security context and its associated + * operations. JGSS security contexts are established between + * peers using locally established credentials. Multiple contexts + * may exist simultaneously between a pair of peers, using the same + * or different set of credentials. The JGSS is independent of + * the underlying transport protocols and depends on its callers to + * transport the tokens between peers. + *
+ * The context object can be thought of as having 3 implicit states: + * before it is established, during its context establishment, and + * after a fully established context exists. + *
+ * Before the context establishment phase is initiated, the context + * initiator may request specific characteristics desired of the + * established context. These can be set using the set methods. After the + * context is established, the caller can check the actual characteristic + * and services offered by the context using the query methods. + *
+ * The context establishment phase begins with the first call to the init + * method by the context initiator. During this phase the init and accept + * methods will produce GSS-API authentication tokens which the calling + * application needs to send to its peer. The init and accept methods may + * return a CONTINUE_NEEDED code which indicates that a token is needed + * from its peer in order to continue the context establishment phase. A + * return code of COMPLETE signals that the local end of the context is + * established. This may still require that a token be sent to the peer, + * depending if one is produced by GSS-API. The isEstablished method can + * also be used to determine if the local end of the context has been + * fully established. During the context establishment phase, the + * isProtReady method may be called to determine if the context can be + * used for the per-message operations. This allows implementation to + * use per-message operations on contexts which aren't fully established. + *
+ * After the context has been established or the isProtReady method + * returns "true", the query routines can be invoked to determine the actual + * characteristics and services of the established context. The + * application can also start using the per-message methods of wrap and + * getMIC to obtain cryptographic operations on application supplied data. + *
+ * When the context is no longer needed, the application should call + * dispose to release any system resources the context may be using. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + *
+ * The GSS-API authentication tokens contain a definitive + * start and end. This method will attempt to read one of these + * tokens per invocation, and may block on the stream if only + * part of the token is available. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + *
+ * This method may return an output token which the application + * will need to send to the peer for further processing by the + * init call. "null" return value indicates that no token needs + * to be sent to the peer. The application can call isEstablished + * to determine if the context establishment phase is complete + * for this peer. A return value of "false" from isEstablished + * indicates that more tokens are expected to be supplied to this + * method. + *
+ * Please note that the accept method may return a token for the + * peer, and isEstablished return "true" also. This indicates that + * the token needs to be sent to the peer, but the local end of the + * context is now fully established. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + * Called by the context acceptor upon receiving a token from + * the peer. May need to be called again if returns CONTINUE_NEEDED. + * + *
+ * The GSS-API authentication tokens contain a definitive start + * and end. This method will attempt to read one of these tokens + * per invocation, and may block on the stream if only part of the + * token is available. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + *
+ * The application will be responsible for sending the token + * to the peer. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + *
+ * The application will be responsible for sending the token + * to the peer. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + * + *
+ * Note that privacy can only be applied through the wrap call. + *
+ * Supports the derivation of MICs from zero-length messages. + * + *
+ * Note that privacy can only be applied through the wrap call. + *
+ * Supports the derivation of MICs from zero-length messages. + * + *
+ * This method deactivates the security context and creates an + * interprocess token which, when passed to the byte array + * constructor of the GSSContext class in another process, + * will re-activate the context in the second process. + *
+ * Only a single instantiation of a given context may be active + * at any one time; a subsequent attempt by a context exporter + * to access the exported security context will fail. + * + *
+ * The context object can be thought of as having 3 implicit states: + * before it is established, during its context establishment, and + * after a fully established context exists. + *
+ * Before the context establishment phase is initiated, the context + * initiator may request specific characteristics desired of the + * established context. These can be set using the set methods. After the + * context is established, the caller can check the actual characteristic + * and services offered by the context using the query methods. + *
+ * The context establishment phase begins with the first call to the init + * method by the context initiator. During this phase the init and accept + * methods will produce GSS-API authentication tokens which the calling + * application needs to send to its peer. The init and accept methods may + * return a CONTINUE_NEEDED code which indicates that a token is needed + * from its peer in order to continue the context establishment phase. A + * return code of COMPLETE signals that the local end of the context is + * established. This may still require that a token be sent to the peer, + * depending if one is produced by GSS-API. The isEstablished method can + * also be used to determine if the local end of the context has been + * fully established. During the context establishment phase, the + * isProtReady method may be called to determine if the context can be + * used for the per-message operations. This allows implementation to + * use per-message operations on contexts which aren't fully established. + *
+ * After the context has been established or the isProtReady method + * returns "true", the query routines can be invoked to determine the actual + * characteristics and services of the established context. The + * application can also start using the per-message methods of wrap and + * getMIC to obtain cryptographic operations on application supplied data. + *
+ * When the context is no longer needed, the application should call + * dispose to release any system resources the context may be using. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + *
+ * The GSS-API authentication tokens contain a definitive + * start and end. This method will attempt to read one of these + * tokens per invocation, and may block on the stream if only + * part of the token is available. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + *
+ * This method may return an output token which the application + * will need to send to the peer for further processing by the + * init call. "null" return value indicates that no token needs + * to be sent to the peer. The application can call isEstablished + * to determine if the context establishment phase is complete + * for this peer. A return value of "false" from isEstablished + * indicates that more tokens are expected to be supplied to this + * method. + *
+ * Please note that the accept method may return a token for the + * peer, and isEstablished return "true" also. This indicates that + * the token needs to be sent to the peer, but the local end of the + * context is now fully established. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + * Called by the context acceptor upon receiving a token from + * the peer. May need to be called again if returns CONTINUE_NEEDED. + * + *
+ * The GSS-API authentication tokens contain a definitive start + * and end. This method will attempt to read one of these tokens + * per invocation, and may block on the stream if only part of the + * token is available. + *
+ * Upon completion of the context establishment, the available + * context options may be queried through the get methods. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + *
+ * The application will be responsible for sending the token + * to the peer. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + *
+ * The application will be responsible for sending the token + * to the peer. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + * + *
+ * Supports the wrapping and unwrapping of zero-length messages. + * + *
+ * Note that privacy can only be applied through the wrap call. + *
+ * Supports the derivation of MICs from zero-length messages. + * + *
+ * Note that privacy can only be applied through the wrap call. + *
+ * Supports the derivation of MICs from zero-length messages. + * + *
+ * This method deactivates the security context and creates an + * interprocess token which, when passed to the byte array + * constructor of the GSSContext class in another process, + * will re-activate the context in the second process. + *
+ * Only a single instantiation of a given context may be active + * at any one time; a subsequent attempt by a context exporter + * to access the exported security context will fail. + * + *
+ * A credential may be used to perform context initiation, acceptance, + * or both. + *
+ * The context options can only be requested in state 1. In state 3, + * the per message operations are available to the callers. The get + * methods for the context options will return the requested options + * while in state 1 and 2, and the established values in state 3. + * Some mechanisms may allow the access to the per-message operations + * and the context flags before the context is fully established. The + * isProtReady method is used to indicate that these services are + * available. + */ + +public interface GSSCtxtSpi { + + /** + * Sets the mechanism options to be used during context + * creation on the initiator's side. This is used to + * initialize a new GSSCtxtSpi object. + * + * @param myCred the principal's credentials; may be null + * @param targName the context peer + * @param desLifetime the requested lifetime; 0 indicates use + * default + * @param mechOptions ORed GSSContext options + * @exception GSSException may be thrown + */ + public void _setInitOptions (GSSCredSpi myCred, GSSNameSpi targName, + int desLifetime, int ctxtOptions) throws GSSException; + + + /** + * Sets the mechanism options to be used during context + * creation on the acceptor's side. This is used to initialize + * a new GSSCtxtSpi object. + * + * @param myCred the principal's credentials; may be null + * @exception GSSException may be thrown + */ + public void _setAcceptOptions (GSSCredSpi myCred) throws GSSException; + + + /** + * Sets the channel bindings to be used during context + * establishment. This method is only called if the application + * wishes to use channel bindings with this context. + * + * @param chb channel bindings to be set + * @exception GSSException may be thrown + */ + public void _setChannelBinding (ChannelBinding chb) throws GSSException; + + + /** + * Retrieves the mechanism options. + * + * @return int GSSContext options ORed together + */ + public int _getOptions (); + + + /** + * Inquire the remaining lifetime. + * + * @return the lifetime in seconds. May return reserved + * value GSSContext.INDEFINITE for an indefinite lifetime. + */ + public int _getLifetime (); + + + /** + * Returns the mechanism oid. + * + * @return the Oid for this context + */ + public Oid _getMech (); + + + /** + * Returns the context initiator name. + * + * @return initiator name + * @exception GSSException may be thrown + */ + public GSSNameSpi _getSrcName () throws GSSException; + + + /** + * Returns the context acceptor name. + * + * @return context acceptor(target) name + * @exception GSSException may be thrown + */ + public GSSNameSpi _getTargName () throws GSSException; + + + /** + * Returns the delegated credential for the context. This + * is an optional feature of contexts which not all + * mechanisms will support. A context can be requested to + * support credential delegation by using the CRED_DELEG. + * This is only valid on the acceptor side of the context. + * @return GSSCredSpi object for the delegated credential + * @exception GSSException may be thrown + * @see GSSContext#getDelegCredState + */ + public GSSCredSpi _getDelegCred () throws GSSException; + + + /** + * Tests if this is the initiator side of the context. + * + * @return boolean indicating if this is initiator (true) + * or target (false) + */ + public boolean _isInitiator (); + + + /** + * Tests if the context can be used for per-message service. + * Context may allow the calls to the per-message service + * functions before being fully established. + * + * @return boolean indicating if per-message methods can + * be called. + */ + public boolean _isProtReady (); + + + /** + * Initiator context establishment call. This method may be + * required to be called several times. A CONTINUE_NEEDED return + * call indicates that more calls are needed after the next token + * is received from the peer. + * + * @param is contains the token received from the peer. On the + * first call it will be ignored. + * @param os to which any tokens required to be sent to the peer + * will be written. It is responsibility of the caller + * to send the token to its peer for processing. + * @return integer indicating if more calls are needed. Possible + * values are COMPLETE and CONTINUE_NEEDED. + * @exception GSSException may be thrown + */ + public int _initSecCtxt (InputStream is, OutputStream os) + throws GSSException; + + + /** + * Acceptor's context establishment call. This method may be + * required to be called several times. A CONTINUE_NEEDED return + * call indicates that more calls are needed after the next token + * is received from the peer. + * + * @param is contains the token received from the peer. + * @param os to which any tokens required to be sent to the peer + * will be written. It is responsibility of the caller + * to send the token to its peer for processing. + * @return integer indicating if more calls are needed. Possible + * values are COMPLETE and CONTINUE_NEEDED. + * @exception GSSException may be thrown + */ + public int _acceptSecCtxt (InputStream is, OutputStream os) + throws GSSException; + + + /** + * Queries the context for largest data size to accomodate + * the specified protection and for the token to remain less then + * maxTokSize. + * + * @param qop the quality of protection that the context will be + * asked to provide. + * @param confReq a flag indicating whether confidentiality will be + * requested or not + * @param outputSize the maximum size of the output token + * @return the maximum size for the input message that can be + * provided to the wrap() method in order to guarantee that these + * requirements are met. + * @exception GSSException may be thrown + */ + public int _getWrapSizeLimit (int qop, boolean confReq, int maxTokSize) + throws GSSException; + + + /** + * Provides per-message token encapsulation. + * + * @param is the user-provided message to be protected + * @param os the token to be sent to the peer. It includes + * the message from is with the requested protection. + * @param msgPro on input it contains the requested qop and + * confidentiality state, on output, the applied values + * @exception GSSException may be thrown + * @see MessageInfo + * @see unwrap + */ + public void _wrap (InputStream is, OutputStream os, MessageProp msgProp) + throws GSSException; + + + /** + * Retrieves the message token previously encapsulated in the wrap + * call. + * + * @param is the token from the peer + * @param os unprotected message data + * @param msgProp will contain the applied qop and confidentiality + * of the input token and any informatory status values + * @exception GSSException may be thrown + * @see MessageInfo + * @see wrap + */ + public void _unwrap (InputStream is, OutputStream os, + MessageProp msgProp) throws GSSException; + + + /** + * Applies per-message integrity services. + * + * @param is the user-provided message + * @param os the token to be sent to the peer along with the + * message token. The message token is not encapsulated. + * @param msgProp on input the desired QOP and output the applied QOP + * @exception GSSException + */ + public void _getMIC (InputStream is, OutputStream os, + MessageProp msgProp) + throws GSSException; + + + /** + * Checks the integrity of the supplied tokens. + * This token was previously generated by getMIC. + * + * @param is token generated by getMIC + * @param msgStr the message to check integrity for + * @param msgProp will contain the applied QOP and confidentiality + * states of the token as well as any informatory status codes + * @exception GSSException may be thrown + */ + public void _verifyMIC (InputStream is, InputStream msgStr, + MessageProp mProp) throws GSSException; + + + /** + * Produces a token representing this context. After this call + * the context will no longer be usable until an import is + * performed on the returned token. + * + * @return exported context token + * @exception GSSException may be thrown + */ + public byte []_export () throws GSSException; + + + /** + * Imports a previously exported context. This will be called + * for newly created objects. + * + * @param is the previously exported token + * @exception GSSException may be thrown + * @see export + */ + public void _importSecCtxt (byte []token) throws GSSException; + + + /** + * Releases context resources and terminates the + * context between 2 peer. + * + * @exception GSSException may be thrown + */ + public void _dispose () throws GSSException; +} + + diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/GSSException.java b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/GSSException.java new file mode 100644 index 0000000000..5a09976344 --- /dev/null +++ b/mucommander-protocol-nfs/src/main/java/com/sun/gssapi/GSSException.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 1999, 2007 Sun Microsystems, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS + * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE + * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE + * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE + * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, + * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED + * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed,licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.sun.gssapi; + +/** + * This exception is thrown whenever a fatal GSS-API error occurs + * including mechanism specific errors. It contains + * both the major and minor JGSS status codes. The mechanism + * implementers are responsible for setting appropriate minor status + * codes when throwing this exception. Methods are included to retrieve + * the error string representation for both major and minor codes. + *
+ *
+ *
+ * JGSS distinguishes between the following name representations: + *
{ 1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 2(gss-host-based-services) } + */ + public static final Oid NT_HOSTBASED_SERVICE; + + + /** + * Name type used to indicate a named user on a local system. It + * represents the following value: + *
+ * { iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) user_name(1) } + */ + public static final Oid NT_USER_NAME; + + + /** + * Name type used to indicate a numeric user identifier corresponding + * to a user on a local system. (e.g. Uid). It represents the + * following value: + *
+ * { iso(1) member-body(2) United States(840) mit(113554) infosys(1) + * gssapi(2) generic(1) machine_uid_name(2) } + */ + public static final Oid NT_MACHINE_UID_NAME; + + + /** + * Name type used to indicate a string of digits representing the + * numeric user identifier of a user on a local system. It + * represents the following value: + *
+ * { iso(1) member-body(2) United States(840) mit(113554) infosys(1) + * gssapi(2) generic(1) string_uid_name(3) } + */ + public static final Oid NT_STRING_UID_NAME; + + + /** + * Name type used to represent an Anonymous identity. It represents + * the following value: + *
+ * { 1(iso), 3(org), 6(dod), 1(internet), 5(security), 6(nametypes), + * 3(gss-anonymous-name) } + */ + public static final Oid NT_ANONYMOUS; + + + /** + * Name type used to indicate an exported name produced by the + * export method. It represents the following value: + *
+ * { 1(iso), 3(org), 6(dod), 1(internet), 5(security), 6(nametypes), + * 4(gss-api-exported-name) } + */ + public static final Oid NT_EXPORT_NAME; + + //initialize the oid objects + static { + try { + NT_HOSTBASED_SERVICE = new Oid("1.3.6.1.5.6.2"); + NT_USER_NAME = new Oid("1.2.840.113554.1.2.1.1"); + NT_MACHINE_UID_NAME = new Oid("1.2.840.113554.1.2.1.2"); + NT_STRING_UID_NAME = new Oid("1.2.840.113554.1.2.1.3"); + NT_ANONYMOUS = new Oid("1.3.6.1.5.6.3"); + NT_EXPORT_NAME = new Oid("1.3.6.1.5.6.4"); + + } catch (GSSException e) { + + //because we are initializeing statics, we can + //only throw a standard runtime exception + throw new NumberFormatException(); + } + } + + + /** + * Converts a contiguous string name to a GSSName object + * of the specified type. The nameStr parameter is + * interpreted based on the type specified. + * In general, the GSSName object created will not be an MN; + * the exception to this is if the type parameter indicates + * NT_EXPORT_NAME. + *
+ *
+ *
+ * Oids are hierarchically globally-interpretable identifiers + * used within the GSS-API framework to identify mechanisms and + * name formats. The structure and encoding of Oids is defined + * in ISOIEC-8824 and ISOIEC-8825. For example the Oid + * representation of Kerberos V5 mechanism is 1.2.840.113554.1.2.2 + *
true
if the file specified by this object
+ * exists; false
otherwise.
+ */
+ public boolean exists() {
+ try {
+ if (iStream == null)
+ iStream = urlConn.getInputStream();
+
+ return true;
+
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Tests if the application can write to this file.
+ *
+ * @return true
if the application is allowed to
+ * write to a file whose name is specified by this
+ * object; false
otherwise.
+ */
+ public boolean canWrite() {
+ try {
+ if (oStream == null)
+ oStream = urlConn.getOutputStream();
+
+ return true;
+
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Tests if the application can read from the specified file.
+ *
+ * @return true
if the file specified by this
+ * object exists and the application can read the file;
+ * false
otherwise.
+ */
+ public boolean canRead() {
+ return exists();
+ }
+
+ /**
+ * Tests if the file represented by this
+ * object is a "normal" file.
+ *
+ * A file is "normal" if it is not a directory and, in
+ * addition, satisfies other system-dependent criteria. Any
+ * non-directory file created by a Java application is
+ * guaranteed to be a normal file.
+ *
+ * @return true
if the file specified by this
+ * XFile
object exists and is a "normal"
+ * file; false
otherwise.
+ */
+ public boolean isFile() {
+ return true;
+ }
+
+
+ /**
+ * Tests if the file represented by this XFileAccessor
+ * object is a directory.
+ *
+ * @return true
if this XFileAccessor object
+ * exists and is a directory; false
+ * otherwise.
+ */
+ public boolean isDirectory() {
+ return false;
+ }
+
+
+ /**
+ * Returns the time that the file represented by this
+ * XFile
object was last modified.
+ *
+ * The return value is system dependent and should only be
+ * used to compare with other values returned by last modified.
+ * It should not be interpreted as an absolute time.
+ *
+ * @return the time the file specified by this object was last
+ * modified, or 0L
if the specified file
+ * does not exist.
+ */
+ public long lastModified() {
+ return urlConn.getLastModified();
+ }
+
+
+ /**
+ * Returns the length of the file represented by this
+ * XFileAccessor object.
+ *
+ * @return the length, in bytes, of the file specified by
+ * this object, or 0L
if the specified
+ * file does not exist.
+ */
+ public long length() {
+ long len = urlConn.getContentLength();
+
+ return len < 0 ? 0 : len;
+ }
+
+
+ /**
+ * Creates a file whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the file could be created;
+ * false
otherwise.
+ */
+ public boolean mkfile() {
+ try {
+ if (oStream == null)
+ oStream = urlConn.getOutputStream();
+
+ return true;
+
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Creates a directory whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the directory could be created;
+ * false
otherwise.
+ */
+ public boolean mkdir() {
+ return false;
+ }
+
+
+ /**
+ * Renames the file specified by this XFileAccessor object to
+ * have the pathname given by the XFileAccessor object argument.
+ *
+ * @param dest the new filename.
+ * @return true
if the renaming succeeds;
+ * false
otherwise.
+ */
+ public boolean renameTo(XFile dest) {
+ return false;
+ }
+
+
+ /**
+ * Returns a list of the files in the directory specified by
+ * this XFileAccessor object.
+ *
+ * @return an array of file names in the specified directory.
+ * This list does not include the current directory or
+ * the parent directory (".
" and
+ * "..
" on Unix systems).
+ */
+ public String[] list() {
+ return new String[0];
+ }
+
+
+ /**
+ * Deletes the file specified by this object. If the target
+ * file to be deleted is a directory, it must be empty for
+ * deletion to succeed.
+ *
+ * @return true
if the file is successfully deleted;
+ * false
otherwise.
+ */
+ public boolean delete() {
+ return false;
+ }
+
+
+ /**
+ * Reads a subarray as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @return number of bytes read; -1 if EOF
+ * @exception IOException If an I/O error has occurred.
+ */
+ public int read(byte b[], int off, int len, long foff)
+ throws IOException {
+
+ int c;
+
+ if (iStream == null)
+ iStream = urlConn.getInputStream();
+
+ if (foff > fp) {
+ iStream.skip(foff - fp);
+ fp = foff;
+ }
+
+ c = iStream.read(b, off, len);
+
+ if (c > 0)
+ fp += c;
+
+ return (c);
+ }
+
+
+ /**
+ * Writes a sub array as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @exception IOException If an I/O error has occurred.
+ */
+ public void write(byte b[], int off, int len, long foff)
+ throws IOException {
+
+ if (oStream == null)
+ oStream = urlConn.getOutputStream();
+
+ oStream.write(b, off, len);
+
+ fp += len;
+ }
+
+
+ /**
+ * Forces any buffered output bytes to be written out.
+ *
+ *
+ * @exception IOException if an I/O error occurs.
+ */
+ public void flush() throws IOException {
+ if (oStream != null)
+ oStream.flush();
+ }
+
+
+ /**
+ * Close the Streams
+ *
+ * @exception IOException If an I/O error has occurred.
+ */
+ public void close() throws IOException {
+
+ if (iStream != null) {
+ iStream.close();
+ iStream = null;
+ }
+ if (oStream != null) {
+ oStream.close();
+ oStream = null;
+ }
+ }
+
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return a string giving the pathname of this object.
+ */
+ public String toString() {
+ return url.toString();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Buffer.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Buffer.java
new file mode 100644
index 0000000000..6eaddfbac1
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Buffer.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.rpc.*;
+
+/**
+ * Here we do all the NFS read and write buffering.
+ *
+ * @see Nfs
+ * @see Nfs2
+ * @see Nfs3
+ * @author Brent Callaghan
+ */
+
+public class Buffer extends Thread {
+
+ Nfs nfs;
+ long foffset;
+
+ byte[] buf; // The buffer itself
+ int bufoff; // Offset into the buffer
+ int buflen; // Bytes in buffer
+ int bufsize; // Size of buffer
+
+ int minOffset; // First byte written
+ int maxOffset; // Last byte written
+
+ int status;
+ private int action;
+ boolean eof;
+ IOException e;
+ Error err;
+ long writeVerifier;
+ int syncType;
+
+ // Various kinds of action
+
+ private final static int IDLE = 0;
+ private final static int LOAD = 1;
+ private final static int UNLOAD = 2;
+ private final static int EXIT = 3;
+
+ /*
+ * The initial state of a buffer is EMPTY.
+ * When file data is read into a file it becomes LOADED.
+ * If the buffer contains data that has not yet been written
+ * to the file then it is DIRTY.
+ * The COMMIT state indicates that the data is written but
+ * not yet committed. Once committed, the state returns
+ * to LOADED.
+ */
+ final static int EMPTY = 0; // Has no data
+ final static int LOADED = 1; // Has file data
+ final static int DIRTY = 2; // Has new data
+ final static int COMMIT = 3; // Not committed
+
+ public Buffer(Nfs nfs, long foffset, int bufsize) {
+ this.nfs = nfs;
+ this.foffset = foffset;
+ this.bufsize = bufsize;
+ this.buflen = 0;
+
+ minOffset = bufsize;
+ maxOffset = 0;
+
+ setDaemon(true); // NFS threads die when app exits
+ try {
+ setName("Buffer-" + (foffset / bufsize));
+ } catch (Exception e) {}; // non-essential, ignore
+ action = IDLE;
+ start();
+ }
+
+ /*
+ * Copy data from a buffer.
+ * We assume the buffer is loaded with data
+ */
+ synchronized int copyFrom(byte[] buff, int boff, long foffset, int length)
+ throws IOException {
+
+ /*
+ * May have gotten an async exception
+ * so throw it here.
+ */
+ if (e != null)
+ throw e;
+ if (err != null)
+ throw err;
+
+ if (status == EMPTY)
+ throw new IOException("no data");
+
+ /*
+ * We may have a partial buffer if the file
+ * has since been extended by a write into
+ * another buffer that is not yet unloaded.
+ * We must make sure that the buffer is complete.
+ */
+ if (buflen < bufsize) {
+ byte[] nbuf = new byte[bufsize]; // bigger buffer
+ if (buflen > 0)
+ System.arraycopy(buf, bufoff, nbuf, 0, buflen);
+ buflen = bufsize;
+ bufoff = 0;
+ buf = nbuf;
+ }
+
+ int off = (int) (foffset - this.foffset);
+ int copylen = Math.min(length, buflen - off);
+ copylen = (int) Math.min((long)copylen, nfs.length() - foffset);
+
+ System.arraycopy(buf, bufoff + off, buff, boff, copylen);
+
+ return copylen;
+ }
+
+ /*
+ * Copy data to a buffer.
+ * If the buffer maps to a valid offset of a file then first
+ * make sure data is loaded from the file into the buffer.
+ * Record the range of data modified in the buffer so that
+ * when the buffer is written only the modified range is
+ * written back to the server.
+ */
+ synchronized int copyTo(byte[] buff, int boff, long foffset, int length)
+ throws IOException {
+
+ /*
+ * May have gotten an async exception
+ * so throw it here.
+ */
+ if (e != null)
+ throw e;
+ if (err != null)
+ throw err;
+
+ int off = (int) (foffset - this.foffset);
+ int copylen = Math.min(length, bufsize - off);
+
+ /*
+ * If writing less than a full buffer and if
+ * overwriting existing file data, then make
+ * sure the buffer is loaded from the file.
+ */
+ if (status == EMPTY) {
+ long bufEnd = Math.min(nfs.length(), this.foffset + nfs.wsize);
+
+ if (this.foffset < nfs.length() &&
+ (foffset > this.foffset || foffset + length < bufEnd)) {
+ startLoad();
+ waitLoaded();
+ }
+ }
+
+ /*
+ * May need to extend the size of the buffer
+ */
+ if (off + copylen > buflen) {
+ byte[] nbuf = new byte[bufsize];
+ if (buf != null)
+ System.arraycopy(buf, bufoff, nbuf, 0, buflen);
+ buf = nbuf;
+ bufoff = 0;
+ buflen = bufsize;
+ }
+
+ System.arraycopy(buff, boff, buf, bufoff + off, copylen);
+
+ status = DIRTY;
+
+ /*
+ * Record the range of the buffer that's been
+ * modified so that we can write less than
+ * the full buffer if possible.
+ */
+ if (off < minOffset)
+ minOffset = off;
+
+ if (off + copylen > maxOffset)
+ maxOffset = off + copylen;
+
+ return copylen;
+ }
+
+ /*
+ * Notify the buffer thread that it is to read data
+ */
+ synchronized void startLoad() {
+ action = LOAD;
+ notifyAll();
+ }
+
+ /*
+ * Wait until the buffer thread has finished loading the buffer
+ */
+ synchronized void waitLoaded() throws IOException {
+
+ /*
+ * Check for an exception thrown by the async thread
+ * in case the thread died and we block forever
+ * waiting for the buffer state to change.
+ */
+ if (e != null)
+ throw e;
+ if (err != null)
+ throw err;
+
+ while (this.status == EMPTY) {
+ try {
+ wait();
+ } catch (InterruptedException e) {}
+
+ if (this.e != null)
+ throw this.e;
+ if (err != null)
+ throw err;
+ }
+ }
+
+ /*
+ * Wait until the buffer thread is finished writing the buffer
+ */
+ synchronized void waitUnloaded() throws IOException {
+
+ /*
+ * Check in case async thread threw an
+ * exception and died.
+ */
+ if (e != null)
+ throw e;
+ if (err != null)
+ throw err;
+
+ while (action == UNLOAD) {
+ try {
+ wait();
+ } catch (InterruptedException e) {}
+
+ if (this.e != null)
+ throw this.e;
+ if (err != null)
+ throw err;
+ }
+ }
+
+
+ /*
+ * Notify the buffer thread that it is to write data
+ */
+ synchronized void startUnload(int sync) {
+ nfs.beginWrite();
+
+ action = UNLOAD;
+ syncType = sync;
+ notifyAll();
+ }
+
+ /*
+ * Request the buffer thread to exit cleanly
+ */
+ synchronized void exit() {
+
+ action = EXIT;
+ notifyAll();
+ }
+
+ /*
+ * This is the run method for the buffer thread.
+ * It is started when the buffer is instantiated
+ * and sleeps on the monitor waiting to be woken
+ * up and perform one of two actions: LOAD data
+ * (write) or UNLOAD data (read).
+ */
+ public void run() {
+
+ synchronized (this) {
+ try {
+ while (true) {
+
+ while (action == IDLE) {
+ try {
+ wait();
+ } catch (InterruptedException e) {}
+ }
+
+ /*
+ * Thread has been notified - perform the action
+ */
+ switch (action) {
+
+ case LOAD:
+ try {
+ nfs.read_otw(this);
+
+ } catch (IOException e) {
+ if (this.e == null)
+ this.e = e;
+ }
+
+ status = LOADED;
+ break;
+
+ case UNLOAD:
+ try {
+
+ /*
+ * Server may do a short write, so keep
+ * writing until all the bytes have been
+ * written.
+ */
+ int saveMin = minOffset;
+ while (minOffset < maxOffset)
+ minOffset += nfs.write_otw(this);
+
+ minOffset = bufsize;
+ maxOffset = 0;
+
+ } catch (IOException e) {
+ if (this.e == null)
+ this.e = e;
+ }
+ nfs.endWrite();
+ break;
+
+ case EXIT:
+ notifyAll();
+
+ /*
+ * XXX Dereferencing the buf here should not be
+ * necessary since the entire buffer is dereferenced
+ * from the bufferList in Nfs, however for some reason
+ * the GC is ignoring dereferenced buffers.
+ * Setting buf to null makes sure that the GC collects
+ * the bulk of the memory tied up in a buffer, even
+ * if the Buffer object itself is not reclaimed.
+ */
+ buf = null;
+
+ return;
+ }
+
+ action = IDLE;
+ notifyAll();
+ }
+ } catch (Error e) {
+ /*
+ * Need to catch errors here, e.g. OutOfMemoryError
+ * and notify threads before this thread dies
+ * otherwise they'll wait forever.
+ */
+ err = e;
+ notifyAll();
+ throw e;
+ }
+ }
+ }
+
+ public String toString() {
+ return (nfs.name + " @ " + foffset + " for " + buflen);
+ }
+
+// protected void finalize() throws Throwable {
+// System.out.println("#### BUFFER FINALIZE " + toString());
+// super.finalize();
+// }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr.java
new file mode 100644
index 0000000000..90bbad6034
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import com.sun.rpc.*;
+
+/**
+ *
+ * NFS file attributes
+ *
+ * This is essentially a container class that holds
+ * the attributes, but it also has methods to encode
+ * and decode the attributes in an Xdr buffer.
+ *
+ * Note that the time at which the attributes were
+ * retrieved is automatically updated and the cache
+ * time varies according to the frequency of file
+ * modification.
+ *
+ * There are two subclasses: Fattr2 for NFS v2
+ * attributes and Fattr3 for v3.
+ *
+ * @see Nfs
+ * @see Fattr2
+ * @see Fattr3
+ * @author Brent Callaghan
+ */
+public abstract class Fattr {
+
+ long validtime; // time when attrs were new
+ long cachetime; // max cache duration in ms
+ static final int ACMIN = 3 * 1000; // 3 sec - min cache time
+ static final int ACMAX = 60 * 1000; // 1 min - max cache time
+
+ static final int NOBODY = 60001; // Svr4 UID/GID "nobody"
+ static final int NFS_NOBODY = -2; // NFS UID/GID "nobody"
+
+
+ /**
+ * Check if the attributes are "fresh" enough.
+ *
+ * If not, then the caller will likely update
+ * the attributes with an NFS getattr request.
+ *
+ * @returns true if the attributes are valid
+ */
+ boolean valid() {
+ long timenow = System.currentTimeMillis();
+
+ return (timenow <= validtime + cachetime);
+ }
+
+ abstract void putFattr(Xdr x);
+
+ abstract void getFattr(Xdr x);
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr2.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr2.java
new file mode 100644
index 0000000000..e6ad22a353
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr2.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.rpc.*;
+import java.util.Date;
+
+/**
+ *
+ * NFS version 2 file attributes
+ *
+ */
+class Fattr2 extends Fattr {
+ int ftype;
+ long mode;
+ long nlink;
+ long uid;
+ long gid;
+ long size;
+ long blocksz;
+ long rdev;
+ long blocks;
+ long fsid;
+ long fileid;
+ long atime;
+ long mtime;
+ long ctime;
+
+ Fattr2() {
+ }
+
+ Fattr2 (Xdr x) {
+ this.getFattr(x);
+ }
+
+ void putFattr(Xdr x) {
+ x.xdr_int(ftype);
+ x.xdr_u_int(mode);
+ x.xdr_u_int(nlink);
+ x.xdr_u_int(uid);
+ x.xdr_u_int(gid);
+ x.xdr_u_int(size);
+ x.xdr_u_int(blocksz);
+ x.xdr_u_int(rdev);
+ x.xdr_u_int(blocks);
+ x.xdr_u_int(fsid);
+ x.xdr_u_int(fileid);
+ x.xdr_u_int(atime / 1000); // sec
+ x.xdr_u_int(atime % 1000); // msec
+ x.xdr_u_int(mtime / 1000); // sec
+ x.xdr_u_int(mtime % 1000); // msec
+ x.xdr_u_int(ctime / 1000); // sec
+ x.xdr_u_int(ctime % 1000); // msec
+ }
+
+ void getFattr(Xdr x) {
+ long oldmtime = mtime;
+
+ ftype = x.xdr_int();
+ mode = x.xdr_u_int();
+ nlink = x.xdr_u_int();
+ uid = x.xdr_u_int(); if (uid == NFS_NOBODY) uid = NOBODY;
+ gid = x.xdr_u_int(); if (gid == NFS_NOBODY) gid = NOBODY;
+ size = x.xdr_u_int();
+ blocksz = x.xdr_u_int();
+ rdev = x.xdr_u_int();
+ blocks = x.xdr_u_int();
+ fsid = x.xdr_u_int();
+ fileid = x.xdr_u_int();
+ atime = x.xdr_u_int() * 1000 + x.xdr_u_int();
+ mtime = x.xdr_u_int() * 1000 + x.xdr_u_int();
+ ctime = x.xdr_u_int() * 1000 + x.xdr_u_int();
+
+ /*
+ * We want the cache time to be short
+ * for files/dirs that change frequently
+ * and long for files/dirs that change
+ * infrequently. So set the cache time to
+ * the delta between file modifications
+ * limited by ACMIN and ACMAX
+ */
+ long delta = mtime - oldmtime;
+ if (delta > 0) {
+ cachetime = delta;
+ if (cachetime < ACMIN)
+ cachetime = ACMIN;
+ else if (cachetime > ACMAX)
+ cachetime = ACMAX;
+ }
+ validtime = System.currentTimeMillis();
+ }
+
+ public String toString() {
+ return (
+ " ftype = " + ftype + "\n" +
+ " mode = 0" + Long.toOctalString(mode) + "\n" +
+ " nlink = " + nlink + "\n" +
+ " uid = " + uid + "\n" +
+ " gid = " + gid + "\n" +
+ " size = " + size + "\n" +
+ "blocksz = " + blocksz + "\n" +
+ " rdev = 0x" + Long.toHexString(rdev) + "\n" +
+ " blocks = " + blocks + "\n" +
+ " fsid = " + fsid + "\n" +
+ " fileid = " + fileid + "\n" +
+ " atime = " + new Date(atime) + "\n" +
+ " mtime = " + new Date(mtime) + "\n" +
+ " ctime = " + new Date(ctime)
+ );
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr3.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr3.java
new file mode 100644
index 0000000000..db3d13cf04
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Fattr3.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.rpc.*;
+import java.util.Date;
+
+/**
+ *
+ * NFS version 3 file attributes
+ *
+ */
+class Fattr3 extends Fattr {
+ int ftype;
+ long mode;
+ long nlink;
+ long uid;
+ long gid;
+ long size;
+ long used;
+ long rdev;
+ long fsid;
+ long fileid;
+ long atime;
+ long mtime;
+ long ctime;
+
+ Fattr3() {
+ }
+
+ Fattr3(Xdr x) {
+ this.getFattr(x);
+ }
+
+ void putFattr(Xdr x) {
+ x.xdr_int(ftype);
+ x.xdr_u_int(mode);
+ x.xdr_u_int(nlink);
+ x.xdr_u_int(uid);
+ x.xdr_u_int(gid);
+ x.xdr_hyper(size);
+ x.xdr_hyper(used);
+ x.xdr_hyper(rdev);
+ x.xdr_hyper(fsid);
+ x.xdr_hyper(fileid);
+ x.xdr_u_int(atime / 1000); // sec
+ x.xdr_u_int(atime % 1000 * 1000000); // nsec
+ x.xdr_u_int(mtime / 1000); // sec
+ x.xdr_u_int(mtime % 1000 * 1000000); // nsec
+ x.xdr_u_int(ctime / 1000); // sec
+ x.xdr_u_int(ctime % 1000 * 1000000); // nsec
+ }
+
+ void getFattr(Xdr x) {
+ long oldmtime = mtime;
+
+ ftype = x.xdr_int();
+ mode = x.xdr_u_int();
+ nlink = x.xdr_u_int();
+ uid = x.xdr_u_int(); if (uid == NFS_NOBODY) uid = NOBODY;
+ gid = x.xdr_u_int(); if (gid == NFS_NOBODY) gid = NOBODY;
+ size = x.xdr_hyper();
+ used = x.xdr_hyper();
+ rdev = x.xdr_hyper();
+ fsid = x.xdr_hyper();
+ fileid = x.xdr_hyper();
+ atime = x.xdr_u_int() * 1000 + x.xdr_u_int() / 1000000;
+ mtime = x.xdr_u_int() * 1000 + x.xdr_u_int() / 1000000;
+ ctime = x.xdr_u_int() * 1000 + x.xdr_u_int() / 1000000;
+
+ /*
+ * We want the cache time to be short
+ * for files/dirs that change frequently
+ * and long for files/dirs that change
+ * infrequently. So set the cache time to
+ * the delta between file modifications
+ * limited by ACMIN and ACMAX
+ */
+ long delta = mtime - oldmtime;
+ if (delta > 0) {
+ cachetime = delta;
+ if (cachetime < ACMIN)
+ cachetime = ACMIN;
+ else if (cachetime > ACMAX)
+ cachetime = ACMAX;
+ }
+ validtime = System.currentTimeMillis();
+ }
+
+ public String toString() {
+ return (
+ " ftype = " + ftype + "\n" +
+ " mode = 0" + Long.toOctalString(mode) + "\n" +
+ " nlink = " + nlink + "\n" +
+ " uid = " + uid + "\n" +
+ " gid = " + gid + "\n" +
+ " size = " + size + "\n" +
+ " used = " + used + "\n" +
+ " rdev = 0x" + Long.toHexString(rdev) + "\n" +
+ " fsid = " + fsid + "\n" +
+ "fileid = " + fileid + "\n" +
+ " atime = " + new Date(atime) + "\n" +
+ " mtime = " + new Date(mtime) + "\n" +
+ " ctime = " + new Date(ctime)
+ );
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Mount.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Mount.java
new file mode 100644
index 0000000000..a50e077f3c
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Mount.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.rpc.*;
+
+/**
+ * Handle the mount protocol for NFS versions 2 and 3
+ *
+ * Note that we transmit an unmount request immediately
+ * after a successful mount request. This avoids having
+ * "mount" entries pile up in the server's /etc/rmtab log.
+ *
+ * @see Nfs
+ * @author Brent Callaghan
+ */
+class Mount {
+ private final static int MOUNTPROG = 100005;
+
+ private final static int MOUNTPROC_MNT = 1;
+ private final static int MOUNTPROC_UMNT = 3;
+ private final static int MOUNTPROC_EXPORT = 5;
+
+ private final static int FHSIZE = 32;
+ private final static int FHSIZE3 = 64;
+
+ private final static int ENOENT = 2;
+ private final static int EACCES = 13;
+
+ String sec_flavor;
+
+ /**
+ * Get an NFS v2 or v3 file handle
+ *
+ * @param server The NFS server
+ * @param path The file path on the server
+ * @param vers The NFS version
+ * @returns The filehandle as a byte array
+ */
+ byte[] getFH(String server, String path, int vers)
+ throws java.net.UnknownHostException, IOException {
+ Rpc mnt;
+ Xdr callmsg = new Xdr(1024);
+ int status;
+ byte[] fh;
+
+ // Use Mount v1 for NFS v2, Mount v3 for NFS v3
+
+ mnt = new Rpc(server, 0, MOUNTPROG, vers == 2 ? 1 : 3, "udp", 512);
+ mnt.setCred(new CredUnix(0, 0));
+ mnt.rpc_header(callmsg, MOUNTPROC_MNT);
+ callmsg.xdr_string(path);
+
+ Xdr replymsg = mnt.rpc_call(callmsg, 3 * 1000, 3);
+
+ status = replymsg.xdr_int();
+ if (status != 0) {
+ /*
+ * If ENOENT is returned and the path didn't
+ * start with a slash then assume it's because
+ * the URL didn't include it (a common mistake).
+ * Add the slash and try again.
+ */
+ if ((status == ENOENT || status == EACCES) && ! path.startsWith("/"))
+ return getFH(server, "/" + path, vers);
+
+ throw new IOException("Mount status: " + status);
+ }
+
+ // Filehandle is different depending on version
+
+ fh = vers == 2 ? replymsg.xdr_raw(FHSIZE) : replymsg.xdr_bytes();
+
+ // Get security flavors if this is MOUNT V3.
+ sec_flavor = null;
+ if (vers == 3) {
+ int numsec = replymsg.xdr_int();
+ String prefer = NfsSecurity.getPrefer();
+ while (numsec-- > 0) {
+ String secmode = Integer.toString(replymsg.xdr_int());
+
+ if ((prefer != null) && prefer.equals(secmode)) {
+ sec_flavor = prefer;
+ }
+
+ if ((sec_flavor == null) &&
+ NfsSecurity.hasValue(secmode)) {
+ sec_flavor = secmode;
+ }
+ } // while
+ }
+ if (sec_flavor == null) {
+ sec_flavor = NfsSecurity.getDefault();
+ }
+
+ /*
+ * Now send an unmount request
+ */
+ mnt.rpc_header(callmsg, MOUNTPROC_UMNT);
+ callmsg.xdr_string(path);
+ try {
+ mnt.rpc_call(callmsg, 1000, 1);
+ } catch (InterruptedIOException e) {
+ // ignore
+ }
+
+ return (fh);
+ }
+
+ /*
+ * get the sec_flavor after a successful getMountInfo()
+ */
+ String getSec() {
+ return (sec_flavor);
+ }
+
+ /*
+ * Get the server's export list
+ *
+ */
+ static String[] getExports(String server)
+ throws java.net.UnknownHostException, IOException {
+ Rpc mnt;
+ Xdr callmsg = new Xdr(255);
+ Xdr replymsg;
+ String[] elist = new String[32];
+ int i = 0;
+
+ try {
+ mnt = new Rpc(server, 0, MOUNTPROG, 1, "tcp", 8192);
+ mnt.setCred(new CredUnix(0, 0));
+ mnt.rpc_header(callmsg, MOUNTPROC_EXPORT);
+
+ // This RPC proc takes no arguments
+
+ replymsg = mnt.rpc_call(callmsg, 3 * 1000, 3);
+
+ } catch (java.net.UnknownHostException e) {
+ throw e;
+
+ } catch (IOException e) {
+ return new String[0]; // an empty export list
+ }
+
+ /*
+ * The exports come back as a linked list.
+ * Walk along the list extracting the export names
+ * into an array and ignore the associated groups list.
+ */
+ while (replymsg.xdr_bool()) {
+ elist[i++] = replymsg.xdr_string();
+ if (i >= elist.length) { // last elem in array ?
+ String[] tmp = elist;
+
+ elist = new String[i*2]; // double its size
+ System.arraycopy(tmp, 0, elist, 0, i);
+ }
+
+ /*
+ * Skip the groups list
+ */
+ while (replymsg.xdr_bool()) {
+ replymsg.xdr_string();
+ }
+ }
+
+ /*
+ * Trim export list to exact size
+ */
+ if (i < elist.length) {
+ String[] tmp = elist;
+
+ elist = new String[i];
+ System.arraycopy(tmp, 0, elist, 0, i);
+ }
+
+ return elist;
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Nfs.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Nfs.java
new file mode 100644
index 0000000000..a05c593141
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Nfs.java
@@ -0,0 +1,731 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.rpc.*;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ *
+ * Container class for an NFS object: either a file
+ * or a directory. Herein are common
+ * methods that are not version specific.
+ *
+ * This class holds the file's filehandle, name,
+ * and attributes. If a regular file then data may
+ * be cached in an XDR buffer. If a directory then
+ * the string array for the entries will be cached.
+ * There's also a static hash table that's used to cache
+ * these Nfs objects.
+ *
+ * @see Nfs2
+ * @see Nfs3
+ * @see Buffer
+ * @author Brent Callaghan
+ * @author Ricardo Labiaga
+ */
+public abstract class Nfs {
+ byte[] fh;
+ Rpc rpc;
+ String name;
+ String[] dircache;
+ String symlink;
+ Buffer[] bufferList;
+ long cacheTime; // Time when object was cached
+ int rsize, wsize;
+ private Object wbLock = new Object(); // write-behind semaphore lock
+ static Hashtable cacheNfs = new Hashtable();
+
+ // Some of the filetypes we're dealing with.
+
+ static final int NFREG = 1;
+ static final int NFDIR = 2;
+ static final int NFLNK = 5;
+
+ // Flags for asynchronous or synchronous writes
+
+ private final static int ASYNC = 0;
+ private final static int SYNC = 2;
+
+ int NRA; // max reads-ahead (set in subclass constructor)
+ int NWB; // max writes-behind (")
+ int NWC; // max writes committed (")
+ int nwb; // current writes-behind
+ int prevReadIndex = -1; // Buffer index of previous read
+ int prevWriteIndex = -1; // Buffer index of previous write
+ int maxIndexRead = 0; // Max file offset read
+ long maxLength = 0; // Size of file
+
+ // Some important permission bits
+
+ static final int RBIT = 004;
+ static final int WBIT = 002;
+
+ /*
+ * The following abstract classes are version-specific
+ * and are implemented in the version subclasses.
+ */
+
+ abstract void checkAttr() throws IOException;
+
+ abstract boolean cacheOK(long t) throws IOException;
+
+ abstract void getattr() throws IOException;
+
+ abstract long mtime() throws IOException;
+
+ abstract long length() throws IOException;
+
+ abstract boolean exists() throws IOException;
+
+ abstract boolean canWrite() throws IOException;
+
+ abstract boolean canRead() throws IOException;
+
+ abstract boolean isFile() throws IOException;
+
+ abstract boolean isDirectory() throws IOException;
+
+ abstract boolean isSymlink() throws IOException;
+
+ abstract Fattr getAttr() throws IOException;
+
+ abstract Nfs lookup(String path) throws IOException;
+
+ abstract String lookupSec() throws IOException;
+
+ abstract void read_otw(Buffer b) throws IOException;
+
+ abstract int write_otw(Buffer buf) throws IOException;
+
+ abstract String[] readdir() throws IOException;
+
+ abstract String readlink() throws IOException;
+
+ abstract Nfs create(String name, long mode) throws IOException;
+
+ abstract Nfs mkdir(String name, long mode) throws IOException;
+
+ abstract boolean remove(String name) throws IOException;
+
+ abstract boolean rename(Nfs dstP, String sName, String dName) throws IOException;
+
+ abstract boolean rmdir(String name) throws IOException;
+
+ abstract void fsinfo() throws IOException;
+
+ abstract long commit(int foffset, int length) throws IOException;
+
+ abstract void invalidate();
+
+
+ /*
+ * The following methods are all NFS version independent.
+ */
+
+ /*
+ * Get FileHandle for Nfs Object
+ */
+ byte[] getFH() {
+ return (fh);
+ }
+
+ /*
+ * Cache an Nfs object
+ *
+ * @param n the object to be cached
+ */
+ static void cache_put(Nfs n) {
+ cacheNfs.put(n.rpc.conn.server + ":" + n.name, n);
+ }
+
+ /*
+ * Retrieve a cached Nfs object
+ *
+ * @param server The server that hosts the object
+ * @param name The pathname of the object
+ * @returns The object - or null if not cached
+ */
+ static Nfs cache_get(String server, String name) {
+ return ((Nfs)cacheNfs.get(server + ":" + name));
+ }
+
+ /*
+ * Remove an Nfs object from the cache
+ *
+ * @param n the object to be removed from cache
+ */
+ static void cache_remove(Nfs n, String name) {
+ if (n.name.equals("."))
+ cacheNfs.remove(n.rpc.conn.server + ":" + name);
+ else
+ cacheNfs.remove(n.rpc.conn.server + ":" + n.name + "/" + name);
+ }
+
+ /**
+ * Read data from the specified file offset
+ *
+ * @param buf The destination buffer
+ * @param boff Offset into the dest buffer
+ * @param length Amount of data to read
+ * @param foffset File offset to begin reading
+ * @exception java.io.IOException
+ * @return actual bytes read
+ */
+ synchronized int read(byte[] buf, int boff, int length, long foffset)
+ throws IOException {
+
+ Buffer b = null;
+ int index;
+ int readAhead = 0;
+ int bytesRead = 0;
+
+ /*
+ * If the file modification time has changed since
+ * the last read then invalidate all cached buffers.
+ */
+ if (!cacheOK(cacheTime) && bufferList != null) {
+ for (int i = 0; i < bufferList.length; i++)
+ if (i != prevWriteIndex) // don't delete dirty buffers
+ bufferList[i] = null;
+
+ prevReadIndex = -1;
+ }
+
+ /*
+ * Check whether we're at EOF
+ */
+ if (foffset >= length())
+ return -1;
+
+ /*
+ * Keep reading until the read request is satisfied.
+ */
+ while (length > 0) {
+
+ /*
+ * Check whether we're at EOF
+ */
+ if (foffset >= length())
+ break;
+
+ /*
+ * Make sure an array of buffers exists that's big enough
+ * to for the entire file.
+ */
+ if (bufferList == null)
+ bufferList = new Buffer[(int) (length() / rsize + 1)];
+
+ /*
+ * Find the block that holds the data
+ */
+ index = (int) (foffset / rsize);
+ if (index > maxIndexRead)
+ maxIndexRead = index;
+
+ /*
+ * Make sure that previously read buffers are
+ * released. If not, then reading a large file
+ * would quickly run the app out of memory, though
+ * must be careful not to release in-use write buffers.
+ * XXX We should find a way to make better use of
+ * available memory and keep file buffers cached.
+ */
+ if (index != prevReadIndex) {
+ if (prevReadIndex >= 0 && prevReadIndex != prevWriteIndex) {
+ b = bufferList[prevReadIndex];
+ if (b.status == b.LOADED) {
+ bufferList[prevReadIndex] = null;
+ b.exit();
+ }
+
+ /*
+ * Do read-ahead only for sequential I/O
+ */
+ if (index == (prevReadIndex + 1) && index >= maxIndexRead)
+ readAhead = NRA;
+ }
+ prevReadIndex = index;
+ }
+
+ /*
+ * Make sure that the buffer is
+ * are loaded or loading - as well as
+ * any buffers that will likely be needed
+ * i.e. read-ahead buffers.
+ */
+ for (int n = index; n <= index + readAhead; n++) {
+
+ if (n >= bufferList.length)
+ break;
+
+ b = bufferList[n];
+ if (b == null) {
+ b = new Buffer(this, (long) n * (long) rsize, rsize);
+ b.startLoad();
+ bufferList[n] = b;
+ }
+ }
+
+ /*
+ * Now select the buffer and wait until its not busy.
+ */
+ b = bufferList[index];
+ try {
+ b.waitLoaded();
+ } catch (NfsException n) {
+ /*
+ * Check if it's a bogus "EBADRPC"
+ * error from a Digital Unix server.
+ * It implies that the read was too
+ * long. The server should just return
+ * a short read - but until they fix it
+ * we'll handle it here.
+ * Optimistically set the read
+ * size to 8k and try again.
+ */
+ if (n.error == 72) { // DEC's EBADRPC
+ rsize = 8192;
+ bufferList =
+ new Buffer[(int)(length() / rsize + 1)];
+ continue;
+ }
+
+ throw n;
+ }
+
+ /*
+ * If the buffer contains less data than requested
+ * and it's not EOF, then assume that we guessed
+ * too big for the server's transfer size.
+ */
+ int bufflen = b.buflen;
+ if (bufflen < rsize && !b.eof) {
+ rsize = bufflen;
+ bufferList = null;
+ prevReadIndex = -1;
+ prevWriteIndex = -1;
+
+ continue; // Try again with new rsize
+ }
+
+ /*
+ * Copy data from the file buffer into the application buffer.
+ */
+ int cc = b.copyFrom(buf, boff, foffset,length);
+
+ boff += cc;
+ foffset += cc;
+ length -= cc;
+ bytesRead += cc;
+ }
+
+ return (bytesRead);
+ }
+
+ /*
+ * These two methods implement a semaphore to prevent the client from
+ * generating an huge number of write-behind threads that could
+ * overload the server.
+ *
+ * These methods synchronize on wbLock rather than the
+ * class monitor otherwise there's a risk of deadlock
+ * through Nfs.write() -> Buffer.startUnload() -> Nfs.beginWrite()
+ */
+ void beginWrite() {
+ synchronized (wbLock) {
+ while (nwb >= NWB) {
+ try {
+ wbLock.wait();
+ } catch (InterruptedException e) {}
+ }
+ nwb++;
+ }
+ }
+
+ void endWrite() {
+ synchronized (wbLock) {
+ nwb--;
+ wbLock.notify();
+ }
+ }
+
+ /**
+ * Write data to a file at a specified offset.
+ *
+ * @param buf The data to write
+ * @param boff Offset into the data buffer
+ * @param length Amount of data to write
+ * @param foffset File offset to begin writing at
+ * @exception java.io.IOException
+ */
+ synchronized void write(byte buf[], int boff, int length, long foffset)
+ throws IOException {
+
+ /*
+ * If the write size is not set then call FSINFO
+ * to set it. We would prefer not to make this call
+ * since it adds an extra turnaround, but the alternative
+ * is to guess at the write size by trying a large write
+ * and see how many bytes the server writes. If we're doing
+ * async write-behind it'll take complex code to recover
+ * from a series of partial writes that would wreak havoc with
+ * any write gathering that the server might be doing. So
+ * it's likely safer just to have the server tell us its
+ * preferred write size as the protocol intended.
+ */
+ if (wsize == 0)
+ fsinfo();
+
+ /*
+ * If we haven't read the file yet then there may
+ * be no buffer list. Allocate one that will hold
+ * all of the existing file blocks, or if it's a newly
+ * created file, assume an initial size of 50 blocks.
+ */
+ if (bufferList == null) {
+ long fileSize = Math.max(length(), 50 * wsize);
+ bufferList = new Buffer[(int)(fileSize / wsize + 1)];
+ }
+
+ /*
+ * Keep writing data to the server in buffer-size chunks
+ * until the write request is satisfied. If the write
+ * is a short one into an existing buffer then no data
+ * will be written at all. This is good, it's much more
+ * efficient to write larger amounts of data to the server.
+ *
+ * We get further improvement in write throughput by writing
+ * buffers asynchronously in a buffer thread. This allows the
+ * application to continue filling a new buffer while previous
+ * buffers are written.
+ *
+ * This method takes advantage of the ability of NFS version 3
+ * to perform safe, asynchronous writes which significantly
+ * increase write throughput.
+ */
+ while (length > 0) {
+
+ int index = (int)(foffset / wsize);
+
+ /*
+ * If writing into a new buffer
+ * start writing out the previous one.
+ */
+ if (index != prevWriteIndex) {
+ if (prevWriteIndex >= 0) {
+ bufferList[prevWriteIndex].startUnload(ASYNC);
+
+ checkCommit(false);
+ }
+ prevWriteIndex = index;
+ }
+
+ /*
+ * If trying to write to a buffer off the end of
+ * the current buffer list, then double the size
+ * of the buffer list.
+ */
+ if (index >= bufferList.length) {
+ Buffer[] tlist = new Buffer[bufferList.length * 2];
+ for (int i = 0; i < bufferList.length; i++)
+ tlist[i] = bufferList[i];
+ bufferList = tlist;
+ }
+
+
+ /*
+ * Check if there's a buffer allocated
+ */
+ Buffer b = bufferList[index];
+ if (b == null) {
+ b = new Buffer(this, (long) index * wsize, wsize);
+ bufferList[index] = b;
+ }
+
+ /*
+ * Copy data from the application buffer to the file buffer.
+ */
+ int cc = b.copyTo(buf, boff, foffset, length);
+
+ boff += cc;
+ foffset += cc;
+ length -= cc;
+
+ /*
+ * Need to record max file offset here in case
+ * the app calls length() before the data has
+ * been written out and recorded in the file attrs.
+ */
+ if (foffset > maxLength)
+ maxLength = foffset;
+
+ } // end while
+ }
+
+ /*
+ * Check the buffer list for buffers that should be released.
+ * Buffers must be released otherwise the entire file will
+ * become cached and we risk running out of memory.
+ *
+ * The same scan also checks for buffers that are pending
+ * commit. If it's a v2 server then there will be none,
+ * but if v3 and there are more than NWC of these then
+ * send a COMMIT request. Until these buffers are committed
+ * they cannot be released. The scan records the range of
+ * buffers pending commit for the benefit of the COMMIT
+ * request which requires an offset and range.
+ *
+ * This method is called with flushing set to true when
+ * the file is being closed. In this case the code must
+ * write the current buffer and wait for all write operations
+ * to complete.
+ */
+ void checkCommit(boolean flushing) throws IOException {
+
+ int minIndex = Integer.MAX_VALUE;
+ int maxIndex = 0;
+ int nwc = 0;
+
+ /*
+ * Determine the first and last buffers in
+ * the buffer list that are waiting commit.
+ * Then we know the byte range to be committed.
+ *
+ * Also, release any LOADED buffers.
+ */
+ for (int i = 0; i < bufferList.length; i++) {
+ Buffer b = bufferList[i];
+ if (b != null) {
+ if (flushing)
+ b.waitUnloaded();
+
+ if (b.status == b.LOADED) {
+
+ /*
+ * Don't throw away the "current" buffer
+ */
+ if (i == prevReadIndex || i == prevWriteIndex)
+ continue;
+
+ bufferList[i] = null;
+ b.exit();
+ } else if (b.status == b.COMMIT) {
+ nwc++;
+ if (i < minIndex)
+ minIndex = i;
+ if (i > maxIndex)
+ maxIndex = i;
+ }
+ }
+ }
+
+ /*
+ * If flushing write the "current" buffer if it is dirty.
+ * Here we catch writes to files no bigger than one
+ * buffer. It's better to do a single sync write than
+ * do an async write followed by a commit for a single
+ * buffer.
+ */
+ if (flushing) {
+ Buffer b = bufferList[prevWriteIndex];
+ if (b != null) {
+ if (b.status == b.DIRTY) {
+ if (nwc == 0) { // just one - do it sync
+ b.startUnload(SYNC);
+ b.waitUnloaded();
+ } else { // more than one - do it async
+ b.startUnload(ASYNC);
+ b.waitUnloaded();
+
+ // Record the commit range
+
+ if (prevWriteIndex < minIndex)
+ minIndex = prevWriteIndex;
+ if (prevWriteIndex > maxIndex)
+ maxIndex = prevWriteIndex;
+ }
+ }
+ }
+ }
+
+ /*
+ * If writing to a v3 server then there may
+ * be some buffers pending commit.
+ * If the commit is successful the buffers can
+ * be released.
+ */
+ if (nwc > 0 && (flushing || nwc >= NWC)) {
+ int commitOffset = minIndex * rsize +
+ bufferList[minIndex].minOffset;
+ int commitLength = (maxIndex * rsize +
+ bufferList[maxIndex].maxOffset) - commitOffset;
+
+ long verf = commit(commitOffset, commitLength);
+
+ /*
+ * Check the write verifiers of the buffers
+ * in the commit range. If each verifier
+ * matches then the buffer data are safe
+ * and we can release the buffer.
+ * If the verifier does not match its possible
+ * that the server lost the data so rewrite
+ * the buffer.
+ */
+ for (int i = minIndex; i <= maxIndex; i++) {
+ Buffer b = bufferList[i];
+ if (b == null)
+ continue;
+
+ if (flushing)
+ b.waitUnloaded();
+
+ if (b.status == b.COMMIT) {
+
+ /*
+ * Can now release committed buffers with
+ * matching verifiers iff they're not "current"
+ */
+ if (b.writeVerifier == verf) {
+
+ if (i == prevReadIndex || i == prevWriteIndex) {
+ b.status = b.LOADED;
+ continue;
+ }
+
+ bufferList[i] = null; // release buffer
+ b.exit();
+ } else {
+
+ /*
+ * Have to rewrite.
+ *
+ * If flushing then do sync-writes because
+ * we can't return until the data are safe.
+ * Otherwise, we just fire off another async
+ * write and have it committed later.
+ */
+ if (flushing) {
+ b.startUnload(SYNC);
+ b.waitUnloaded();
+ } else {
+ b.startUnload(ASYNC);
+ }
+ }
+ }
+ } // end for
+ }
+ }
+
+ /**
+ * Flush any buffered writes to the file. This must be
+ * called after any series of writes to guarantee that the
+ * data reach the server.
+ * @exception java.io.IOException if writes failed for some reason, e.g.
+ * if server ran out of disk space.
+ */
+ synchronized public void flush() throws IOException {
+ if (prevWriteIndex >= 0) // if no writes then don't bother
+ checkCommit(true);
+ }
+
+ /**
+ * Close the file by flushing data and
+ * deallocating buffers.
+ * @exception java.io.IOException if failure during flushing.
+ */
+ synchronized public void close() throws IOException {
+ int n = 0;
+
+ if (bufferList == null)
+ return;
+
+ flush(); // unwritten data
+
+ for (int i = 0; i < bufferList.length; i++) {
+ if (bufferList[i] != null) {
+ Buffer b = bufferList[i];
+ bufferList[i] = null;
+ b.exit();
+ }
+ }
+
+ prevReadIndex = -1;
+ prevWriteIndex = -1;
+ }
+
+ /*
+ * Make sure that pending writes are flushed if the app
+ * neglected to call flush().
+ */
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+
+ public String toString() {
+
+ try {
+ if (isSymlink()) {
+ if (symlink != null)
+ return "\"" + name + "\": symlink -> \"" + symlink + "\"";
+ else
+ return "\"" + name + "\": symlink";
+
+ }
+
+ if (isDirectory()) {
+ String s = "\":" + name + "\" directory";
+
+ if (dircache != null)
+ return s + "(" + dircache.length + " entries)";
+ else
+ return s;
+ }
+
+ // Must be a regular file
+
+ return "\"" + name + "\": file (" + length() + " bytes)";
+
+ } catch (IOException e) {
+ return e.getMessage();
+ }
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Nfs2.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Nfs2.java
new file mode 100644
index 0000000000..372ee72e13
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/Nfs2.java
@@ -0,0 +1,832 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.rpc.*;
+
+/**
+ * This class contains the methods specific to
+ * NFS version 2.
+ *
+ * @see Nfs
+ * @see Nfs3
+ * @see Fattr
+ * @author Brent Callaghan
+ * @author Ricardo Labiaga
+ */
+public class Nfs2 extends Nfs {
+
+ Fattr2 attr;
+
+ /*
+ * NFS version 2 procedure numbers
+ */
+ private final static int NFSPROC2_NULL = 0;
+ private final static int NFSPROC2_GETATTR = 1;
+ private final static int NFSPROC2_SETATTR = 2;
+ private final static int NFSPROC2_LOOKUP = 4;
+ private final static int NFSPROC2_READLINK = 5;
+ private final static int NFSPROC2_READ = 6;
+ private final static int NFSPROC2_WRITE = 8;
+ private final static int NFSPROC2_CREATE = 9;
+ private final static int NFSPROC2_REMOVE = 10;
+ private final static int NFSPROC2_RENAME = 11;
+ private final static int NFSPROC2_LINK = 12;
+ private final static int NFSPROC2_SYMLINK = 13;
+ private final static int NFSPROC2_MKDIR = 14;
+ private final static int NFSPROC2_RMDIR = 15;
+ private final static int NFSPROC2_READDIR = 16;
+ private final static int NFSPROC2_STATFS = 17;
+
+ private final static int NFS_OK = 0;
+ private final static int RWSIZE = 8192; // optimal read/write size
+ private final static int FHSIZE = 32; // file handle size
+
+ int nwb; // current writes-behind
+
+ /**
+ * Construct a new NFS version 2 object (file or directory)
+ *
+ * @param rpc Rpc object for the server
+ * @param fh File handle for the object
+ * @param name Name of the file/dir
+ * @param attr File attributes for the object
+ */
+ public Nfs2(Rpc rpc, byte[] fh, String name, Fattr2 attr) {
+ this.rpc = rpc;
+ this.fh = fh;
+ if (name.startsWith("./")) // normalize for cache lookup
+ name = name.substring(2);
+ this.name = name;
+ this.attr = attr == null ? new Fattr2() : attr;
+ this.rsize = RWSIZE;
+ NRA = 2; // Max reads-ahead
+ NWB = 8; // Max writes-behind
+ }
+
+ void getattr() throws IOException {
+ Xdr call = new Xdr(rsize + 512);
+ rpc.rpc_header(call, NFSPROC2_GETATTR);
+ call.xdr_raw(fh);
+
+ Xdr reply;
+
+ try {
+ reply = rpc.rpc_call(call, 2 * 1000, 2);
+ } catch (IOException e) {
+ // don't let a mere getattr hang
+ // the app if the server is down.
+ return;
+ }
+
+ int status = reply.xdr_int();
+ if (status != NFS_OK)
+ throw new NfsException(status);
+
+ attr.getFattr(reply);
+ }
+
+ void checkAttr() throws IOException {
+
+ if (! attr.valid())
+ getattr();
+ }
+
+ boolean cacheOK(long t) throws IOException {
+ checkAttr();
+
+ return t == attr.mtime;
+ }
+
+ void invalidate() {
+ attr.validtime = 0;
+ }
+
+ /*
+ * Get the file modification time
+ * @return the time in milliseconds
+ */
+ long mtime() throws IOException {
+ checkAttr();
+
+ return attr.mtime;
+ }
+
+ /*
+ * Get the file size in bytes.
+ *
+ * Note that the size may be greater than that
+ * shown in the attributes if the file is being written.
+ *
+ * @return file size
+ */
+ long length() throws IOException {
+ checkAttr();
+
+ return maxLength > attr.size ? maxLength : attr.size;
+ }
+
+ /*
+ * Verify if file exists
+ * @return true if file exists
+ */
+ boolean exists() throws IOException {
+ checkAttr();
+
+ return true;
+ }
+
+ private boolean check_access(long mode) {
+ boolean found = false;
+ long uid = NfsConnect.getCred().getUid();
+ long gid = NfsConnect.getCred().getGid();
+ int gids[] = NfsConnect.getCred().getGids();
+
+ /*
+ * Access check is based on only
+ * one of owner, group, public.
+ * If not owner, then check group.
+ * If not a member of the group,
+ * then check public access.
+ */
+ mode <<= 6;
+ if (uid != attr.uid) {
+ mode >>= 3;
+ if (gid != attr.gid) {
+ // check group list
+ int gidsLength = 0;
+
+ if (gids != null)
+ gidsLength = gids.length;
+
+ for (int i = 0; i < gidsLength; i++)
+ if (found = ((long)gids[i] == attr.gid))
+ break;
+ if (!found) {
+ // not in group list, check "other" field
+ mode >>= 3;
+ }
+ }
+ }
+
+ return (attr.mode & mode) == mode;
+ }
+
+ /*
+ * Verify if file can be created/updated
+ * @return true if file can be created/updated
+ */
+ boolean canWrite() throws IOException {
+ checkAttr();
+
+ return check_access(WBIT);
+ }
+
+ /*
+ * Verify if file can be read
+ * @return true if file can be read
+ */
+ boolean canRead() throws IOException {
+ checkAttr();
+
+ return check_access(RBIT);
+ }
+
+ /*
+ * Verify if this is a file
+ * @return true if a file
+ */
+ boolean isFile() throws IOException {
+ checkAttr();
+
+ return attr.ftype == NFREG;
+ }
+
+ /*
+ * Verify if this is a directory
+ * @return true if a directory
+ */
+ boolean isDirectory() throws IOException {
+ checkAttr();
+
+ return attr.ftype == NFDIR;
+ }
+
+ /*
+ * Verify if this is a symbolic link
+ * @return true if a symbolic link
+ */
+ boolean isSymlink() throws IOException {
+ checkAttr();
+
+ return attr.ftype == NFLNK;
+ }
+
+ /*
+ * @return file attributes
+ */
+ Fattr getAttr() throws IOException {
+ checkAttr();
+
+ return (Fattr)attr;
+ }
+
+ /*
+ * Lookup a name in a directory
+ *
+ * If its a symbolic link - follow it
+ *
+ * @param name Name of entry in directory
+ * @returns Nfs object
+ * @exception java.io.IOException
+ */
+ Nfs lookup(String name) throws IOException {
+ byte[] newfh;
+ Fattr2 newattrs;
+ Nfs nfs;
+ String pathname;
+
+ /* For multi-component lookup, the name would already be
+ * filled in when object is created and
+ * thus name passed in will be null.
+ */
+ if (name == null) {
+ pathname = this.name;
+ name = this.name;
+ } else { /* Single component case */
+ if (this.name == null)
+ pathname = name;
+ else
+ pathname = this.name + "/" + name;
+ }
+
+ /*
+ * First check the cache to see
+ * if we already have this file/dir
+ */
+ nfs = cache_get(rpc.conn.server, pathname);
+ if (nfs != null && nfs.cacheOK(cacheTime)) {
+
+ // If a symbolic link then follow it
+
+ if (((Nfs2)nfs).attr.ftype == NFLNK)
+ nfs = NfsConnect.followLink(nfs);
+
+ return nfs;
+ }
+
+ Xdr call = new Xdr(rsize + 512);
+ Xdr reply = null;
+
+ /*
+ * If needed, give one try to get the security information
+ * from the server.
+ */
+ for (int sec_tries = 1; sec_tries >= 0; sec_tries--) {
+ rpc.rpc_header(call, NFSPROC2_LOOKUP);
+ call.xdr_raw(fh);
+ call.xdr_string(name);
+
+ try {
+ reply = rpc.rpc_call(call, 5 * 1000, 0);
+ break;
+ } catch (MsgRejectedException e) {
+ /*
+ * Check if this lookup is using public fh.
+ * If so and if the call receives an AUTH_TOOWEAK error,
+ * lookupSec() is called to get the security flavor
+ * information from the server by using the WebNFS
+ * security negotiation protocol (supported in Solaris 8).
+ */
+ boolean is_v2pubfh = true;
+ for (int i = 0; i < 32; i++) {
+ if (fh[i] != (byte) 0) {
+ is_v2pubfh = false;
+ break;
+ }
+ }
+ if (is_v2pubfh && e.why ==
+ MsgRejectedException.AUTH_TOOWEAK) {
+ String secKey = lookupSec();
+ if (secKey != null &&
+ NfsSecurity.getMech(secKey) != null) {
+ rpc.setCred(new CredGss("nfs",
+ NfsSecurity.getMech(secKey),
+ NfsSecurity.getService(secKey),
+ NfsSecurity.getQop(secKey)));
+ continue;
+ } else if (secKey != null && secKey.equals("1")) {
+ rpc.setCred(new CredUnix());
+ continue;
+ }
+ }
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ }
+ } // for
+
+ int status = reply.xdr_int();
+ if (status != NFS_OK)
+ throw new NfsException(status);
+
+ newfh = reply.xdr_raw(FHSIZE);
+ newattrs = new Fattr2(reply);
+
+ nfs = new Nfs2(rpc, newfh, pathname, newattrs);
+ cache_put(nfs);
+
+ // If a symbolic link then follow it
+
+ if (((Nfs2)nfs).attr.ftype == NFLNK)
+ nfs = NfsConnect.followLink(nfs);
+
+ return nfs;
+ }
+
+ /*
+ * lookupSec() uses the WebNFS security negotiation protocol to
+ * get nfs flavor numbers required by the nfs server.
+ *
+ * If the server fails to return the security numbers, the client
+ * will use a default security mode specified in the
+ * nfssec.properties file.
+ *
+ * If the server successfully returns a list of security modes,
+ * the client will use the preferred security mode that matches
+ * any security number found in the list, otherwise, it
+ * will use the first security number from the list that the
+ * client supports.
+ *
+ * Null string is returned if the client does not support any
+ * security numbers that the server requests.
+ *
+ * Here is an example of the WebNFS security negotiation protocol:
+ *
+ * Suppose the server shares /export/home as follows:
+ *
+ * share -o sec=sec_1:sec_2:sec_3 /export/secure
+ *
+ * Here is how to READ a file from server:/export/secure:
+ *
+ * Client Server
+ * ------ ------
+ *
+ * LOOKUP 0x0, foo, "path"
+ * ----------->
+ * <-----------
+ * AUTH_TOOWEAK
+ *
+ * LOOKUP 0x0, foo, 0x81,
+ * An instance of the NfsHandler class is
+ * registered with the setHandler method of
+ * the NFS XFileExtensionAccessor.
+ *
+ * @param server The name of the server to which the
+ * request was sent.
+ * @param retries The number of times the request has
+ * been retransmitted. After the first timeout
+ * retries will be zero.
+ * @param wait Total time since first call in milliseconds
+ * (cumulative value of all retransmission timeouts).
+ * @return false if retransmissions are to continue.
+ * If the method returns true, the RPC layer will
+ * abort the retransmissions and return an
+ * InterruptedIOException to the application.
+ */
+ public abstract boolean timeout(String server, int retries, int wait);
+
+ /**
+ * Called when a server reply is recieved after a timeout.
+ *
+ * @param server The name of the server that returned
+ * the reply.
+ */
+ public abstract void ok(String server);
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/NfsSecurity.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/NfsSecurity.java
new file mode 100644
index 0000000000..1188e8450b
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/NfsSecurity.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import java.util.*;
+import com.sun.rpc.*;
+
+/**
+ * NfsSecurity is a static class. It reads in the com.sun.properties.nfssec
+ * properties file and provides the vehicle to retrieve properties values
+ * which are the (mechanism, service, qop) mappings for the NFS security pseudo
+ * flavor numbers.
+ *
+ * @author Lin Ling
+ */
+public final class NfsSecurity {
+
+ static ResourceBundle props;
+ static String secName, secMode, mech;
+ static int service, qop;
+ static {
+ initialize();
+ }
+
+ private static void initialize() {
+
+ try {
+ props = ResourceBundle.getBundle("com.sun.nfs.nfssec");
+ } catch (MissingResourceException e) {
+ props = null;
+ }
+ }
+
+ /*
+ * Parse the string value using ":" as the delimiter.
+ *
+ * nfsSecName:mechanismOID:service:qualityProtection
+ *
+ * (e.g. dummyp:1.3.6.1.4.1.42.2.26.1.2:privacy:0)
+ *
+ */
+ private static void parseValue(String value) {
+
+ StringTokenizer parser = new StringTokenizer(value, ":\n\r");
+
+ secName = parser.nextToken();
+
+ try {
+ mech = parser.nextToken();
+ } catch (NoSuchElementException e) {
+ // non-RPCSEC_GSS flavors
+ mech = null;
+ service = 0;
+ qop = 0;
+ return;
+ }
+
+ String serviceString = parser.nextToken();
+ if (serviceString.equals("none"))
+ service = Cred.SVC_NONE;
+ else if (serviceString.equals("integrity"))
+ service = Cred.SVC_INTEGRITY;
+ else if (serviceString.equals("privacy"))
+ service = Cred.SVC_PRIVACY;
+ else
+ service = Cred.SVC_PRIVACY; // just use privacy service
+
+ qop = Integer.parseInt(parser.nextToken());
+
+ }
+
+ /**
+ * Does the key have a value defined in the nfssec.properties file?
+ * (i.e. is key=value defined in the properties list?)
+ *
+ * @param key the key to be searched
+ * @returns true or false
+ */
+ public static boolean hasValue(String key) {
+
+ if (props == null)
+ return false;
+
+ try {
+ props.getString(key);
+ return true;
+
+ } catch (MissingResourceException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Get the default security flavor number if it is specified in the
+ * nfssec.properties file, otherwise, simply return "1" for AUTH_SYS.
+ */
+ public static String getDefault() {
+
+ if (props == null)
+ return "1";
+
+ try {
+ return props.getString("default");
+ } catch (MissingResourceException e) {
+ return "1";
+ }
+ }
+
+ /**
+ * Get the preferred nfs security flavor number if it is specified
+ * in the nfssec.properties file, otherwise, return null.
+ */
+ public static String getPrefer() {
+
+ if (props == null)
+ return null;
+
+ try {
+ return props.getString("prefer");
+ } catch (MissingResourceException e) {
+ return null;
+ }
+ }
+
+ /**
+ * getName will get the NFS security flavor name from the first token
+ * in the value.
+ *
+ * key=nfsSecName:mechOid:service:qop
+ * ^^^^^^^^^^
+ *
+ * @param key the key to be searched
+ * @returns NFS Security flavor name
+ */
+ public static String getName(String key) {
+
+ if (key.equals(secMode)) {
+ return secName;
+ }
+
+ parseValue(props.getString(key));
+ secMode = key;
+ return secName;
+ }
+
+ /**
+ * getMech will get the security mechanism OID string from the second token
+ * in the value.
+ *
+ * key=nfsSecName:mechOid:service:qop
+ * ^^^^^^^
+ *
+ * @param key the key to be searched
+ * @returns security mechansim OID string
+ */
+ public static String getMech(String key) {
+
+ if (key.equals(secMode)) {
+ return mech;
+ }
+
+ parseValue(props.getString(key));
+ secMode = key;
+ return mech;
+ }
+
+ /**
+ * getService will get the security service type from the third token
+ * in the value.
+ *
+ * key=nfsSecName:mechOid:service:qop
+ * ^^^^^^^
+ *
+ * @param key the key to be searched
+ * @returns one of (none, integrity, privacy); if the third token
+ * in the value does not have the expected data, simply
+ * returns the privacy service number.
+ */
+ public static int getService(String key) {
+
+ if (key.equals(secMode)) {
+ return service;
+ }
+
+ parseValue(props.getString(key));
+ secMode = key;
+ return service;
+ }
+
+ /**
+ * getQop will get the Quality of Protection number from the fourth token
+ * in the value.
+ *
+ * key=nfsSecName:mechOid:service:qop
+ * ^^^
+ *
+ * @param key the key to be searched
+ * @returns qop number; 0 means the mechanism-specific default qop
+ */
+ public static int getQop(String key) {
+
+ if (key.equals(secMode)) {
+ return qop;
+ }
+
+ parseValue(props.getString(key));
+ secMode = key;
+ return qop;
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/NfsURL.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/NfsURL.java
new file mode 100644
index 0000000000..10107b2858
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/NfsURL.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.net.MalformedURLException;
+
+/**
+ * This is just a dumb URL parser class.
+ * I wrote it because I got fed up with the
+ * JDK URL class calling NFS URL's "invalid"
+ * simply because the Handler wasn't installed.
+ *
+ * This URL parser also handles undocumented
+ * testing options inserted in the URL in the
+ * port field. The following sequence of option
+ * letters may appear before or after the port
+ * number, or alone if the port number is not
+ * given.
+ * vn - NFS version, e.g. "v3"
+ * u - Force UDP - normally TCP is preferred
+ * t - Force TDP - don't fall back to UDP
+ * m - Force Mount protocol. Normally public filehandle
+ * is preferred
+ *
+ * Option ordering is not important.
+ *
+ * Example:
+ * nfs://server:123v2um/path
+ *
+ * Use port 123 with NFS v2 over UDP and Mount protocol
+ *
+ * nfs://server:m/path
+ *
+ * Use default port, prefer V3/TCP but use Mount protocol
+ *
+ * @author Brent Callaghan
+ */
+
+public class NfsURL {
+
+ private String url;
+ private String protocol;
+ private String host;
+ private String location;
+ private int port;
+ private String file;
+
+ /*
+ * Undocumented testing options
+ */
+ private int version;
+ private String proto;
+ private boolean pub = true;
+
+ public NfsURL(String url) throws MalformedURLException {
+ int p, q, r;
+
+ url = url.trim(); // remove leading & trailing spaces
+ this.url = url;
+ int end = url.length();
+
+ p = url.indexOf(':');
+ if (p < 0)
+ throw new MalformedURLException("colon expected");
+ protocol = url.substring(0, p);
+ p++; // skip colon
+ if (url.regionMatches(p, "//", 0, 2)) { // have hostname
+ p += 2;
+ q = url.indexOf('/', p);
+ if (q < 0)
+ q = end;
+ location = url.substring(0, q);
+ r = url.indexOf(':', p);
+ if (r > 0 && r < q) {
+ byte[] opts = url.substring(r + 1, q).toLowerCase().getBytes();
+ for (int i = 0; i < opts.length; i++) {
+ if (opts[i] >= '0' && opts[i] <= '9') {
+ port = (port * 10) + (opts[i] - '0');
+ } else {
+ switch (opts[i]) {
+ case 'v': // NFS version
+ version = opts[++i] - '0';
+ break;
+ case 't': // Force TCP only
+ proto = "tcp";
+ break;
+ case 'u': // Force UDP only
+ proto = "udp";
+ break;
+ case 'w': // Force WebNFS only
+ pub = true;
+ break;
+ case 'm': // Force MOUNT protocol only
+ pub = false;
+ break;
+ default:
+ throw new MalformedURLException(
+ "invalid port number");
+ }
+ }
+ }
+ } else {
+ r = q; // no port
+ }
+ if (p < r)
+ host = url.substring(p, r);
+ } else {
+ q = p;
+ }
+
+ if (q < end)
+ file = url.substring(q + 1, end);
+
+ }
+
+ public String getProtocol() {
+ return (protocol);
+ }
+
+ public String getLocation() {
+ return (location);
+ }
+
+ public String getHost() {
+ return (host);
+ }
+
+ public int getPort() {
+ return (port);
+ }
+
+ public String getFile() {
+ return (file);
+ }
+
+ /*
+ * Undocumented options for testing
+ */
+ int getVersion() {
+ return (version);
+ }
+
+ String getProto() {
+ return (proto);
+ }
+
+ boolean getPub() {
+ return (pub);
+ }
+
+
+ public String toString() {
+ String s = getProtocol() + ":";
+
+ if (host != null)
+ s += "//" + host;
+
+ if (port > 0)
+ s += ":" + port;
+
+ if (file != null)
+ s += "/" + file;
+
+ return (s);
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/XFileAccessor.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/XFileAccessor.java
new file mode 100644
index 0000000000..348ee3af05
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/XFileAccessor.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import com.sun.xfile.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * The XFileAccessor interface is implemented by filesystems that
+ * need to be accessed via the XFile API.
+ *
+ * @author Brent Callaghan
+ * @version 1.0, 04/08/98
+ * @see com.sun.xfile.XFile
+ */
+public
+class XFileAccessor implements com.sun.xfile.XFileAccessor {
+
+ XFile xf;
+ boolean serial;
+ boolean readOnly;
+ Nfs nfs;
+
+ /**
+ * Open this NFS object
+ *
+ * @param xf the XFile object
+ * @param serial true if serial access
+ * @param readOnly true if read only
+ */
+ public boolean open(XFile xf, boolean serial, boolean readOnly) {
+ this.xf = xf;
+ try {
+ nfs = NfsConnect.connect(xf.getAbsolutePath());
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ public XFile getXFile() {
+ return xf;
+ }
+
+ protected Nfs getParent(XFile xf) throws IOException {
+ XFile xfp = new XFile(xf.getParent());
+ XFileAccessor nfsx = new XFileAccessor();
+ nfsx.open(xfp, serial, readOnly);
+
+ return nfsx.getNfs();
+ }
+
+ protected Nfs getNfs() {
+ return nfs;
+ }
+
+ /**
+ * Tests if this XFileAccessor object exists.
+ *
+ * @return
+ * A file is "normal" if it is not a directory and, in
+ * addition, satisfies other system-dependent criteria. Any
+ * non-directory file created by a Java application is
+ * guaranteed to be a normal nfs.
+ *
+ * @return
+ * The return value is system dependent and should only be
+ * used to compare with other values returned by last modified.
+ * It should not be interpreted as an absolute time.
+ *
+ * @return the time the file specified by this object was last
+ * modified, or
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void flush() throws IOException {
+ nfs.flush();
+ }
+
+
+ /**
+ * Close the file
+ *
+ * Since NFS has no concept of file close, we just
+ * flush any buffered data.
+ *
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ public void close() throws IOException {
+ nfs.close();
+ }
+
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return a string giving the pathname of this object.
+ */
+ public String toString() {
+ return nfs.toString();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/XFileExtensionAccessor.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/XFileExtensionAccessor.java
new file mode 100644
index 0000000000..0ac6e86f4e
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/XFileExtensionAccessor.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+import java.io.*;
+import com.sun.xfile.*;
+
+public class XFileExtensionAccessor
+ extends com.sun.xfile.XFileExtensionAccessor {
+
+ XFile xf;
+
+ public XFileExtensionAccessor(XFile xf) {
+
+ super(xf);
+ if (! xf.getFileSystemName().equals("nfs"))
+ throw new IllegalArgumentException("Invalid argument");
+
+ this.xf = xf;
+ }
+
+ /**
+ * Sets the user's RPC credential from Login name and password.
+ *
+ * Every NFS request includes a "credential" that identifies the user.
+ * An AUTH_SYS credential includes the user's UID and GID values.
+ * These are determined from the user's login name (and password)
+ * by the PCNFSD service that must be available on a local server.
+ * Once the credential is set, it is assigned globally to all
+ * future NFS XFile objects.
+ *
+ * If this method is not called, a default credential is assigned
+ * with a UID and GID of "nobody".
+ *
+ * @param
+ * Note: This credential setting method exposes an
+ * inherent security hole in RPC AUTH_SYS authentication.
+ * The server trusts the client to authenticate the
+ * user before setting the UID and GID values. It is
+ * possible for a malicious client to allow the UID and/or
+ * group ids to be set to allow unauthorized access to
+ * other user's files on the server.
+ *
+ * Servers can avoid this security hole by exporting NFS
+ * filesystem securely - requiring clients to use secure
+ * Diffie-Hellman or Kerberos credentials.
+ *
+ *
+ * If this method is not called, a default credential is assigned
+ * with a UID and GID of "nobody".
+ *
+ * @param
+ * The XFile object is functionally equivalent to the java.io.File
+ * object with the ability to handle not only native pathnames
+ * but also URL-style pathnames. URL pathnames have some advantages
+ * over native pathnames:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Pathnames that are not represented as URL names will be
+ * assumed to represent "native" names and XFile will present
+ * the same semantics as the java.io.File class.
+ */
+public class XFile {
+
+ /**
+ * File Accessor that implements the underlying filesystem
+ */
+ private XFileAccessor xfa;
+
+ /**
+ * The url of the file.
+ */
+ private XFurl url;
+ private String urlStr;
+ private File nativeFile;
+ private boolean bound;
+
+ /**
+ * Creates a
+ *
+ * If the
+ * If the
+ * If the object is a URL type, the path is the part
+ * of the URL following the location, e.g.
+ *
+ *
+ *
+ *
+ * If the object is represented by a URL then return the entire URL
+ * string.
+ *
+ * @return a system-dependent absolute pathname for this
+ *
+ * For native paths the precise definition of canonical form
+ * is system-dependent, but it usually specifies an absolute
+ * pathname in which all relative references and references
+ * to the current user directory have been completely
+ * resolved. The canonical form of a pathname of a nonexistent
+ * file may not be defined.
+ *
+ * @return the canonical path of the object
+ * @exception java.io.IOException If an I/O error occurs, which
+ * is possible because the construction of the
+ * canonical path may require filesystem queries.
+ */
+ public String getCanonicalPath() throws IOException {
+ if (nativeFile != null)
+ return nativeFile.getCanonicalPath();
+
+ return urlStr;
+ }
+
+
+ /**
+ * Returns the parent part of the pathname of this
+ *
+ * For native paths the parent part is generally everything
+ * leading up to the last occurrence of the separator character,
+ * although the precise definition is system dependent.
+ * On UNIX, for example, the parent part of
+ * The return value is system dependent and should only be used to
+ * compare with other values returned by last modified. It should
+ * not be interpreted as an absolute time.
+ *
+ * @return the time the file specified by this object was last
+ * modified, or
+ * A class file that implements this interface must be named
+ * "XFileAccessor" and be installed in a directory named after
+ * the URL scheme that it implements, for instance, an XFileAccessor
+ * that provides file access through the HTTP protocol would be
+ * associated with the "http" URL and its class file would be
+ * called:
+ *
+ * A class prefix is added to this name. The default prefix is
+ *
+ * The default class prefix
+ * For instance, if you want to use the "ftp"
+ * XFileAccessor from Acme, Inc and the "nfs" XFileAccessor
+ * from "ABC Inc." then you can set the system property as
+ * follows:
+ *
+ * The class loader attempts to load each of the constructed
+ * package names in turn relative to the CLASSPATH until it is
+ * successful.
+ *
+ * A subsequent reference to an "nfs" URL will result in
+ * the following list of candidate package names:
+ *
+ * <proto>://<location>/<path>
+ *
+ * where <proto> is the name of the filesystem,
+ * e.g. "nfs" and <location> is the location of
+ * the filesystem. For nfs this is the network name of
+ * a server. The <path> is a pathname that locates
+ * the file within <location>. As required by
+ * RFC 1738, the component delimiters in the pathname
+ * are as for URL syntax: forward slashes only.
+ * @param serial true if serial access; false if random access
+ * @param readOnly true if read only; false if read/write
+ */
+ boolean open(XFile xf, boolean serial, boolean readOnly);
+
+ /**
+ * Return the XFile for this Accessor
+ */
+ XFile getXFile();
+
+
+ /**
+ * Tests if this XFile object exists.
+ *
+ * @return
+ * A file is "normal" if it is not a directory and, in
+ * addition, satisfies other system-dependent criteria. Any
+ * non-directory file created by a Java application is
+ * guaranteed to be a normal file.
+ *
+ * @return
+ * @return the time the file specified by this object was last
+ * modified, or
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ void flush() throws IOException;
+
+
+ /**
+ * Close the file.
+ *
+ * Closes this file and releases any system resources
+ * associated with the file.
+ *
+ * After the file is closed further I/O operations may
+ * throw IOException.
+ *
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ void close() throws IOException;
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileExtensionAccessor.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileExtensionAccessor.java
new file mode 100644
index 0000000000..0425845d76
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileExtensionAccessor.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+/**
+ * This is an abstract class to intended to be extended with
+ * filesystem-specific methods.
+ *
+ * An XFileExtensionAccessor class must be associated with an
+ * XFileAccessor. An XFileExtensionAccessor can be used to
+ * provide access to filesystem-specific methods that are not
+ * defined within the XFileAccessor interface.
+ * A subclass of XFileExtensionAccessor must be declared as:
+ *
+ * public class XFileExtensionAccessor extends com.sun.xfile.XFileExtensionAccessor {
+ * :
+ *
+ *
+ * An XFileExtensionAccessor class is loaded when the
+ *
+ * An application that needs to use the methods within the
+ * XFileExtensionAccessor must cast the result of XFile.getExtensionAccessor.
+ *
+ * XFile xf = new XFile("ftp://server/path");
+ * com.acme.ftp.XFileExtensionAccessor xftp =
+ * (com.acme.ftp.XFileExtensionAccessor) xf.getExtensionAccessor();
+ * xftp.login();
+ * :
+ *
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void flush() throws IOException {
+ xfa.flush();
+ }
+
+
+ /**
+ * Closes this file output stream, flushes any buffered,
+ * unwritten data, and releases any system resources
+ * associated with this stream.
+ *
+ * After the file is closed further I/O operations may
+ * throw IOException.
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void close() throws IOException {
+ xfa.close();
+ }
+
+
+ /**
+ * Ensures that the
+ * The mode argument must either be equal to
+ *
+ * @param n the number of bytes to be skipped.
+ * @return the number of bytes skipped, which is always
+ *
+ * This method blocks until the byte is read, the end of the stream
+ * is detected, or an exception is thrown.
+ *
+ * @return the next byte of this file as a signed 8-bit
+ *
+ * This method blocks until the byte is read, the end of the stream
+ * is detected, or an exception is thrown.
+ *
+ * @return the next byte of this file, interpreted as an unsigned
+ * 8-bit number.
+ * @exception java.io.EOFException if this file has reached the end.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final int readUnsignedByte() throws IOException {
+ int ch = this.read();
+ if (ch < 0)
+ throw new EOFException();
+ return ch;
+ }
+
+
+ /**
+ * Reads a signed 16-bit number from this file. The method reads 2
+ * bytes from this file. If the two bytes read, in order, are
+ *
+ * This method blocks until the two bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next two bytes of this file, interpreted as a signed
+ * 16-bit number.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * two bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final short readShort() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (short)((ch1 << 8) + (ch2 << 0));
+ }
+
+
+ /**
+ * Reads an unsigned 16-bit number from this file. This method reads
+ * two bytes from the file. If the bytes read, in order, are
+ *
+ * This method blocks until the two bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next two bytes of this file, interpreted as an unsigned
+ * 16-bit integer.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * two bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final int readUnsignedShort() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (ch1 << 8) + (ch2 << 0);
+ }
+
+
+ /**
+ * Reads a Unicode character from this file. This method reads two
+ * bytes from the file. If the bytes read, in order, are
+ *
+ * This method blocks until the two bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next two bytes of this file as a Unicode character.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * two bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final char readChar() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (char)((ch1 << 8) + (ch2 << 0));
+ }
+
+
+ /**
+ * Reads a signed 32-bit integer from this file. This method reads 4
+ * bytes from the file. If the bytes read, in order, are
+ * This method blocks until the four bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next four bytes of this file, interpreted as an
+ *
+ * then the result is equal to:
+ *
+ * This method blocks until the eight bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next eight bytes of this file, interpreted as a
+ *
+ * This method blocks until the four bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next four bytes of this file, interpreted as a
+ *
+ * This method blocks until the eight bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next eight bytes of this file, interpreted as a
+ *
+ * A line of text is terminated by a carriage-return character
+ * (
+ * This method blocks until a newline character is read, a carriage
+ * return and the byte following it are read (to see if it is a
+ * newline), the end of the stream is detected, or an exception is thrown.
+ *
+ * @return the next line of text from this file.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final String readLine() throws IOException {
+ StringBuffer input = new StringBuffer();
+ int c;
+
+ while (((c = read()) != -1) && (c != '\n')) {
+ input.append((char)c);
+ }
+ if ((c == -1) && (input.length() == 0)) {
+ return null;
+ }
+ return input.toString();
+ }
+
+
+ /**
+ * Reads in a string from this file. The string has been encoded
+ * using a modified UTF-8 format.
+ *
+ * The first two bytes are read as if by
+ *
+ * This method blocks until all the bytes are read, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return a Unicode string.
+ * @exception java.io.EOFException if this file reaches the end before
+ * reading all the bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ * @exception java.io.UTFDataFormatException if the bytes do not represent
+ * valid UTF-8 encoding of a Unicode string.
+ * @see com.sun.xfile.XRandomAccessFile#readUnsignedShort()
+ */
+ public final String readUTF() throws IOException {
+ return DataInputStream.readUTF(this);
+ }
+
+
+ /**
+ * Writes a
+ * First, two bytes are written to the file as if by the
+ * true
if the file specified by this object
+ * exists; false
otherwise.
+ */
+ public boolean exists() {
+ try {
+ return nfs.exists();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Tests if the application can write to this file.
+ *
+ * @return true
if the application is allowed to
+ * write to a file whose name is specified by this
+ * object; false
otherwise.
+ */
+ public boolean canWrite() {
+ try {
+ return nfs.canWrite();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Tests if the application can read from the specified file.
+ *
+ * @return true
if the file specified by this
+ * object exists and the application can read the file;
+ * false
otherwise.
+ */
+ public boolean canRead() {
+ try {
+ return nfs.canRead();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Tests if the file represented by this
+ * object is a "normal" nfs.
+ * true
if the file specified by this
+ * XFile
object exists and is a "normal"
+ * file; false
otherwise.
+ */
+ public boolean isFile() {
+ try {
+ return nfs.isFile();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Tests if the file represented by this XFileAccessor
+ * object is a directory.
+ *
+ * @return true
if this XFileAccessor object
+ * exists and is a directory; false
+ * otherwise.
+ */
+ public boolean isDirectory() {
+ try {
+ return nfs.isDirectory();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns the time that the file represented by this
+ * XFile
object was last modified.
+ * 0L
if the specified file
+ * does not exist.
+ */
+ public long lastModified() {
+ try {
+ return nfs.mtime();
+ } catch (IOException e) {
+ return 0L;
+ }
+ }
+
+
+ /**
+ * Returns the length of the file represented by this
+ * XFileAccessor object.
+ *
+ * @return the length, in bytes, of the file specified by
+ * this object, or 0L
if the specified
+ * file does not exist.
+ */
+ public long length() {
+ try {
+ return nfs.length();
+ } catch (IOException e) {
+ return 0L;
+ }
+ }
+
+
+ /**
+ * Creates a file whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the file could be created;
+ * false
otherwise.
+ */
+ public boolean mkfile() {
+ try {
+ nfs = getParent(xf).create(xf.getName(), (long)0666);
+ return true;
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Creates a directory whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the directory could be created;
+ * false
otherwise.
+ */
+ public boolean mkdir() {
+ try {
+ nfs = getParent(xf).mkdir(xf.getName(), (long)0777);
+ return true;
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Renames the file specified by this XFileAccessor object to
+ * have the pathname given by the XFileAccessor object argument.
+ *
+ * @param dest the new filename.
+ * @return true
if the renaming succeeds;
+ * false
otherwise.
+ */
+ public boolean renameTo(XFile dest) {
+ try {
+ Nfs sParent = getParent(xf);
+ Nfs dParent = getParent(dest);
+
+ return sParent.rename(dParent, xf.getName(), dest.getName());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns a list of the files in the directory specified by
+ * this XFileAccessor object.
+ *
+ * @return an array of file names in the specified directory.
+ * This list does not include the current directory or
+ * the parent directory (".
" and
+ * "..
" on Unix systems).
+ */
+ public String[] list() {
+ try {
+ return nfs.readdir();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Deletes the file specified by this object. If the target
+ * file to be deleted is a directory, it must be empty for
+ * deletion to succeed.
+ *
+ * @return true
if the file is successfully deleted;
+ * false
otherwise.
+ */
+ public boolean delete() {
+ boolean ok;
+
+ try {
+ if (isFile())
+ ok = getParent(xf).remove(xf.getName());
+ else
+ ok = getParent(xf).rmdir(xf.getName());
+
+ // Purge cached attrs & filehandle
+
+ if (ok) {
+ nfs.invalidate();
+ nfs = null;
+ }
+
+ return ok;
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Reads a subarray as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ public int read(byte b[], int off, int len, long foff)
+ throws IOException {
+
+ int c = nfs.read(b, off, len, foff);
+ return c;
+ }
+
+
+ /**
+ * Writes a sub array as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ public void write(byte b[], int off, int len, long foff)
+ throws IOException {
+
+ nfs.write(b, off, len, foff);
+ }
+
+
+ /**
+ * Forces any buffered output bytes to be written out.
+ * host
The name of the host that runs the PCNFSD service.
+ * This does not have to be an NFS server.
+ * @param username
The user's login name.
+ * @param password
The user's password.
+ * This is obscured before transmission to the PCNFSD server.
+ * @return true if the login succeeded, false otherwise.
+ */
+ public boolean loginPCNFSD(String host, String username, String password) {
+ return NfsConnect.getCred().fetchCred(host, username, password);
+ }
+
+ /**
+ * Sets the user's RPC credential to "nobody"
+ */
+
+ public void logoutPCNFSD() {
+ NfsConnect.getCred().setCred();
+ }
+
+
+ /**
+ * Sets the user's RPC credential to a known uid/gid.
+ *
+ * Assumes that the calling application has already
+ * authenticated the user and has obtained to uid/gid
+ * itself.
+ * uid
The user-ID.
+ * @param gid
The group-ID.
+ * @param gids
The group-ID list.
+ */
+ public void loginUGID(int uid, int gid, int[] gids) {
+ NfsConnect.getCred().setCred(uid, gid, gids);
+ }
+
+ /**
+ * Sets the user's RPC credential to "nobody"
+ */
+
+ public void logoutUGID() {
+ NfsConnect.getCred().setCred();
+ }
+
+ /**
+ * Assigns an NfsHandler class that allows
+ * the application to receive RPC timeout
+ * notifications. The handler
is used for all
+ * NFS files accessed by the application.
+ * The default handler can be restored by
+ * passing a null handler
argument.
+ *
+ * @param handler
An instance of the NfsHandler class.
+ */
+ public void setNfsHandler(NfsHandler handler) {
+ NfsConnect.setRpcHandler(handler);
+ }
+
+ /**
+ * Get server's export list
+ */
+ public String[] getExports()
+ throws java.net.UnknownHostException, IOException {
+
+ return new Mount().getExports(new NfsURL(xf.toString()).getHost());
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/nfs/nfsXFileExtensionAccessor.java b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/nfsXFileExtensionAccessor.java
new file mode 100644
index 0000000000..01c50f5148
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/nfs/nfsXFileExtensionAccessor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.nfs;
+
+/**
+ * This is a compatibility stub for the NFS XFileExtensionAccessor
+ *
+ * @see com.sun.nfs.XFileExtensionAccessor
+ * @deprecated The more generic XFileExtensionAccessor class
+ * should be used instead.
+ */
+public class nfsXFileExtensionAccessor
+ extends com.sun.nfs.XFileExtensionAccessor {
+
+ public nfsXFileExtensionAccessor(com.sun.xfile.XFile xf) {
+ super(xf);
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/ConnectDatagram.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/ConnectDatagram.java
new file mode 100644
index 0000000000..100d56d529
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/ConnectDatagram.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+
+/**
+ * Sets up a UDP connection to the server.
+ * Since UDP is really connectionless, we
+ * don't really have a connection, so perhaps
+ * describing this as an association
+ * is more accurate.
+ *
+ * This class lets us transmit and receive buffers
+ * of data to a port on a remote server.
+ *
+ * @see Connection
+ * @author Brent Callaghan
+ */
+public class ConnectDatagram extends Connection {
+
+ DatagramSocket ds;
+ DatagramPacket dp;
+ InetAddress addr;
+
+ /**
+ * Construct a new connection to a specified server and port.
+ * @param server The hostname of the server
+ * @param port The port number on the server
+ * @param maxSize The maximum size in bytes of the received message
+ * @exception IOException if the server does not exist
+ */
+ public ConnectDatagram (String server, int port, int maxSize)
+ throws IOException {
+
+ super(server, port, "udp", maxSize);
+
+ ds = new DatagramSocket();
+ addr = InetAddress.getByName(server);
+ start();
+ }
+
+ void sendOne(Xdr x) throws IOException {
+
+ /*
+ * The interrupt call here is to break the listener
+ * thread from its socket read. For some unknown
+ * reason a datagram socket read blocks threads
+ * attempting to send. The interrupt unblocks the
+ * listener briefly so we can do the send.
+ *
+ * The blocking problem appears to be fixed as
+ * of JDK 1.1.6, so the interrupt is skipped removed.
+ */
+ //interrupt();
+
+ ds.send(new DatagramPacket(x.xdr_buf(), x.xdr_offset(), addr, port));
+ }
+
+ void receiveOne(Xdr x, int timeout) throws IOException {
+ ds.setSoTimeout(timeout);
+ dp = new DatagramPacket(x.xdr_buf(), x.xdr_buf().length);
+ ds.receive(dp);
+ }
+
+ InetAddress getPeer() {
+ return dp.getAddress();
+ }
+
+ /*
+ * No connection to drop.
+ */
+ void dropConnection() {
+ }
+
+ /*
+ * No connection to check
+ */
+ void checkConnection() {
+ }
+
+ protected void finalize() throws Throwable {
+ if (ds != null) {
+ ds.close();
+ ds = null;
+ }
+ super.finalize();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/ConnectSocket.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/ConnectSocket.java
new file mode 100644
index 0000000000..595cba8e2c
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/ConnectSocket.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+import java.net.Socket;
+import java.net.InetAddress;
+
+/**
+ * Sets up a TCP connection to the server.
+ *
+ * This class lets us transmit and receive buffers
+ * of data to a port on a remote server.
+ * It also handles reconnection of broken TCP links.
+ *
+ * @see Connection
+ * @author Brent Callaghan
+ */
+public class ConnectSocket extends Connection {
+
+ static final int LAST_FRAG = 0x80000000;
+ static final int SIZE_MASK = 0x7fffffff;
+ static final int MTUSZ = 1460 - 4; // good for Ethernet
+
+ private OutputStream outs;
+ private InputStream ins;
+ private Socket sock;
+ Xdr rcv_mark = new Xdr(4);
+
+ /**
+ * Construct a new connection to a specified server and port.
+ *
+ * @param server The hostname of the server
+ * @param port The port number on the server
+ * @param maxSize The maximum size of the received reply
+ * @exception IOException if the connection cannot be made
+ */
+ public ConnectSocket (String server, int port, int maxSize)
+ throws IOException {
+
+ super(server, port, "tcp", maxSize);
+ doConnect();
+ start(); // the listener
+ }
+
+ private void doConnect()
+ throws IOException {
+
+ if (server == null)
+ throw new java.net.UnknownHostException("null host");
+
+ sock = new Socket(server, port);
+ sock.setTcpNoDelay(true);
+ ins = sock.getInputStream();
+ outs = sock.getOutputStream();
+ }
+
+ private void doClose() throws IOException {
+ if (ins != null) {
+ ins.close();
+ ins = null;
+ }
+
+ if (outs != null) {
+ outs.close();
+ outs = null;
+ }
+
+ if (sock != null) {
+ sock.close();
+ sock = null;
+ }
+ }
+
+ void sendOne(Xdr x) throws IOException {
+ int recsize;
+ int lastbit = 0;
+ int bufsiz = x.xdr_offset();
+ int save;
+
+ /*
+ * Use the connection only if unlocked.
+ * Otherwise we risk attempting a sendOne()
+ * while it's being reconnected. We also
+ * need to protect threads from a concurrent
+ * sendOne that may interleave record data.
+ */
+ synchronized (this) {
+ /*
+ * The XDR buffer needs to be transmitted on the
+ * socket outputstream in MTUSZ records. In RPC
+ * over TCP each of these records begins with a
+ * 32 bit record mark which comprises a byte
+ * count for the record and a LAST_FRAG bit which
+ * is set on the last record.
+ *
+ * You'll notice that the code goes through some
+ * hoops to prepend this record mark on each of
+ * the records transmitted from the XDR buffer
+ * WITHOUT COPYING THE DATA.
+ * Notice that there's a 4 octet space inserted at
+ * the front of the buffer for the first record
+ * mark by the code that builds the RPC header.
+ * Space is taken from the buffer for subsequent
+ * record marks and the data in this space is
+ * saved and restored after the record is sent,
+ * so hopefully we leave things as they were
+ * when we're done.
+ *
+ * BTW: this code originally wrote the record
+ * mark to the OutputStream separately, but the
+ * JVM doesn't seem to be doing Nagle and the
+ * record mark sailed off in its own tiny TCP
+ * segment separate from the rest of the record,
+ * which wasn't what I had in mind.
+ */
+ for (int off = 4; off < bufsiz; off += recsize) {
+ /*
+ * Insert the record mark
+ */
+ recsize = bufsiz - off;
+ if (recsize > MTUSZ)
+ recsize = MTUSZ;
+ if ((off + recsize) >= bufsiz)
+ lastbit = LAST_FRAG;
+ x.xdr_offset(off-4);
+ save = x.xdr_int();
+ x.xdr_offset(off-4);
+ x.xdr_int(lastbit | recsize);
+
+ /*
+ * then send the record data
+ */
+ outs.write(x.xdr_buf(), off-4, recsize+4);
+ outs.flush();
+ x.xdr_offset(off-4);
+ x.xdr_int(save);
+ }
+ x.xdr_offset(bufsiz); // restore XDR offset
+ }
+ }
+
+ void receiveOne(Xdr x, int timeout) throws IOException {
+ int off;
+ int rcount;
+ boolean lastfrag = false;
+ long recsize;
+
+ sock.setSoTimeout(timeout);
+
+ try {
+ for (off = 0; !lastfrag; off += recsize) {
+ /*
+ * Read the record mark
+ */
+ if (ins.read(rcv_mark.xdr_buf()) != 4)
+ throw new IOException("TCP record mark: lost connection");
+ rcv_mark.xdr_offset(0);
+ recsize = rcv_mark.xdr_u_int();
+ lastfrag = (recsize & LAST_FRAG) != 0;
+ recsize &= SIZE_MASK;
+
+ /*
+ * then read the record data
+ */
+ for (int i = 0; i < recsize; i += rcount) {
+ rcount = ins.read(x.xdr_buf(), off + i, (int) recsize - i);
+ if (rcount < 0)
+ throw new IOException("TCP data: lost connection");
+ }
+ }
+ x.xdr_size(off);
+ } catch (java.io.InterruptedIOException e) {
+ throw e;
+
+ } catch (IOException e) {
+ /*
+ * Assume something bad happened to the connection.
+ * Close the connection and attempt to reconnect.
+ */
+ reconnect();
+ throw e;
+ }
+ }
+
+ /*
+ * Get the address of the caller.
+ * This is needed when we get a reply from
+ * and RPC to a broadcast address.
+ */
+ InetAddress getPeer() {
+ return sock.getInetAddress();
+ }
+
+ /*
+ * This method is called when it is suspected that
+ * the connection has been broken. It keeps retrying
+ * connection attempts until it is successful.
+ */
+ void reconnect() {
+
+ System.err.println("Lost connection to " + server +
+ " - attempting to reconnect");
+
+ /*
+ * Lock the connection while we're messing
+ * with it so that another thread can't
+ * attempt a sendOne() on it.
+ */
+ synchronized (this) {
+ while (true) {
+ try {
+ doClose(); // make sure we're at a known state
+ doConnect();
+ break; // success
+
+ } catch (IOException e) {
+
+ /*
+ * Wait here for 5 sec so's we don't
+ * overwhelm the server with connection requests.
+ */
+ try {
+ java.lang.Thread.sleep(5000);
+ } catch (java.lang.InterruptedException i) {
+ }
+ }
+ }
+ }
+
+ System.err.println("Reconnected to " + server);
+ }
+
+ /*
+ * The listener calls this after an idle timeout.
+ * Be kind to the server and drop the connection.
+ */
+ void dropConnection() {
+ try {
+ doClose();
+ } catch (IOException e) {};
+ }
+
+ /*
+ * Check to make sure that the connection is up.
+ * If not, then reconnect and resume the listener.
+ */
+ void checkConnection() {
+ if (sock != null)
+ return;
+
+ reconnect();
+ }
+
+ protected void finalize() throws Throwable {
+ doClose();
+ super.finalize();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Connection.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Connection.java
new file mode 100644
index 0000000000..5a37acc587
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Connection.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+import java.util.Hashtable;
+import java.net.InetAddress;
+
+/**
+ * Sets up a connection to the server using
+ * either UDP or TCP as determined by the
+ * subclass.
+ *
+ * This class also handles the connection caching.
+ *
+ * @see ConnectSocket
+ * @see ConnectDatagram
+ * @author Brent Callaghan
+ */
+public abstract class Connection extends Thread {
+
+ static Hashtable connections = new Hashtable();
+ public String server;
+ public int port;
+ String proto;
+ Hashtable waiters = new Hashtable();
+ static final int IDLETIME = 300 * 1000; // idle connection after 5 min
+ int xid; // transaction id
+ Xdr reply;
+ int maxSize; // size of reply Xdr buffer
+ Error err; // might get thrown by the thread
+
+ /**
+ * Construct a new connection to a specified server
+ * and port using protocol proto with a
+ * reply buffer of size maxsize.
+ *
+ * @param server The hostname of the server
+ * @param port The port number on the server
+ */
+ public Connection (String server, int port, String proto, int maxSize) {
+ this.server = server;
+ this.port = port;
+ this.proto = proto;
+ this.maxSize = maxSize;
+
+ setName("Listener-" + server);
+ setDaemon(true);
+ }
+
+ /**
+ * Get a cached connection for the specified server, port and protocol
+ *
+ * @param server The hostname of the server
+ * @param port The port number on the server
+ * @param proto The connection type: "tcp" or "udp"
+ * @returns null If there is no cached connection
+ */
+ public static Connection getCache(String server, int port, String proto) {
+ Connection conn = (Connection) connections.get(
+ server + ":" + port + ":" + proto);
+
+ return conn;
+ }
+
+ /**
+ * Stash a new connection in the cache
+ *
+ * @param The connection to be cached
+ */
+ public static void putCache(Connection conn) {
+ connections.put(conn.server + ":" + conn.port + ":" + conn.proto, conn);
+ }
+
+ abstract void sendOne(Xdr call) throws IOException;
+
+ abstract void receiveOne(Xdr reply, int timeout) throws IOException;
+
+ abstract InetAddress getPeer();
+
+ abstract void dropConnection();
+
+ abstract void checkConnection();
+
+ /**
+ * Return information about the connection
+ *
+ * @returns server, port number and protocol info.
+ */
+ public String toString() {
+ return (server + ":" + port + ":" + proto);
+ }
+
+ private boolean running;
+
+ synchronized void suspendListener() {
+ running = false;
+
+ while (!running) {
+ try {
+ wait();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ synchronized void resumeListener() {
+ running = true;
+ notifyAll();
+ }
+
+ synchronized Xdr send(Xdr call, int timeout)
+ throws IOException {
+
+ checkConnection();
+ resumeListener();
+ sendOne(call);
+
+ waiters.put(new Integer(call.xid), new Integer(timeout));
+
+ /*
+ * Now sleep until the listener thread posts
+ * my XID and notifies me - or I time out.
+ */
+ while (xid != call.xid) {
+ long t = System.currentTimeMillis();
+
+ if (err != null)
+ throw err;
+
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {}
+
+ if (err != null)
+ throw err;
+
+ timeout -= (System.currentTimeMillis() - t);
+ if (timeout <= 0) {
+ waiters.remove(new Integer(call.xid));
+ throw new InterruptedIOException(); // timed out
+ }
+ }
+
+ /*
+ * My reply has come in.
+ */
+ xid = 0;
+ waiters.remove(new Integer(call.xid));
+ notifyAll(); // wake the listener
+
+ return reply;
+ }
+
+ /*
+ * This is the code for the listener thread.
+ * It blocks in a receive waiting for an RPC
+ * reply to come in, then delivers it to the
+ * appropriate thread.
+ */
+ public void run() {
+
+ try {
+ while (true) {
+
+ synchronized (this) {
+ while (xid != 0) {
+ try {
+ wait();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ reply = new Xdr(maxSize);
+
+ /*
+ * The listener thread now blocks reading
+ * from the socket until either a packet
+ * comes in - or it gets an idle timeout.
+ */
+ try {
+ receiveOne(reply, IDLETIME);
+ } catch (InterruptedIOException e) {
+
+ /*
+ * Got an idle timeout. If there's
+ * no threads waiting then drop the
+ * connection and suspend.
+ */
+ if (waiters.isEmpty())
+ dropConnection();
+ suspendListener();
+ } catch (IOException e) {
+ continue;
+ }
+
+ /*
+ * Have received an Xdr buffer.
+ * Extract the xid and check the hashtable
+ * to see if there's thread waiting for that reply.
+ * If there is, then notify the thread. If not
+ * then ignore the reply (its thread may
+ * have timed out and gone away).
+ */
+ synchronized (this) {
+ xid = reply.xdr_int();
+ if (waiters.containsKey(new Integer(xid)))
+ notifyAll();
+ else
+ xid = 0; // ignore it
+ }
+ }
+ } catch (Error e) {
+ /*
+ * Need to catch errors here, e.g. OutOfMemoryError
+ * and notify threads before this listener thread dies
+ * otherwise they'll wait forever.
+ */
+ this.err = e;
+ synchronized (this) {
+ notifyAll();
+ }
+ throw e;
+ }
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Cred.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Cred.java
new file mode 100644
index 0000000000..c6a9c5f13d
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Cred.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+/**
+ * RPC Credentials
+ *
+ * Extended by each credential class
+ */
+
+public abstract class Cred {
+
+ // service types: authentication, integrity and privacy
+ public static final int SVC_NONE = 1;
+ public static final int SVC_INTEGRITY = 2;
+ public static final int SVC_PRIVACY = 3;
+
+ /**
+ * Put creds into an XDR buffer
+ */
+ abstract void putCred(Xdr x) throws RpcException;
+
+ /**
+ * Get creds from an XDR buffer
+ */
+ abstract void getCred(Xdr x);
+
+ /**
+ * Initiate a security context with peers
+ */
+ abstract void init(Connection conn, int prog, int vers)
+ throws RpcException;
+
+ /**
+ * Refresh the cred
+ */
+ abstract boolean refresh(Connection conn, int prog, int vers);
+
+ /**
+ * Encrypt an XDR buffer
+ */
+ abstract void wrap(Xdr x, byte[] arg) throws RpcException;
+
+ /**
+ * Descrypt an XDR buffer
+ */
+ abstract int unwrap(Xdr x) throws RpcException;
+
+ /**
+ * Validate the response verifier from server
+ */
+ abstract void validate(byte[] verifier, int verifiee)
+ throws RpcException;
+
+ /**
+ * Destroy the cred data and its security context with the server
+ */
+ abstract void destroy(Rpc rpc) throws RpcException;
+
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredGss.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredGss.java
new file mode 100644
index 0000000000..eb982f327b
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredGss.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+import com.sun.gssapi.*;
+
+/**
+ * The credential class for the RPCSEC_GSS security flavor.
+ *
+ * @see Cred
+ * @author Lin Ling
+ *
+ */
+
+public class CredGss extends Cred {
+
+ /*
+ * These are the data needed for setting up RPCSEC_GSS dialog.
+ */
+ public int serviceType; // authenticate, integrity or privacy
+
+ Oid mechOid;
+ int qop;
+ String serviceName; // e.g. "nfs" is a service name
+ int seq_num_out; // sequence number in the out going request
+ int seq_window;
+ int control; // RPCSEC_GSS_INIT or RPCSEC_GSS_DATA ...etc
+ GSSContext gssCtx; // context object for gss operations
+ byte[] ctx_handle; // context handle for the security context
+
+ public static final int RPCSEC_GSS = 6;
+ public static final int RPCSEC_GSS_DATA = 0;
+ public static final int RPCSEC_GSS_INIT = 1;
+ public static final int RPCSEC_GSS_CONTINUE_INIT = 2;
+ public static final int RPCSEC_GSS_DESTROY = 3;
+
+ public static final int RPCSEC_GSS_VERS_1 = 1;
+
+ private static final int RPCGSS_MAXSZ = 1024;
+ private static final int PROC_NULL = 0;
+
+
+ /**
+ * Constructor creates an instance of RPCSEC_GSS credential with
+ * given service name, mechanism, service type and qop number.
+ *
+ * @param svcName the target service name
+ * @param mech the string format of mech oid; e.g. "1.2.3.4.5"
+ * @param svcType none, integrity or privacy
+ * @param qop_num the number of quality protection
+ */
+ public CredGss(String svcName, String mech, int svcType, int qop_num)
+ throws IOException {
+
+ try {
+ mechOid = new Oid(mech);
+ } catch (GSSException e) {
+ throw new IOException ("can not construct CredGss object");
+ }
+ serviceName = svcName;
+ serviceType = svcType;
+ qop = qop_num;
+ seq_num_out = 0;
+ ctx_handle = null;
+ gssCtx = null;
+ control = RPCSEC_GSS_INIT;
+ }
+
+ /**
+ * Constructor creates an instance of RPCSEC_GSS credential with
+ * given service name, mechanism, service type and qop number.
+ *
+ * @param svcName the target service name
+ * @param mech the GSS Oid object of the mech
+ * @param svcType none, integrity or privacy
+ * @param qop_num the number of quality protection
+ */
+ public CredGss(String svcName, Oid mech, int svcType, int qop_num) {
+
+ mechOid = mech;
+ serviceName = svcName;
+ serviceType = svcType;
+ qop = qop_num;
+ seq_num_out = 0;
+ ctx_handle = null;
+ gssCtx = null;
+ control = RPCSEC_GSS_INIT;
+ }
+
+ /**
+ * Put RPCSEC_GSS cred/verf into an XDR buffer
+ *
+ * @param xdr buffer
+ */
+ synchronized void putCred(Xdr x) throws RpcException {
+
+ MessageProp mInfo = new MessageProp(qop, false);
+
+ /*
+ * Marshalling the cred field
+ */
+ x.xdr_int(RPCSEC_GSS);
+
+ /*
+ * For every data request (including retransmit)
+ * use a different sequence number.
+ */
+ if (control == RPCSEC_GSS_DATA || control == RPCSEC_GSS_DESTROY)
+ seq_num_out++;
+
+ /*
+ * If a context is established, encode the context handle.
+ * otherwise, encode a 0 length field.
+ */
+ if (ctx_handle != null) {
+ // length = 20 + ctx_handle.length
+ x.xdr_int(20 + ctx_handle.length);
+ x.xdr_int(RPCSEC_GSS_VERS_1);
+ x.xdr_int(control);
+ x.xdr_int(seq_num_out);
+ x.xdr_int(serviceType);
+ x.xdr_bytes(ctx_handle);
+ } else {
+ // length = 20
+ x.xdr_int(20);
+ x.xdr_int(RPCSEC_GSS_VERS_1);
+ x.xdr_int(control);
+ x.xdr_int(seq_num_out);
+ x.xdr_int(serviceType);
+ x.xdr_int(0);
+ }
+
+ /*
+ * Marshalling the verifier field
+ */
+ if (gssCtx != null) {
+ // Checksum the header data upto cred field.
+ try {
+ byte[] headerMIC = gssCtx.getMIC(x.xdr_buf(), 0,
+ x.xdr_offset(), mInfo);
+ x.xdr_int(RPCSEC_GSS);
+ x.xdr_bytes(headerMIC);
+ } catch (GSSException e) {
+ throw new RpcException("can not checksum the header");
+ }
+ } else {
+ // if context is not established yet, use null verifier
+ x.xdr_int(CredNone.AUTH_NONE);
+ x.xdr_int(0);
+ }
+
+ x.xdr_wrap_offset(x.xdr_offset());
+ if (control == RPCSEC_GSS_DATA && serviceType != SVC_NONE) {
+ x.xdr_int(seq_num_out);
+ }
+ }
+
+ public void getCred(Xdr x) {
+ // No-Op
+ }
+
+ /*
+ * Send rpcsec_gss init control requests. Retry if time out.
+ *
+ * This routine is similar to rpc.rpc_call() but is customized for the
+ * rpcsec_gss init sec context handshakes.
+ *
+ * For init control requests, when it needs to do a refresh, it does
+ * not need to retry the original init call after the cred is refreshed.
+ * (upon a successful refresh, a context is established)
+ * Use this routine instead of rpc_call() to avoid confusion.
+ */
+ private Xdr rpc_send(Rpc rpc, Xdr call, int timeout, int retries)
+ throws RpcException, IOException {
+
+ Xdr reply = null;
+
+ if (retries == 0) {
+ retries = Integer.MAX_VALUE; //retry forever
+ }
+
+ for (int c = 0; c < retries; c++) {
+
+ /*
+ * This is for init control requests, not a data request.
+ * No argument for wrapping.
+ */
+ try {
+ reply = rpc.rpc_call_one(call, null, timeout);
+ break; // reply received OK
+
+ } catch (RpcException e) {
+ throw e;
+
+ } catch (IOException e) {
+ // probably a timeout. retry
+ continue;
+ }
+ }
+
+ if (reply == null) // reached retry limit
+ throw new InterruptedIOException();
+
+ return reply;
+ }
+
+ /**
+ * Init a security context using the given connection instance.
+ *
+ * @param conn The connection to the server
+ * @param prog The program number of the rpc service
+ * @param vers The version number of the rpc service
+ */
+ synchronized void init(Connection conn, int prog, int vers)
+ throws RpcException {
+
+ byte[] inTok = new byte[0];
+ Rpc secRpc;
+ Xdr secCall, secReply;
+ CredGss initCred;
+ int major = 0, minor = 0;
+
+ try {
+ GSSContext ctx = new GSSContext(new GSSName(serviceName,
+ GSSName.NT_HOSTBASED_SERVICE),
+ mechOid, null, 0);
+
+ // set context options
+ ctx.requestConf(true);
+ ctx.requestInteg(true);
+ ctx.requestMutualAuth(true);
+ ctx.requestReplayDet(true);
+ ctx.requestSequenceDet(true);
+
+ initCred = new CredGss(serviceName, mechOid, serviceType, qop);
+ secRpc = new Rpc(conn, prog, vers, initCred);
+ secCall = new Xdr(RPCGSS_MAXSZ);
+
+ /*
+ * gss token exchange semantics:
+ *
+ * (1 token)
+ * Client Server
+ * init stat = complete
+ * token length > 0 ---> token --> accept stat = complete
+ * token.len = 0
+ *
+ * (2 tokens)
+ * Client Server
+ * init stat = cont
+ * token.length > 0 ---> token ---> accept stat = complete
+ * token.len > 0
+ * <--- token <---
+ * init stat = complete
+ * token.length = 0
+ *
+ * (3 tokens)
+ * Client Server
+ * init stat = cont
+ * token.length > 0 ---> token ---> accept stat = cont
+ * token.length > 0
+ * <--- token <---
+ * init stat = complete
+ * token.length > 0 ---> token ---> accept stat = complete
+ * token.length = 0
+ */
+ initCred.control = RPCSEC_GSS_INIT;
+ int num_refresh = 2;
+ do {
+ byte[] outTok = ctx.init(inTok, 0, inTok.length);
+
+ if (outTok != null) {
+ secRpc.rpc_header(secCall, PROC_NULL);
+ secCall.xdr_bytes(outTok);
+
+ try {
+ secReply = rpc_send(secRpc, secCall, 30 * 1000, 5);
+
+ } catch (MsgRejectedException e) {
+
+ if (num_refresh > 0 &&
+ (e.why == MsgRejectedException.RPCSEC_GSS_NOCRED ||
+ e.why == MsgRejectedException.RPCSEC_GSS_FAILED)) {
+
+ // reset the parameters and retry (refresh)
+ inTok = new byte[0];
+ ctx = new GSSContext(new GSSName(serviceName,
+ GSSName.NT_HOSTBASED_SERVICE),
+ mechOid, null, 0);
+ ctx.requestConf(true);
+ ctx.requestInteg(true);
+ ctx.requestMutualAuth(true);
+ ctx.requestReplayDet(true);
+ ctx.requestSequenceDet(true);
+ initCred.gssCtx = null;
+ initCred.ctx_handle = null;
+ initCred.control = RPCSEC_GSS_INIT;
+ num_refresh--;
+ continue;
+
+ } else {
+ throw e;
+ }
+ } catch (RpcException e) {
+ throw e;
+ }
+
+ // decode the result and get the context id
+ this.ctx_handle = secReply.xdr_bytes();
+ major = secReply.xdr_int();
+ minor = secReply.xdr_int();
+ if (major != GSSContext.COMPLETE &&
+ major != GSSContext.CONTINUE_NEEDED) {
+
+ throw new RpcException("cred.init server failed");
+ }
+
+ this.seq_window = secReply.xdr_int();
+ inTok = secReply.xdr_bytes(); // token from the server
+
+ if (!ctx.isEstablished() && inTok == null) {
+ throw new RpcException("cred.init:bad token");
+ }
+
+ } else if (major == GSSContext.CONTINUE_NEEDED) {
+ // no more token, but server is waiting for one
+ throw new RpcException("cred.init:server needs token");
+ }
+
+ initCred.control = RPCSEC_GSS_CONTINUE_INIT;
+ initCred.ctx_handle = ctx_handle;
+
+ } while (!ctx.isEstablished());
+
+ this.gssCtx = ctx;
+ this.control = RPCSEC_GSS_DATA;
+
+ } catch (IOException e) {
+ throw new RpcException("cred.init: io errors ");
+ } catch (GSSException e) {
+ throw new RpcException("cred.init: gss errors");
+ }
+ }
+
+ /**
+ * Refresh the RPCSEC_GSS credential.
+ * Nulled context and ctx_handle and re-try init sec context.
+ *
+ * @param conn The connection to the server
+ * @param prog The program number of the rpc service
+ * @param vers The version number of the rpc service
+ * @return true if success
+ */
+ synchronized boolean refresh(Connection conn, int prog, int vers) {
+
+ // If no context has established, don't try to recreate it.
+ if (ctx_handle == null) {
+ return false;
+ }
+
+ gssCtx = null;
+ ctx_handle = null;
+ try {
+ init(conn, prog, vers);
+ return true;
+ } catch (RpcException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Use this cred object to encrypt the given xdr buffer.
+ *
+ * @param call xdr buffer
+ * @param arg the rpc argument to be encrypted
+ * @return the xdr buffer with the encrypted data
+ */
+ synchronized void wrap(Xdr call, byte[] arg) throws RpcException {
+ byte[] argTok;
+ MessageProp mInfo = new MessageProp(qop, false);
+
+ if (control != RPCSEC_GSS_DATA) {
+ return;
+ }
+
+ try {
+ switch (serviceType) {
+ case SVC_NONE:
+ break;
+
+ case SVC_INTEGRITY:
+ argTok = gssCtx.getMIC(arg, 0, arg.length, mInfo);
+ call.xdr_offset(call.xdr_wrap_offset());
+ call.xdr_bytes(arg);
+ call.xdr_bytes(argTok);
+ break;
+
+ case SVC_PRIVACY:
+ mInfo.setPrivacy(true);
+ argTok = gssCtx.wrap(arg, 0, arg.length, mInfo);
+ call.xdr_offset(call.xdr_wrap_offset());
+ call.xdr_bytes(argTok);
+ break;
+ }
+
+ } catch (GSSException e) {
+ throw new RpcException("wrap: Can not wrap RPC arg");
+ }
+ }
+
+ /**
+ * Use this cred object to decrypt the given xdr buffer.
+ *
+ * @param reply xdr buffer
+ * @return the xdr buffer with the unencrypted data
+ */
+ synchronized int unwrap(Xdr reply) throws RpcException {
+ int result_off, result_len, verify_off, csum_len, seq_num_in = 0;
+ byte[] result;
+ MessageProp mInfo = new MessageProp();
+
+ if (control != RPCSEC_GSS_DATA) {
+ return 0;
+ }
+
+ result_off = reply.xdr_offset();
+
+ switch (serviceType) {
+ case SVC_NONE:
+ return 0;
+
+ case SVC_INTEGRITY:
+ result_len = reply.xdr_int();
+ if (result_len <= 4) // length of (seq num + rpc arg) is 0 or 4
+ return 0;
+
+ verify_off = reply.xdr_offset(); // offset of (seq num + rpc arg)
+ seq_num_in = reply.xdr_int();
+ /*
+ * When server is slow, it is possible that client will
+ * receive packets in different order. Seqence window should be
+ * the maximum number of client requests that maybe outstanding
+ * for this context. The this.seq_window is set to the sequence
+ * window length supported by the server for this context.
+ */
+ if ((seq_num_in < (seq_num_out - seq_window)) ||
+ (seq_num_in > seq_num_out)) {
+ throw new RpcException("unwrap: bad sequence number");
+ }
+
+ result = reply.xdr_raw(result_len - 4); // 4-length of seq num
+ csum_len = reply.xdr_int();
+
+ try {
+ gssCtx.verifyMIC(reply.xdr_buf(), reply.xdr_offset(),
+ csum_len, reply.xdr_buf(), verify_off,
+ result_len, mInfo);
+ } catch (GSSException e) {
+ throw new RpcException("unwrap: gss_verifyMIC failed");
+ }
+
+ if (mInfo.getQOP() != qop) {
+ throw new RpcException("unwrap: unexpected qop");
+ }
+
+ reply.xdr_offset(result_off);
+ reply.xdr_raw(result);
+ reply.xdr_offset(result_off);
+ break;
+
+ case SVC_PRIVACY:
+ result_len = reply.xdr_int();
+ if (result_len == 0)
+ return 0;
+
+ try {
+ result = gssCtx.unwrap(reply.xdr_buf(), reply.xdr_offset(),
+ result_len, mInfo);
+ } catch (GSSException e) {
+ throw new RpcException("unwrap: gss_unwrap failed");
+ }
+
+ if (mInfo.getQOP() != qop) {
+ throw new RpcException("unwrap: unexpected qop");
+ }
+
+ reply.xdr_offset(result_off);
+ reply.xdr_raw(result);
+ reply.xdr_offset(result_off);
+ seq_num_in = reply.xdr_int();
+ if ((seq_num_in < (seq_num_out - seq_window)) ||
+ (seq_num_in > seq_num_out)) {
+ throw new RpcException("unwrap: bad sequence number");
+ }
+ break;
+ }
+
+ return seq_num_in;
+ }
+
+
+ /**
+ * Validate RPC response verifier from server. The response verifier
+ * is the checksum of the request sequence number.
+ *
+ * @param snumber the sequence number
+ * @param token the verifier
+ */
+ synchronized void validate(byte[] token, int snumber)
+ throws RpcException {
+
+ if (control != RPCSEC_GSS_DATA)
+ return;
+
+ MessageProp mInfo = new MessageProp();
+
+ // create a buffer for the sequence number in the net order
+ byte[] msg = new byte[4];
+ msg[0] = (byte)(snumber >>> 24);
+ msg[1] = (byte)(snumber >> 16);
+ msg[2] = (byte)(snumber >> 8);
+ msg[3] = (byte)snumber;
+
+ try {
+ gssCtx.verifyMIC(token, 0, token.length,
+ msg, 0, msg.length, mInfo);
+ } catch (GSSException e) {
+ throw new RpcException("CredGss: validate failed");
+ }
+ }
+
+ /**
+ * Delete the RPC credential data and destroy its security
+ * context with the server.
+ *
+ * @param rpc delete the security context of this Rpc object
+ */
+ synchronized void destroy(Rpc rpc)
+ throws RpcException {
+
+ if (gssCtx != null) {
+ try {
+ Xdr secCall = new Xdr(RPCGSS_MAXSZ);
+ control = CredGss.RPCSEC_GSS_DESTROY;
+ rpc.rpc_header(secCall, PROC_NULL);
+ rpc.rpc_call(secCall, 30 * 1000, 5);
+ gssCtx.dispose();
+ mechOid = null;
+ gssCtx = null;
+ ctx_handle = null;
+ } catch (IOException e) {
+ } catch (GSSException e) {
+ /*
+ * If the request to destroy the context fails for some
+ * reason, the client need not take any special action.
+ * The server must be prepared to deal with situations
+ * where clients never inform the server to maintain
+ * a context.
+ */
+ return;
+ }
+ }
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredNone.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredNone.java
new file mode 100644
index 0000000000..8221fa3a91
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredNone.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+
+/**
+ * This is the "NONE" credential, i.e. no credential
+ * It's the default credential for RPC unless set
+ * to something else.
+ */
+
+public class CredNone extends Cred {
+
+ static final int AUTH_NONE = 0;
+
+ /**
+ * Put "no" creds into an XDR buffer
+ */
+ void putCred(Xdr x) {
+
+ x.xdr_int(AUTH_NONE);
+ x.xdr_int(0); // no cred data
+ x.xdr_int(0); // no verifier
+ x.xdr_int(0); // no verifier
+ }
+
+ /**
+ * Get "no" creds from an XDR buffer
+ */
+ void getCred(Xdr x) {
+
+ x.xdr_int(); // assume it's AUTH_NONE
+ x.xdr_int(); // cred length == 0
+ x.xdr_int(); // no verifier
+ x.xdr_int(); // no verifier
+ }
+
+ void init(Connection conn, int prog, int vers) {
+ // No-op
+ }
+
+
+ boolean refresh(Connection conn, int prog, int vers) {
+ // No-op
+ return true;
+ }
+
+ void wrap(Xdr x, byte[] arg) {
+ // No-op
+ }
+
+ int unwrap(Xdr x) {
+ // No-op
+ return 0;
+ }
+
+ void validate(byte[] verifier, int verifiee) {
+ // No-op
+ }
+
+ void destroy(Rpc rpc) {
+ // No-op
+ }
+
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredUnix.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredUnix.java
new file mode 100644
index 0000000000..9c1bd5e5f8
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/CredUnix.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+
+/**
+ * The Unix credential. Contains information specific
+ * to Unix users and NFS: uid/gid/grplist
+ */
+
+public class CredUnix extends Cred {
+
+ /*
+ * These are the data normally
+ * found in a Unix credential.
+ */
+ private int uid;
+ private int gid;
+ private int[] gids;
+
+ /*
+ * These are additional info provided by
+ * version 2 of PCNFSD.
+ */
+ private String home;
+ private int def_umask;
+ public int status;
+
+ /*
+ * Default credential values
+ */
+ static final int AUTH_UNIX = 1;
+ static final int UID_NOBODY = 60001;
+ static final int GID_NOBODY = 60001;
+
+ /*
+ * Constants for PCNFSD protocol
+ */
+ private static final int PCNFSDPROG = 150001;
+ private static final int PCNFSD_AUTH = 1;
+ private static final int PCNFSD2_AUTH = 13;
+ private static final int MAXREPLY = 512;
+
+ static final int AUTH_RES_OK = 0;
+ static final int AUTH_RES_FAKE = 1;
+ static final int AUTH_RES_FAIL = 2;
+
+ private Xdr cr = new Xdr(64);
+
+ /**
+ * Constructor creates an instance of
+ * Unix credential with given uid/gid
+ */
+ public CredUnix(int uid, int gid) {
+ this.uid = uid;
+ this.gid = gid;;
+ }
+
+ /**
+ * Constructor creates an instance of
+ * Unix credential and sets default uid/gid
+ * to "nobody".
+ */
+ public CredUnix() {
+ this(UID_NOBODY, GID_NOBODY);
+ }
+
+ /**
+ * Put Unix creds into an XDR buffer
+ *
+ * @param xdr buffer
+ */
+ synchronized void putCred(Xdr x) {
+
+ x.xdr_int(AUTH_UNIX);
+
+ cr.xdr_offset(0);
+ cr.xdr_int((int) (System.currentTimeMillis()/1000));
+ cr.xdr_string("javaclient");
+ cr.xdr_int(uid);
+ cr.xdr_int(gid);
+ if (gids == null)
+ cr.xdr_int(0);
+ else {
+ cr.xdr_int(gids.length);
+ for (int i = 0; i < gids.length; i++)
+ cr.xdr_int(gids[i]);
+ }
+
+ x.xdr_bytes(cr);
+
+ x.xdr_int(0); // no verifier
+ x.xdr_int(0); // no verifier
+ }
+
+ /**
+ * Get Unix creds from an XDR buffer
+ *
+ * @param xdr buffer
+ */
+ void getCred(Xdr x) {
+
+ x.xdr_int(); // assume it's AUTH_UNIX
+ x.xdr_int(); // cred length
+ x.xdr_int(); // timestamp
+ x.xdr_string(); // hostname
+ uid = x.xdr_int();
+ gid = x.xdr_int();
+ int count = x.xdr_int();
+ if (count > 0) {
+ gids = new int[count];
+ for (int i = 0; i < count; i++)
+ gids[i] = x.xdr_int();
+ }
+ x.xdr_int(); // no verifier
+ x.xdr_int(); // no verifier
+ }
+
+ /**
+ * Given a username and passwd, obtain Unix creds
+ * from the named server. This is not necessarily
+ * an NFS server.
+ *
+ * If we fail then the creds are unaffected.
+ *
+ * @param server Name of the pcnfsd server that will return the creds.
+ * @param username the login name of the user.
+ * @param passwd of the user.
+ *
+ */
+ public boolean fetchCred(String server, String username, String passwd) {
+
+ username = disguise(username);
+ passwd = disguise(passwd);
+
+ try {
+ try {
+ return (callV2(server, username, passwd));
+ } catch (MsgAcceptedException e) {
+ if (e.error != e.PROG_MISMATCH)
+ return false;
+
+ return (callV1(server, username, passwd));
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Set the cred back to the default: nobody/nobody
+ */
+ public void setCred() {
+ uid = UID_NOBODY;
+ gid = GID_NOBODY;
+ gids = null;
+ }
+
+ /**
+ * Set the uid, gid
+ */
+ public void setCred(int uid, int gid, int[] gids) {
+ this.uid = uid;
+ this.gid = gid;
+ this.gids = gids;
+ }
+
+ /*
+ * Disguise the string so that it's not
+ * obvious to a casual snooper.
+ */
+ private String disguise(String s) {
+ byte[] b = s.getBytes();
+
+ for (int i = 0; i < b.length; i++)
+ b[i] = (byte)((b[i] & 0x7f) ^ 0x5b);
+
+ return (new String(b));
+ }
+
+ /**
+ * Get the Unix user id for the user
+ * @return uid
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * Get the Unix group id for the user
+ * @return gid
+ */
+ public int getGid() {
+ return gid;
+ }
+
+ /**
+ * Get the Unix group list for the user
+ * @return gids
+ */
+ public int[] getGids() {
+ return gids;
+ }
+
+ /**
+ * Get the user's home directory path
+ * @return pathname of home directory.
+ */
+ public String getHome() {
+ return home;
+ }
+
+ /**
+ * Get the user's home Unix umask
+ * @return umask
+ */
+ public int getUmask() {
+ return def_umask;
+ }
+
+ private boolean callV1(String server, String username, String passwd)
+ throws java.net.UnknownHostException, IOException {
+
+ Rpc pc = new Rpc(server, 0, PCNFSDPROG, 1, "udp", MAXREPLY);
+ Xdr call = new Xdr(MAXREPLY);
+ pc.rpc_header(call, PCNFSD_AUTH);
+
+ call.xdr_string(username);
+ call.xdr_string(passwd);
+
+ Xdr reply = pc.rpc_call(call, 10 * 1000, 2);
+
+ status = reply.xdr_int();
+ if (status == AUTH_RES_FAIL)
+ return false;
+
+ uid = reply.xdr_int();
+ gid = reply.xdr_int();
+ gids = null;
+ home = null;
+ def_umask = 0;
+
+ return true;
+ }
+
+ private boolean callV2(String server, String username, String passwd)
+ throws java.net.UnknownHostException, IOException {
+
+ Rpc pc = new Rpc(server, 0, PCNFSDPROG, 2, "udp", MAXREPLY);
+ Xdr call = new Xdr(MAXREPLY);
+ pc.rpc_header(call, PCNFSD2_AUTH);
+
+ call.xdr_string("(anyhost)"); // XXX should be hostname
+ call.xdr_string(username);
+ call.xdr_string(passwd);
+ call.xdr_string("Java client"); // comment
+
+ Xdr reply = pc.rpc_call(call, 10 * 1000, 2);
+
+ status = reply.xdr_int();
+ if (status == AUTH_RES_FAIL)
+ return false;
+
+ uid = reply.xdr_int();
+ gid = reply.xdr_int();
+ gids = new int[reply.xdr_int()];
+ for (int i = 0; i < gids.length; i++)
+ gids[i] = reply.xdr_int();
+ home = reply.xdr_string();
+ def_umask = reply.xdr_int();
+
+ return true;
+ }
+
+ public String toString() {
+ String s = "AUTH_UNIX:\n uid=" + uid + ",gid=" + gid + "\n";
+ if (gids != null) {
+ s += " gids=";
+ for (int i = 0; i < gids.length; i++)
+ s += gids[i] + " ";
+ }
+ if (home != null)
+ s += "\n home=" + home;
+ if (def_umask != 0)
+ s += "\n umask=0" + Long.toOctalString(def_umask);
+
+ return s;
+ }
+
+ public void init(Connection conn, int prog, int vers) {
+ // No-op
+ }
+
+ public boolean refresh(Connection conn, int prog, int vers) {
+ // No-op
+ return true;
+ }
+
+ public void wrap(Xdr x, byte[] arg) {
+ // No-op
+ }
+
+ public int unwrap(Xdr x) {
+ // No-op
+ return 0;
+ }
+
+ public void validate(byte[] verifier, int verifiee) {
+ // No-op
+ }
+
+ public void destroy(Rpc rpc) {
+ // No-op
+ }
+
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/MsgAcceptedException.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/MsgAcceptedException.java
new file mode 100644
index 0000000000..b98a4fb0ce
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/MsgAcceptedException.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 1997, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+/**
+ *
+ * Handle the RPC "message accepted" class of errors.
+ *
+ * Note that some of the errors also convey low and high
+ * version information.
+ *
+ * @see RpcException
+ * @author Brent Callaghan
+ */
+public class MsgAcceptedException extends RpcException {
+ int lo, hi;
+
+ public static final int PROG_UNAVAIL = 1;
+ public static final int PROG_MISMATCH = 2;
+ public static final int PROC_UNAVAIL = 3;
+ public static final int GARBAGE_ARGS = 4;
+ public static final int SYSTEM_ERR = 5;
+
+ /*
+ * Construct a new Exception for the specified RPC accepted error
+ * @param error The RPC error number
+ */
+ public MsgAcceptedException(int error) {
+ super(error);
+ }
+
+ /*
+ * Construct a new RPC error with the given low and high parameters
+ * @param error The RPC error number
+ * @param lo The low version number
+ * @param hi The high version number
+ */
+ public MsgAcceptedException(int error, int lo, int hi) {
+ super(error);
+ this.lo = lo;
+ this.hi = hi;
+ }
+
+ public String toString() {
+ switch (error) {
+
+ case PROG_UNAVAIL:
+ return "Program unavailable";
+
+ case PROG_MISMATCH:
+ return "Program number mismatch: " +
+ "low=" + lo + ",high=" + hi;
+
+ case PROC_UNAVAIL:
+ return "Procedure Unavailable: " +
+ "low=" + lo + ",high=" + hi;
+
+ case GARBAGE_ARGS:
+ return "Garbage Arguments";
+
+ case SYSTEM_ERR:
+ return "System error";
+
+ default:
+ return "Unknown RPC Error = " + error;
+ }
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/MsgRejectedException.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/MsgRejectedException.java
new file mode 100644
index 0000000000..366230db5c
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/MsgRejectedException.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+/**
+ *
+ * Handle the RPC "message rejected" class of errors.
+ *
+ * Note that some of the errors also convey low and high
+ * version information or an authentication sub-error.
+ *
+ * @see RpcException
+ * @author Brent Callaghan
+ */
+public class MsgRejectedException extends RpcException {
+
+ public static final int RPC_MISMATCH = 0;
+ public static final int AUTH_ERROR = 1;
+
+ public static final int AUTH_BADCRED = 1;
+ public static final int AUTH_REJECTEDCRED = 2;
+ public static final int AUTH_BADVERF = 3;
+ public static final int AUTH_REJECTEDVERF = 4;
+ public static final int AUTH_TOOWEAK = 5;
+ public static final int AUTH_INVALIDRESP = 6;
+ public static final int AUTH_FAILED = 7;
+
+ public static final int RPCSEC_GSS_NOCRED = 13;
+ public static final int RPCSEC_GSS_FAILED = 14;
+
+ /*
+ * Construct a new Exception for the specified RPC accepted error
+ * @param error The RPC error number
+ */
+ public MsgRejectedException(int error) {
+ super(error);
+ }
+
+ /*
+ * Construct a new RPC error with the given auth sub-error
+ * @param error The RPC error number
+ * @param why The auth sub-error
+ */
+ public MsgRejectedException(int error, int why) {
+ super(error);
+ this.why = why;
+ }
+
+ /*
+ * Construct a new RPC error with the given low and high parameters
+ * @param error The RPC error number
+ * @param lo The low version number
+ * @param hi The high version number
+ */
+ public MsgRejectedException(int error, int lo, int hi) {
+ super(error);
+ this.lo = lo;
+ this.hi = hi;
+ }
+
+ public String toString() {
+ switch (error) {
+
+ case RPC_MISMATCH:
+ return "Version mismatch: " +
+ "low=" + lo + ",high=" + hi;
+
+ case AUTH_ERROR:
+ String msg = "Authentication error: ";
+ switch (why) {
+ case AUTH_BADCRED:
+ msg += "bogus credentials (seal broken)";
+ break;
+ case AUTH_REJECTEDCRED:
+ msg += "client should begin new session";
+ break;
+ case AUTH_BADVERF:
+ msg += "bogus verifier (seal broken)";
+ break;
+ case AUTH_REJECTEDVERF:
+ msg += "verifier expired or was replayed";
+ break;
+ case AUTH_TOOWEAK:
+ msg += "too weak";
+ break;
+ case AUTH_INVALIDRESP:
+ msg += "bogus response verifier";
+ break;
+ case RPCSEC_GSS_NOCRED:
+ msg += "no credentials for user";
+ break;
+ case RPCSEC_GSS_FAILED:
+ msg += "GSS failure, credentials deleted";
+ break;
+ case AUTH_FAILED:
+ default:
+ msg += "unknown reason";
+ break;
+ }
+ return msg;
+
+ default:
+ return "Unknown RPC Error = " + error;
+ }
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Rpc.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Rpc.java
new file mode 100644
index 0000000000..47f233c942
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Rpc.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+import java.net.InetAddress;
+
+/**
+ *
+ * This class transmits and receives RPC calls to an RPC service
+ * at a specific host and port.
+ *
+ * @see Connection
+ * @see RpcException
+ * @see MsgAcceptedException
+ * @see MsgDeniedException
+ * @author Brent Callaghan
+ */
+public class Rpc {
+ public Connection conn;
+ int prog;
+ int vers;
+ Cred cred;
+ RpcHandler rhandler = new RpcHandler();
+
+ private static int xid = (int) System.currentTimeMillis() & 0x0fffffff;
+
+ private static final int PMAP_PROG = 100000;
+ private static final int PMAP_PORT = 111;
+ private static final int PMAP_VERS = 2;
+ private static final int PMAP_GETPORT = 3;
+ private static final int PMAP_MAXSZ = 128;
+
+ private static final int MAX_TIMEOUT = 30 * 1000; // 30 sec
+ private static final int MAX_REPLY = 8192 + 256;
+
+ /**
+ * Construct a new Rpc object - equivalent to a "client handle"
+ * using an AUTH_NONE cred handle.
+ *
+ * @param conn A connection to the server
+ * @param prog The program number of the service
+ * @param vers The version number of the service
+ */
+ public Rpc(Connection conn, int prog, int vers) {
+ this.conn = conn;
+ this.prog = prog;
+ this.vers = vers;
+ cred = new CredNone();
+ }
+
+ /**
+ * Construct a new Rpc object - equivalent to a "client handle"
+ * using a given cred handle "cr"
+ *
+ * @param conn A connection to the server
+ * @param prog The program number of the service
+ * @param vers The version number of the service
+ * @param cr The cred to be used: CredUnix or CredGss
+ */
+ public Rpc(Connection conn, int prog, int vers, Cred cr) {
+ this.conn = conn;
+ this.prog = prog;
+ this.vers = vers;
+ cred = cr;
+ }
+
+ /**
+ * Construct a new Rpc object - equivalent to a "client handle"
+ *
+ * @param server The hostname of the server
+ * @param port The port number for the service
+ * @param prog The program number of the service
+ * @param vers The version number of the service
+ * @param proto The protocol to be used: "tcp" or "udp"
+ * @param maxReply The maximum size of the RPC reply
+ * @exception IOException if an I/O error occurs
+ */
+ public Rpc(String server, int port, int prog, int vers,
+ String proto, int maxReply)
+ throws IOException {
+ this.conn = getConnection(server, port, prog, vers,
+ proto, maxReply);
+ this.prog = prog;
+ this.vers = vers;
+ cred = new CredNone();
+ }
+
+
+ private Connection getConnection(String server, int port, int prog,
+ int vers, String proto, int maxReply)
+ throws IOException {
+
+ if (port == 0) {
+ Rpc pmap = new Rpc(server, PMAP_PORT, PMAP_PROG, PMAP_VERS,
+ "udp", PMAP_MAXSZ);
+ Xdr call = new Xdr(PMAP_MAXSZ);
+
+ pmap.rpc_header(call, PMAP_GETPORT);
+
+ call.xdr_int(prog);
+ call.xdr_int(vers);
+ call.xdr_int(proto.equals("tcp") ? 6 : 17);
+ call.xdr_int(0); // no port
+
+ Xdr reply = pmap.rpc_call(call, 5 * 1000, 3);
+
+ port = reply.xdr_int();
+ if (port == 0)
+ throw new MsgAcceptedException(PROG_UNAVAIL);
+ }
+
+ /*
+ * Check the connection cache first to see
+ * if there's a connection already set up
+ * for this port, server and protocol. This is
+ * particularly important for TCP with its
+ * high connection overhead.
+ *
+ * The code is synchronized to prevent concurrent
+ * threads from missing the cache and setting
+ * up multiple connections.
+ */
+ synchronized (Connection.connections) {
+ conn = Connection.getCache(server, port, proto);
+
+ if (conn == null) {
+ if (proto.equals("tcp"))
+ conn = new ConnectSocket(server, port, maxReply);
+ else
+ conn = new ConnectDatagram(server, port, maxReply);
+
+ Connection.putCache(conn);
+ }
+ }
+
+ return conn;
+ }
+
+ /**
+ * Set the RPC credential
+ *
+ * @param c - cred to be used
+ */
+ public void setCred(Cred c) throws RpcException {
+
+ cred = c;
+ cred.init(conn, prog, vers);
+ }
+
+ /**
+ * Delete the RPC credential data and destroy its security
+ * context with the server.
+ */
+ public void delCred() throws RpcException {
+
+ cred.destroy(this);
+ }
+
+ /**
+ * Return the RPC credential
+ *
+ * @return The credential
+ */
+ public Cred getCred() {
+ return cred;
+ }
+
+ /**
+ *
+ */
+ public void setRpcHandler(RpcHandler r) {
+ rhandler = r == null ? new RpcHandler() : r;
+ }
+
+ /**
+ * Construct an RPC header in the XDR buffer
+ *
+ * @param call The XDR buffer for the header
+ * @param proc The service procedure to be called
+ */
+ public void rpc_header(Xdr call, int proc) throws RpcException {
+
+ call.xid = next_xid();
+
+ /*
+ * Initialize XDR buffer
+ * If using TCP then reserve space for record mark
+ */
+ call.xdr_offset(conn instanceof ConnectSocket ? 4 : 0);
+
+ call.xdr_int(call.xid);
+ call.xdr_int(0); // direction=CALL
+ call.xdr_int(2); // RPC version
+ call.xdr_int(prog);
+ call.xdr_int(vers);
+ call.xdr_int(proc);
+ cred.putCred(call);
+ }
+
+ /*
+ * A rare static method! We need to
+ * make sure that the xid is unique
+ * for all instances of an RPC connection
+ * on this client.
+ */
+ static synchronized int next_xid() {
+ return xid++;
+ }
+
+ /*
+ * Message type
+ */
+ static final int CALL = 0;
+ static final int REPLY = 1;
+
+ /*
+ * Reply Status
+ */
+ static final int MSG_ACCEPTED = 0;
+ static final int MSG_DENIED = 1;
+
+ /*
+ * Accept Status
+ */
+ static final int SUCCESS = 0;
+ static final int PROG_UNAVAIL = 1;
+ static final int PROG_MISMATCH = 2;
+ static final int PROC_UNAVAIL = 3;
+ static final int GARBAGE_ARGS = 4;
+ static final int SYSTEM_ERR = 5;
+
+ /*
+ * Reject Status
+ */
+ static final int RPC_MISMATCH = 0;
+ static final int AUTH_ERROR = 1;
+
+ /*
+ * Re-construct the call xdr buffer when retranmitting an
+ * RPCSEC_GSS request.
+ * (i.e. for a retransmitted RPCSEC_GSS requests, it uses a
+ * different sequence number and it needs to use its un-encrypted
+ * argument to do wrap() again.
+ */
+ private Xdr call_reconstruct(Xdr call, byte[] arg)
+ throws IOException, RpcException {
+
+ Xdr recall = new Xdr(call.xdr_size());
+ recall.xid = call.xid;
+
+ // the rpc_header
+ recall.xdr_raw(call.xdr_raw(0,
+ conn instanceof ConnectSocket ? 28 : 24));
+ cred.putCred(recall);
+
+ // the not-yet-encrypted rpc argument
+ if (arg != null) {
+ if (recall.xdr_offset() == recall.xdr_wrap_offset()) {
+ recall.xdr_raw(arg);
+ } else {
+ recall.xdr_raw(arg, 4, arg.length - 4);
+ }
+ }
+
+ return recall;
+ }
+
+ /**
+ * Transmit the XDR call buffer containing an RPC header
+ * followed by a protocol header and receive the
+ * reply.
+ *
+ * @param call XDR buffer containing RPC call to transmit
+ * @param arg (seq_num + RPC argument) if wrap
+ * @param timeout after this number of milliseconds
+ * @return Xdr the XDR buffer for the reply
+ * @throws RpcException
+ */
+ public Xdr rpc_call_one(Xdr call, byte[] arg, int timeout)
+ throws IOException, RpcException {
+
+ int status, astat, rstat;
+ int why;
+ byte[] verifier;
+
+ // encrypt the rpc argument if it's needed
+ if (arg != null)
+ cred.wrap(call, arg);
+
+ Xdr reply = conn.send(call, timeout);
+
+ // XID already xdr'ed by the connection listener
+
+ if (reply.xdr_int() != REPLY) // direction
+ throw new RpcException("Unknown RPC header");
+
+ status = reply.xdr_int();
+ switch (status) {
+ case MSG_ACCEPTED:
+ reply.xdr_skip(4); // verifier flavor
+ verifier = reply.xdr_bytes(); // get the verifier
+ astat = reply.xdr_int();
+
+ switch (astat) {
+ case SUCCESS:
+ int seq_num_in = cred.unwrap(reply);
+
+ // decrypt the result if it's needed
+ if (seq_num_in > 0) {
+ cred.validate(verifier, seq_num_in);
+ }
+ break;
+
+ case PROG_UNAVAIL:
+ case PROG_MISMATCH:
+ case PROC_UNAVAIL:
+ throw new MsgAcceptedException(astat,
+ reply.xdr_int(), reply.xdr_int());
+
+ case GARBAGE_ARGS:
+ case SYSTEM_ERR:
+ default:
+ throw new MsgAcceptedException(astat);
+ }
+ break;
+
+ case MSG_DENIED:
+ rstat = reply.xdr_int();
+ switch (rstat) {
+ case RPC_MISMATCH:
+ throw new MsgRejectedException(rstat,
+ reply.xdr_int(), reply.xdr_int());
+ case AUTH_ERROR:
+ why = reply.xdr_int();
+ throw new MsgRejectedException(rstat, why);
+
+ default:
+ throw new MsgRejectedException(rstat);
+ }
+ }
+
+ return reply;
+ }
+
+ /**
+ * Make an RPC call but retry if necessary
+ *
+ * Retries use exponential backoff up to MAX_TIMEOUT ms.
+ *
+ * Note that we handle TCP connections differently: there is
+ * no timeout, and retransmission is used only when reconnecting.
+ *
+ * @param call XDR buffer containing RPC call to transmit
+ * @param timeout for the initial call
+ * @param retries the number of times to retry the call.
+ * A value of zero implies forever.
+ * @return Xdr the XDR buffer for the reply
+ * @throws IOException
+ */
+ public Xdr rpc_call(Xdr call, int timeout, int retries)
+ throws IOException {
+
+ boolean timedout = false;
+ Xdr reply = null;
+ long startTime = System.currentTimeMillis();
+
+ if (retries == 0)
+ retries = Integer.MAX_VALUE; // retry forever
+
+ /*
+ * If it's a TCP connection, do retries only
+ * to re-establish connection.
+ */
+ if (conn instanceof ConnectSocket)
+ timeout = MAX_TIMEOUT;
+
+ // refresh twice if needed
+ int num_refresh = 2;
+ for (int c = 0; c < retries; c++) {
+
+ byte[] arg = null;
+
+ /*
+ * Currently, only CredNone, CredUnix, CredGss is supported.
+ * For CredGss: save the (seq_num + rpc argument) before
+ * it's encrypted. This arg will be needed during retransmit.
+ *
+ * CredGss not checked to avoid loading un-used CredGss class.
+ */
+ if (!(cred instanceof CredUnix) && !(cred instanceof CredNone) &&
+ (call.xdr_offset() > call.xdr_wrap_offset())) {
+ arg = call.xdr_raw(call.xdr_wrap_offset(),
+ call.xdr_offset() - call.xdr_wrap_offset());
+ }
+
+ try {
+
+ reply = rpc_call_one(call, arg, timeout);
+ break; // reply received OK
+
+ } catch (MsgRejectedException e) {
+
+ /*
+ * Refresh the cred and try again
+ */
+ if (num_refresh > 0 &&
+ (e.why == MsgRejectedException.RPCSEC_GSS_NOCRED ||
+ e.why == MsgRejectedException.RPCSEC_GSS_FAILED) &&
+ cred.refresh(conn, prog, vers)) {
+
+ // re-construct the "call" Xdr buffer.
+ call = call_reconstruct(call, arg);
+ num_refresh--;
+ c--; // to do refresh
+
+ } else {
+ throw e;
+ }
+
+ } catch (RpcException e) {
+
+ /*
+ * An error that cannot be recovered by
+ * retrying - just give up.
+ */
+ throw e;
+
+ } catch (IOException e) {
+
+ /*
+ * If it's a timeout then tell the RPC handler.
+ * It may request an abort by returning true.
+ */
+ if (rhandler.timeout(conn.server, c,
+ (int) (System.currentTimeMillis() - startTime)))
+ throw new InterruptedIOException();
+
+ /*
+ * Probably a timeout.
+ * Double the timeout and retry
+ */
+ timedout = true;
+ timeout *= 2; // double the timeout
+ if (timeout > MAX_TIMEOUT)
+ timeout = MAX_TIMEOUT;
+
+ /*
+ * For CredGss: reconstruct the clear-text-argument
+ * and use a new sequence number.
+ * Currently, only CredNone, CredUnix, CredGss is supported.
+ *
+ * CredGss not checked to avoid loading un-used CredGss class.
+ */
+ if (!(cred instanceof CredUnix) &&
+ !(cred instanceof CredNone)) {
+ call = call_reconstruct(call, arg);
+ }
+ }
+ }
+
+ if (reply == null) // reached retry limit
+ throw new InterruptedIOException();
+
+ /*
+ * If recovered after a timeout then tell
+ * the RPC Handler so it can display a
+ * "server OK" message.
+ */
+ if (timedout && reply != null)
+ rhandler.ok(conn.server);
+
+ return reply;
+ }
+
+ /**
+ * Since this returns the address of the server it may
+ * seem redundant - but if you receive a reply to a
+ * broadcast RPC you need to know who is replying.
+ * @return address of the Peer
+ */
+ public InetAddress getPeer() {
+ return conn.getPeer();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/RpcException.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/RpcException.java
new file mode 100644
index 0000000000..2de45b997c
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/RpcException.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+/**
+ *
+ * Handle the RPC exceptions. Most of the work is done
+ * by the two subclasses that handle the Accepted and Denied
+ * classes of RPC errors.
+ *
+ * @see MsgDeniedException
+ * @see MsgAcceptedConnection
+ * @author Brent Callaghan
+ */
+public class RpcException extends java.io.IOException {
+
+ public int error;
+ public int why;
+ public int lo, hi;
+
+ /*
+ * Construct a new RPC exception with the given error
+ * @param string The error message
+ */
+ public RpcException(String msg) {
+ super("RPC error: " + msg);
+ }
+
+ /*
+ * Construct a new RPC exception with the given error
+ * @param error The RPC error
+ */
+ public RpcException(int error) {
+ super("RPC error: " + error);
+ this.error = error;
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/RpcHandler.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/RpcHandler.java
new file mode 100644
index 0000000000..ca9a628021
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/RpcHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+
+/**
+ * This handler is implemented by the RPC application
+ * if it wishes to be notifed of retransmissions.
+ * A good example is an NFS client that displays
+ * "NFS Server not responding" and "NFS server OK"
+ */
+
+public class RpcHandler {
+
+ /**
+ * Called when the RPC times out.
+ *
+ * @param server The name of the server
+ * @param retries Number of retries (initially 0)
+ * @param waittime Total time waiting for response
+ * @return bool True to abort, false to continue retrying
+ */
+ public boolean timeout(String server, int retries, int waittime) {
+ return false;
+ }
+
+ /**
+ * Called when the server responds after a timeout
+ *
+ * @param server The name of the server
+ */
+ public void ok(String server) {
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Xdr.java b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Xdr.java
new file mode 100644
index 0000000000..05e4ef5bc8
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/Xdr.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 1997-1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.rpc;
+
+import java.io.*;
+
+/**
+ * This class handles the marshalling/unmarshalling of
+ * primitive data types into and out of a buffer.
+ *
+ * The XDR buffer is a field within this class and its
+ * size is determined when the class is instantiated.
+ * Other than this buffer, there are just two pointers:
+ * "off" is the current XDR offset into the buffer and
+ * moves up the buffer by an integral number of XDRUNITs
+ * as data are encoded/decoded. The other pointer is
+ * "size" which is the number of valid data bytes in
+ * the buffer and is set only for received buffers.
+ *
+ * XXX we should perhaps check that off <= size
+ * whenever an item is decoded so that we can raise
+ * an exception if the received data is underlength.
+ *
+ * @see Rpc
+ * @author Brent Callaghan
+ */
+public class Xdr {
+ private static int XDRUNIT = 4;
+ private byte[] buf;
+ private int size, off, wrap_offset;
+ int xid;
+
+ /**
+ * Build a new Xdr object with a buffer of given size
+ *
+ * @param size of the buffer in bytes
+ */
+ public Xdr(int size) {
+ this.buf = new byte[size];
+ this.size = size;
+ this.off = 0;
+ }
+
+ /**
+ * Skip a number of bytes.
+ *
Note that the count is
+ * rounded up to the next XDRUNIT.
+ *
+ * @param count of the buffer in bytes
+ */
+ public void xdr_skip(int count) {
+ int r = (off += count) % XDRUNIT;
+
+ if (r > 0)
+ off += XDRUNIT - r;
+ }
+
+ /**
+ * Return the entire Xdr buffer
+ *
+ * @return Xdr buffer
+ */
+ public byte[] xdr_buf() {
+ return buf;
+ }
+
+ /**
+ * Return the current offset
+ *
+ * @return offset
+ */
+ public int xdr_offset() {
+ return off;
+ }
+
+ /**
+ * Set the current offset
+ *
+ * @param off offset into XDR buffer
+ */
+ public void xdr_offset(int off) {
+ this.off = off;
+ }
+
+ /**
+ * Return the starting point of the bytes that will
+ * be encrypted.
+ *
+ * @return offset for bytes to be encrypted
+ */
+ public int xdr_wrap_offset() {
+ return wrap_offset;
+ }
+
+ /**
+ * Set the starting point of the bytes that will
+ * be encrypted.
+ *
+ * @return offset for bytes to be encrypted
+ */
+ public void xdr_wrap_offset(int off) {
+ wrap_offset = off;
+ }
+
+ /**
+ * Return the current size of the XDR buffer
+ *
+ * @return size
+ */
+ public int xdr_size() {
+ return size;
+ }
+
+ /**
+ * Set the current size of the XDR buffer
+ *
+ * @param size of buffer
+ */
+ public void xdr_size(int size) {
+ this.size = size;
+ }
+
+ /**
+ * Get an integer from the buffer
+ *
+ * @return integer
+ */
+ public int xdr_int() {
+ return ((buf[off++] & 0xff) << 24 |
+ (buf[off++] & 0xff) << 16 |
+ (buf[off++] & 0xff) << 8 |
+ (buf[off++] & 0xff));
+ }
+
+ /**
+ * Put an integer into the buffer
+ *
+ * @param i Integer to store in XDR buffer.
+ */
+ public void xdr_int(int i) {
+ buf[off++] = (byte)(i >>> 24);
+ buf[off++] = (byte)(i >> 16);
+ buf[off++] = (byte)(i >> 8);
+ buf[off++] = (byte)i;
+ }
+
+ /**
+ * Get an unsigned integer from the buffer
+ *
+ *
Note that Java has no unsigned integer
+ * type so we must return it as a long.
+ *
+ * @return long
+ */
+ public long xdr_u_int() {
+ return ((buf[off++] & 0xff) << 24 |
+ (buf[off++] & 0xff) << 16 |
+ (buf[off++] & 0xff) << 8 |
+ (buf[off++] & 0xff));
+ }
+
+ /**
+ * Put an unsigned integer into the buffer
+ *
+ * Note that Java has no unsigned integer
+ * type so we must submit it as a long.
+ *
+ * @param i unsigned integer to store in XDR buffer.
+ */
+ public void xdr_u_int(long i) {
+ buf[off++] = (byte)(i >>> 24 & 0xff);
+ buf[off++] = (byte)(i >> 16);
+ buf[off++] = (byte)(i >> 8);
+ buf[off++] = (byte)i;
+ }
+
+ /**
+ * Get a long from the buffer
+ *
+ * @return long
+ */
+ public long xdr_hyper() {
+ return ((long)(buf[off++] & 0xff) << 56 |
+ (long)(buf[off++] & 0xff) << 48 |
+ (long)(buf[off++] & 0xff) << 40 |
+ (long)(buf[off++] & 0xff) << 32 |
+ (long)(buf[off++] & 0xff) << 24 |
+ (long)(buf[off++] & 0xff) << 16 |
+ (long)(buf[off++] & 0xff) << 8 |
+ (long)(buf[off++] & 0xff));
+ }
+
+ /**
+ * Put a long into the buffer
+ *
+ * @param i long to store in XDR buffer
+ */
+ public void xdr_hyper(long i) {
+ buf[off++] = (byte)(i >>> 56) ;
+ buf[off++] = (byte)((i >> 48) & 0xff);
+ buf[off++] = (byte)((i >> 40) & 0xff);
+ buf[off++] = (byte)((i >> 32) & 0xff);
+ buf[off++] = (byte)((i >> 24) & 0xff);
+ buf[off++] = (byte)((i >> 16) & 0xff);
+ buf[off++] = (byte)((i >> 8) & 0xff);
+ buf[off++] = (byte)(i & 0xff);
+ }
+
+ /*
+ * Note: we have no XDR routines for encoding/decoding
+ * unsigned longs. They exist in XDR but not in Java
+ * hence we can't represent them.
+ * Best just to use xdr_hyper() and hope the sign bit
+ * isn't used.
+ */
+
+ /**
+ * Get a boolean from the buffer
+ *
+ * @return boolean
+ */
+ public boolean xdr_bool() {
+ return (xdr_int() != 0);
+ }
+
+ /**
+ * Put a boolean into the buffer
+ *
+ * @param b boolean
+ */
+ public void xdr_bool(boolean b) {
+ xdr_int(b ? 1 : 0);
+ }
+
+ /**
+ * Get a floating point number from the buffer
+ *
+ * @return float
+ */
+ public float xdr_float() {
+ return (Float.intBitsToFloat(xdr_int()));
+ }
+
+ /**
+ * Put a floating point number into the buffer
+ *
+ * @param f float
+ */
+ public void xdr_float(float f) {
+ xdr_int(Float.floatToIntBits(f));
+ }
+
+ /**
+ * Get a string from the buffer
+ *
+ * @return string
+ */
+ public String xdr_string() {
+ int len = xdr_int();
+
+ String s = new String(buf, off, len);
+ xdr_skip(len);
+ return s;
+ }
+
+ /**
+ * Put a string into the buffer
+ *
+ * @param s string
+ */
+ public void xdr_string(String s) {
+ xdr_bytes(s.getBytes());
+ }
+
+ /**
+ * Get a counted array of bytes from the buffer
+ *
+ * @return bytes
+ */
+ public byte[] xdr_bytes() {
+ return (xdr_raw(xdr_int()));
+ }
+
+ /**
+ * Put a counted array of bytes into the buffer.
+ * Note that the entire byte array is encoded.
+ *
+ * @param b byte array
+ */
+ public void xdr_bytes(byte[] b) {
+ xdr_bytes(b, 0, b.length);
+ }
+
+ /**
+ * Put a counted array of bytes into the buffer
+ *
+ * @param b byte array
+ * @param len number of bytes to encode
+ */
+ public void xdr_bytes(byte[] b, int len) {
+ xdr_bytes(b, 0, len);
+ }
+
+ /**
+ * Put a counted array of bytes into the buffer
+ *
+ * @param b byte array
+ * @param boff offset into byte array
+ * @param len number of bytes to encode
+ */
+ public void xdr_bytes(byte[] b, int boff, int len) {
+ xdr_int(len);
+ System.arraycopy(b, boff, buf, off, len);
+ xdr_skip(len);
+ }
+
+ /**
+ * Put an Xdr buffer into the buffer
+ *
+ *
This is used to encode the RPC credentials
+ *
+ * @param x XDR buffer
+ */
+ public void xdr_bytes(Xdr x) {
+ xdr_bytes(x.xdr_buf(), x.xdr_offset());
+ }
+
+ /**
+ * Get a fixed number of bytes from the buffer
+ *
+ * e.g. an NFS v2 filehandle
+ *
+ * @param len Number of bytes to get
+ * @return byte array
+ */
+ public byte[] xdr_raw(int len) {
+ if (len == 0)
+ return null;
+
+ byte[] b = new byte[len];
+
+ System.arraycopy(buf, off, b, 0, len);
+ xdr_skip(len);
+ return b;
+ }
+
+ /**
+ * Get a fixed number (len) of bytes from the buffer
+ * at offset off. Do not change any buffer indicators.
+ *
+ * @param off Offset of bytes to get from
+ * @param len Number of bytes to copy
+ * @return byte array
+ */
+ public byte[] xdr_raw(int off, int len) {
+ if (len == 0)
+ return null;
+
+ byte[] b = new byte[len];
+
+ System.arraycopy(buf, off, b, 0, len);
+ return b;
+ }
+
+ /**
+ * Put a fixed number of bytes into the buffer
+ * The length is not encoded.
+ *
+ * e.g. an NFS v2 filehandle
+ *
+ * @param b byte array
+ */
+ public void xdr_raw(byte[] b) {
+ int len = b.length;
+
+ System.arraycopy(b, 0, buf, off, len);
+ xdr_skip(len);
+ }
+
+ /**
+ * Put a fixed number of bytes into the buffer
+ * at offset off. The length is not encoded.
+ *
+ * @param b byte array
+ * @param off where to put the byte array
+ */
+ public void xdr_raw(byte[] b, int off) {
+ int len = b.length;
+
+ System.arraycopy(b, 0, buf, off, len);
+ xdr_skip(len);
+ }
+
+ /**
+ * Put a counted array of bytes into the buffer.
+ * The length is not encoded.
+ *
+ * @param b byte array
+ * @param boff offset into byte array
+ * @param len number of bytes to encode
+ */
+ public void xdr_raw(byte[] b, int boff, int len) {
+ System.arraycopy(b, boff, buf, off, len);
+ xdr_skip(len);
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/rpc/samples/README b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/samples/README
new file mode 100644
index 0000000000..937afbf3eb
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/rpc/samples/README
@@ -0,0 +1,88 @@
+
+#pragma ident "@(#)README 1.3 98/11/12 SMI"
+
+
+System
+------
+ Solaris
+ Install the dummy mechanism package on the server.
+
+
+Server Setup
+------------
+
+Before running the client java test program, the C server program
+has to be invoked first:
+
+/home/lling/test/rpcsec_gss/C/list_svc [-l] -n
+ *
+ *
+ * You can use the same name to reference a file
+ * independent of the pathname syntax supported
+ * by the underlying operating system. The
+ * component separator in a URL pathname is always
+ * a forward slash.
+ *
+ * For instance, a URL name can refer to a file
+ * anywhere on the Internet, e.g.
+ * nfs://santa.northpole.org/toys/catalog
+ *
+ * For example:
+ *
+ *
+ * This property makes possible the dynamic loading
+ * of new filesystem accessors.
+ * file:///C|/java/bin
(a local directory)
+ * nfs://myserver/home/ed
+ * (directory on NFS server)
+ * ftp://ftpsrv/pub/pkg.zip
+ * (file on FTP server)
+ *
+ * URLs support a well defined set of rules for the use
+ * of names relative to a "base" URL described in
+ * RFC 1808.
+ *
For instance:
+ *
+ *
+ *
+ * Base Relative Composition
+ *
+ *
+ * file:///a/b/c
+ * x
+ * file:///a/b/c/x
+ *
+ * nfs://server/a/b/c
+ * /y
+ * nfs://server/y
+ *
+ * nfs://server/a/b/c
+ * ../z
+ * nfs://server/a/b/z
+ *
+ * file:///a/b/c
+ * d/.
+ * nfs://server/a/b/c/d
+ *
+ *
+ * file:///a/b/c
+ * nfs://srv/x
+ * nfs://srv/x
+ * Although URLs are necessarily location dependent,
+ * location indepent Universal Resource Names (URN)
+ * names can be used within the same structure (see
+ *
+ * RFC 2141.
+ * XFile
instance that represents the file
+ * whose pathname is the given url argument.
+ *
+ * If the the name argument contains the string "://" then it
+ * is assumed to be a URL name. The characters prior to the
+ * colon are assumed to be the filesystem scheme name, e.g.
+ * "file", "nfs", etc. The rest of the URL name is assumed
+ * to be structured according to the Common Internet Scheme
+ * syntax described in
+ * RFC 1738;
+ * an optional location part followed by a hierarchical set
+ * of slash separated directories.
+ * <scheme>://<location>/<path>
+ *
+ * @param name the file url
+ * @exception java.lang.NullPointerException if the file url
+ * is equal to
null
.
+ */
+ public XFile(String name) {
+
+ urlStr = name;
+ if (name == null)
+ throw new NullPointerException();
+
+ try {
+ url = new XFurl(name);
+ xfa = loadAccessor(url);
+ } catch (Exception e) {
+ if (name.startsWith(".:"))
+ name = name.substring(2); // lop off ".:"
+
+ nativeFile = new File(name);
+ xfa = makeNative(nativeFile);
+ }
+ }
+
+
+ /**
+ * Creates a XFile
instance that represents the file
+ * with the specified name in the specified directory.
+ *
+ * If the dir
XFile is null
, or if the
+ * name
string is a full URL, then the single-arg
+ * constructor is used on the name
.
+ * dir
XFile represents a native file
+ * and the name
string isAbsolute
+ * then the single-arg constructor is used on the name
.
+ * If the name
is not absolute then the resulting
+ * path is the simple concatenation of the dir
+ * path with the file separator and the name
as
+ * for the two-arg constructor of the File
class.
+ * dir
XFile represents a URL name then
+ * the dir
is assumed to be a base URL
+ * and the name
string is evaluated as a
+ * relative URL according to the rules described in
+ * RFC 1808.
+ *
+ *
+ *
+ *
+ * Dir Name Composition
+ *
+ *
+ * file:///a/b/c
+ * x
+ * file:///a/b/c/x
+ *
+ * nfs://server/a/b/c
+ * /y
+ * nfs://server/y
+ *
+ * nfs://server/a/b/c
+ * ../z
+ * nfs://server/a/b/z
+ *
+ * file:///a/b/c
+ * d/.
+ * nfs://server/a/b/c/d
+ *
+ * file:///a/b/c
+ * nfs://srv/x
+ * nfs://srv/x
+ *
+ *
+ * C:\Data\Programs
+ * myprog.exe
+ * C:\Data\Programs\myprog.exe
XFile
object.
+ */
+ public String getName() {
+ if (nativeFile != null)
+ return nativeFile.getName();
+
+ return url.getName();
+ }
+
+
+ /**
+ * Returns the pathname of the file represented by this object.
+ *
+ * @return the pathname represented by this XFile
+ * object.
+ * new XFile("nfs://location/a/b/c").getPath()
+ * == "a/b/c"
+ * new XFile("file:///a/b/c").getPath()
+ * == "a/b/c"
+ * new XFile("nfs://server/").getPath()
+ * == ""
+ */
+ public String getPath() {
+ if (nativeFile != null)
+ return nativeFile.getPath();
+
+ return url.getPath();
+ }
+
+
+ /**
+ * Returns the absolute pathname of the file represented by this
+ * object.
+ *
+ * If this object is represented by a native pathname and is an
+ * absolute pathname, then return the pathname. Otherwise, return
+ * a pathname that is a concatenation of the current user
+ * directory, the separator character, and the pathname of this
+ * file object.
+ * The system property user.dir
contains the current
+ * user directory.
+ * XFile
.
+ */
+ public String getAbsolutePath() {
+ if (nativeFile != null)
+ return nativeFile.getAbsolutePath();
+
+ return urlStr;
+ }
+
+
+ /**
+ * Returns the canonical form of this XFile
object's
+ * pathname.
+ *
+ * If the object is represented by a URL name then the full
+ * URL is always returned. URL names are always canonical.
+ * XFile
object, or null
if the name
+ * has no parent part.
+ *
+ * If the name is a URL then the parent part is the URL with
+ * the last component of the pathname removed. If the URL
+ * has no pathname part, then the URL is returned unchanged.
+ * "/usr/lib"
+ * is "/usr"
whose parent part is "/"
,
+ * which in turn has no parent.
+ * On Windows platforms, the parent part of "c:\java"
+ * is "c:\"
, which in turn has no parent.
+ *
+ * @return the name of the parent directory
+ */
+ public String getParent() {
+ if (nativeFile != null)
+ return nativeFile.getParent();
+
+ return url.getParent();
+ }
+
+
+ /**
+ * Tests if the file represented by this XFile
+ * object is an absolute pathname.
+ *
+ * If the object is represented by a URL then true
+ * is always returned.
+ * If the XFile
represents a native name then
+ * the definition of an absolute pathname is system
+ * dependent. For example, on UNIX, a pathname is absolute if its
+ * first character is the separator character.
+ * On Windows platforms,
+ * a pathname is absolute if its first character is an ASCII
+ * '\' or '/', or if it begins with a letter followed by
+ * a colon.
+ *
+ * @return true
if the pathname indicated by the
+ * XFile
object is an absolute pathname;
+ * false
otherwise.
+ */
+ public boolean isAbsolute() {
+ if (nativeFile != null)
+ return nativeFile.isAbsolute();
+
+ return true;
+ }
+
+
+ /**
+ * Tests if this XFile
exists.
+ *
+ * @return true
if the file specified by this object
+ * exists; false
otherwise.
+ */
+ public boolean exists() {
+ if (!bind())
+ return false;
+
+ return xfa.exists();
+ }
+
+
+ /**
+ * Tests if the application can write to this file.
+ *
+ * @return true
if the application is allowed to
+ * write to a file whose name is specified by this object;
+ * false
otherwise.
+ */
+ public boolean canWrite() {
+ if (!bind())
+ return false;
+
+ return xfa.canWrite();
+ }
+
+
+ /**
+ * Tests if the application can read from the specified file.
+ *
+ * @return true
if the file specified by this
+ * object exists and the application can read the file;
+ * false
otherwise.
+ */
+ public boolean canRead() {
+ if (!bind())
+ return false;
+
+ return xfa.canRead();
+ }
+
+
+ /**
+ * Tests if the file represented by this XFile
+ * object is a "normal" file.
+ *
+ * A file is "normal" if it is not a directory and, in
+ * addition, satisfies other system-dependent criteria. Any
+ * non-directory file created by a Java application is guaranteed
+ * to be a normal file.
+ *
+ * @return true
if the file specified by this object
+ * exists and is a "normal" file; false
+ * otherwise.
+ */
+ public boolean isFile() {
+ if (!bind())
+ return false;
+
+ return xfa.isFile();
+ }
+
+
+ /**
+ * Tests if the file represented by this XFile
+ * object is a directory.
+ *
+ * @return true
if this XFile
exists
+ * and is a directory; false
otherwise.
+ */
+ public boolean isDirectory() {
+ if (!bind())
+ return false;
+
+ return xfa.isDirectory();
+ }
+
+
+
+ /**
+ * Returns the time that the file represented by this
+ * XFile
object was last modified.
+ * 0L
if the specified file
+ * does not exist.
+ */
+ public long lastModified() {
+ if (!bind())
+ return 0L;;
+
+ return xfa.lastModified();
+ }
+
+
+ /**
+ * Returns the length of the file represented by this
+ * XFile
object.
+ *
+ * @return the length, in bytes, of the file specified by this
+ * object, or 0L
if the specified file does
+ * not exist. The length constitutes the number of bytes
+ * readable via an InputStream. The length value for
+ * a directory is undefined.
+ */
+ public long length() {
+ if (!bind())
+ return 0L;
+
+ return xfa.exists() ? xfa.length() : 0L;
+ }
+
+
+ /**
+ * Renames the file specified by this XFile
object to
+ * have the pathname given by the XFile
argument.
+ *
+ * This object and dest
must represent filesystems
+ * of the same type. For instance: both native or both of
+ * the same URL scheme.
+ *
+ * After a successful renameTo, this object continues to
+ * be a valid reference to the file. Only the name
+ * is different.
+ *
+ * If the destination filename already exists, it will be replaced.
+ * The application must have permission to modify the source and
+ * destination directory.
+ *
+ * @param dest the new filename.
+ * @return true
if the renaming succeeds;
+ * false
otherwise.
+ */
+ public boolean renameTo(XFile dest) {
+ if (dest == null)
+ throw new NullPointerException();
+
+ if (! xfa.getClass().isInstance(dest.getAccessor()))
+ return false;
+
+ if (!bind())
+ return false;
+
+ boolean ok = xfa.renameTo(dest);
+
+ /*
+ * Only the name of the file is changed.
+ * Its data and state are unaffected.
+ * Hence we make this XFile object a clone
+ * of the dest XFile.
+ */
+ if (ok) {
+ url = dest.getURL();
+ urlStr = dest.getAbsolutePath();
+ nativeFile = dest.getNative();
+ xfa = dest.getAccessor();
+ bound = dest.getBound();
+ }
+
+ return ok;
+ }
+
+
+ /**
+ * Creates a directory whose pathname is specified by this
+ * XFile
object.
+ *
+ * If any parent directories in the pathname do not
+ * exist, the method will return false.
+ *
+ * @return true
if the directory could be created;
+ * false
otherwise.
+ */
+ public boolean mkdir() {
+ bind();
+
+ return xfa.mkdir();
+ }
+
+
+ /**
+ * Creates a directory whose pathname is specified by this
+ * XFile
object, including any necessary parent
+ * directories.
+ *
+ * @return true
if the directory (or directories)
+ * could be created; false
otherwise.
+ */
+ public boolean mkdirs() {
+ bind();
+
+ if (exists()) {
+ return false;
+ }
+ if (mkdir()) {
+ return true;
+ }
+
+ String parent = getParent();
+ return (parent != null) && (new XFile(parent).mkdirs() && mkdir());
+ }
+
+
+ /**
+ * Returns a list of the files in the directory specified by this
+ * XFile
object.
+ *
+ * @return an array of file names in the specified directory.
+ * This list does not include the current directory or the
+ * parent directory (".
" and "..
"
+ * on Unix systems).
+ */
+ public String[] list() {
+ if (!bind())
+ return null;;
+
+ return xfa.list();
+ }
+
+
+ /**
+ * Returns a list of the files in the directory specified by this
+ * XFile
that satisfy the specified filter.
+ *
+ * @param filter a filename filter.
+ * @return an array of file names in the specified directory.
+ * This list does not include the current directory or the
+ * parent directory (".
" and "..
"
+ * on Unix systems).
+ * @see com.sun.xfilenameFilter
+ */
+ public String[] list(XFilenameFilter filter) {
+ if (!bind())
+ return null;;
+
+ String names[] = list();
+
+ if (names == null) {
+ return null;
+ }
+
+ // Fill in the Vector
+ Vector v = new Vector();
+ for (int i = 0 ; i < names.length ; i++) {
+ if ((filter == null) || filter.accept(this, names[i])) {
+ v.addElement(names[i]);
+ }
+ }
+
+ // Create the array
+ String files[] = new String[v.size()];
+ v.copyInto(files);
+
+ return files;
+ }
+
+
+ /**
+ * Deletes the file specified by this object.
+ * If the target file to be deleted is a directory, it must be
+ * empty for deletion to succeed.
+ *
+ * @return true
if the file is successfully deleted;
+ * false
otherwise.
+ */
+ public boolean delete() {
+ if (!bind())
+ return false;;
+
+ boolean ok = xfa.delete();
+
+ bound = !ok;
+
+ return ok;
+ }
+
+
+ /**
+ * Computes a hashcode for the file.
+ *
+ * @return a hash code value for this XFile
object.
+ */
+ public int hashCode() {
+ return urlStr.hashCode() ^ 1234321;
+ }
+
+
+ /**
+ * Compares this object against the specified object.
+ *
+ * Returns true
if and only if the argument is
+ * not null
and is a XFile
object whose
+ * pathname is equal to the pathname of this object.
+ *
+ * @param obj the object to compare with.
+ * @return true
if the objects are the same;
+ * false
otherwise.
+ */
+ public boolean equals(Object obj) {
+ if ((obj == null) || (! (obj instanceof XFile)))
+ return false;
+
+ return url.toString().equals(((XFile)obj).getURL().toString());
+ }
+
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return a string giving the pathname of this object.
+ */
+ public String toString() {
+ if (nativeFile != null)
+ return (nativeFile.toString());
+
+ return urlStr;
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileAccessor.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileAccessor.java
new file mode 100644
index 0000000000..b6d379c134
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileAccessor.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.io.IOException;
+
+/**
+ * The XFileAccessor interface is implemented by filesystems that
+ * need to be accessed via the XFile API.
+ *
+ * Classes that implement this interface must be associated
+ * with a URL scheme that is structured according to the
+ * Common Internet Scheme syntax described in
+ * RFC 1738;
+ * an optional location part followed by a hierarchical set
+ * of slash separated directories.
+ *
+ * http.XFileAccessor
+ *
com.sun
and this composite name is located by the
+ * classLoader via the CLASSPATH.
+ * For instance, Sun's "nfs" XFileAccessor is installed as:
+ *
+ * com.sun.nfs.XFileAccessor
+ *
com.sun
can be changed by
+ * setting the System property java.protocol.xfile
+ * to any desired prefix or a list of prefixes separated by
+ * vertical bars. Each prefix in the list will be used to
+ * construct a package name and the classLoader will attempt
+ * to load that package via the CLASSPATH. This process will
+ * continue until the XFileAccessor is successfully loaded.
+ *
+ * When an "ftp" URL is used, the following package names will
+ * be constructed:
+ *
+ * java.protocol.xfile=com.acme|com.abc
+ *
+ * (the default "com.sun" prefix is automatically added to
+ * the end of the property list)
+ *
+ * com.acme.ftp.XFileAccessor
+ * com.abc.ftp.XFileAccessor
+ * com.sun.ftp.XFileAccessor
+ *
+ * In this case the "nfs" XFileAccessor from ABC, Inc. will
+ * be loaded in preference to Sun's NFS.
+ *
+ *
+ * @author Brent Callaghan
+ * @version 1.0, 04/08/98
+ * @see com.sun.xfile.XFile
+ */
+public interface XFileAccessor {
+
+
+ /**
+ * Open a file in this filesystem.
+ *
+ * This method is called before any other method.
+ * It may be used to open the real file.
+ *
+ * @param xf The XFile for the file to be accessed
+ * The URL will be of the form
+ *
+ * com.acme.nfs.XFileAccessor
+ * com.abc.nfs.XFileAccessor
+ * com.sun.nfs.XFileAccessor
+ *
true
if the file specified by this object
+ * exists; false
otherwise.
+ */
+ boolean exists();
+
+
+ /**
+ * Tests if the application can write to this file.
+ *
+ * @return true
if the application is allowed to
+ * write to a file whose name is specified by this
+ * object; false
otherwise.
+ */
+ boolean canWrite();
+
+
+ /**
+ * Tests if the application can read from the specified file.
+ *
+ * @return true
if the file specified by this
+ * object exists and the application can read the file;
+ * false
otherwise.
+ */
+ boolean canRead();
+
+ /**
+ * Tests if the file represented by this
+ * object is a "normal" file.
+ * true
if the file specified by this
+ * object exists and is a "normal"
+ * file; false
otherwise.
+ */
+ boolean isFile();
+
+
+ /**
+ * Tests if the file represented by this XFileAccessor
+ * object is a directory.
+ *
+ * @return true
if this XFileAccessor object
+ * exists and is a directory; false
+ * otherwise.
+ */
+ boolean isDirectory();
+
+
+ /**
+ * Returns the time that the file represented by this
+ * XFile
object was last modified.
+ * It is measured as the time in milliseconds since
+ * midnight, January 1, 1970 UTC.
+ * 0L
if the specified file
+ * does not exist.
+ */
+ long lastModified();
+
+
+ /**
+ * Returns the length of the file represented by this
+ * XFileAccessor object.
+ *
+ * @return the length, in bytes, of the file specified by
+ * this object, or 0L
if the specified
+ * file does not exist.
+ */
+ long length();
+
+
+ /**
+ * Creates an empty file whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the file was created;
+ * false
otherwise.
+ */
+ boolean mkfile();
+
+
+ /**
+ * Creates a directory whose pathname is specified by this
+ * XFileAccessor object.
+ *
+ * @return true
if the directory could be created;
+ * false
otherwise.
+ */
+ boolean mkdir();
+
+
+ /**
+ * Renames the file specified by this XFileAccessor object to
+ * have the pathname given by the XFileAccessor object argument.
+ *
+ * The destination XFile object will be of the same URL
+ * scheme as this object. The change of name must not
+ * affect the existence or accessibility of this object.
+ *
+ * @param dest the new filename.
+ * @return true
if the renaming succeeds;
+ * false
otherwise.
+ */
+ boolean renameTo(XFile dest);
+
+
+ /**
+ * Deletes the file specified by this object. If the target
+ * file to be deleted is a directory, it must be empty for deletion
+ * to succeed.
+ *
+ * @return true
if the file is successfully deleted;
+ * false
otherwise.
+ */
+ boolean delete();
+
+
+ /**
+ * Returns a list of the files in the directory specified by
+ * this XFileAccessor object.
+ *
+ * @return an array of file names in the specified directory.
+ * This list does not include the current directory or
+ * the parent directory (".
" and
+ * "..
" on Unix systems).
+ */
+ String[] list();
+
+
+ /**
+ * Reads a subarray as a sequence of bytes.
+ *
+ * @param b the buffer into which the data is read
+ * @param off the start offset in the data buffer
+ * @param len the maximum number of bytes to be read
+ * @param foff the offset into the file
+ * @return number of bytes read - zero if none.
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ int read(byte b[], int off, int len, long foff) throws IOException;
+
+
+ /**
+ * Writes a sub array as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data in the buffer
+ * @param len the number of bytes that are written
+ * @param foff the offset into the file
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ void write(byte b[], int off, int len, long foff) throws IOException;
+
+
+ /**
+ * Forces any buffered output bytes to be written out.
+ *
+ *
+ * import com.sun.xfile.*;
+ *
XFile.getExtensionAccessor()
method is invoked. The
+ * class loading process is identical to that of an
+ * XFileAccessor except for the final component of the package
+ * name: "XFileExtensionAccessor" instead of "XFileAccessor".
+ *
+ *
+ *
+ * @author Brent Callaghan
+ * @see com.sun.xfile.XFile#getExtensionAccessor()
+ * @see com.sun.xfile.XFileAccessor
+ */
+public abstract class XFileExtensionAccessor {
+
+ private XFile xf;
+
+ /*
+ * Constructor for the XFileExtensionAccessor.
+ *
+ * Invoked by the XFile class when its getExtensionAccessor
+ * method is called. The
+ * import com.sun.xfile.*;
+ *
XFile
argument of
+ * the constructor provides context for the methods
+ * within the class.
+ */
+ public XFileExtensionAccessor(XFile xf) {
+ this.xf = xf;
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileInputStream.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileInputStream.java
new file mode 100644
index 0000000000..c11c8b68c9
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileInputStream.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.io.*;
+
+/**
+ * An XFile input stream is an input stream for reading data from an
+ * XFile
.
+ */
+public class XFileInputStream extends InputStream {
+
+ private long fp; /* File Pointer */
+
+ /**
+ * File Accessor that implements the underlying filesystem
+ */
+ private XFileAccessor xfa;
+
+
+ /**
+ * Creates an input file stream to read from the specified
+ * XFile
object.
+ *
+ * @param xfile the file to be opened for reading.
+ * @exception java.io.FileNotFoundException if the file is
+ * not found.
+ */
+ public XFileInputStream(XFile xfile) throws IOException {
+ xfa = xfile.newAccessor();
+ if (! xfa.open(xfile, true, true)) // serial, read-only
+ throw new FileNotFoundException("no file");
+
+ if (!xfa.canRead())
+ throw new IOException("no read permission");
+ }
+
+
+ /**
+ * Creates an input file stream to read from a file with the
+ * specified name.
+ *
+ * @param name the system-dependent file name.
+ * @exception java.io.FileNotFoundException if the file is
+ * not found.
+ */
+ public XFileInputStream(String name) throws IOException {
+ this(new XFile(name));
+ }
+
+
+ /*
+ * Reads a subarray as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ synchronized private int XFAread(byte b[], int off, int len)
+ throws IOException {
+
+ if (b == null)
+ throw new NullPointerException();
+
+ if (len == 0)
+ return 0;
+
+ if (off < 0 || len < 0 || off >= b.length || (off + len) > b.length)
+ throw new IllegalArgumentException("Invalid argument");
+
+ int c = xfa.read(b, off, len, fp);
+
+ if (c <= 0)
+ return (-1);
+
+ fp += c;
+
+ return (c);
+ }
+
+ /**
+ * Reads a byte of data from this XFile.
+ *
+ * @return the next byte of data, or -1
+ * if the end of the file is reached.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int read() throws IOException {
+ byte[] b = new byte[1];
+
+ if (XFAread(b, 0, 1) != 1)
+ return (-1);
+
+ return b[0] & 0xff;
+ }
+
+ /**
+ * Reads up to b.length
bytes of data from this file
+ * into an array of bytes.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because
+ * the end of the file has been reached.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int read(byte b[]) throws IOException {
+ return XFAread(b, 0, b.length);
+ }
+
+
+ /**
+ * Reads up to len
bytes of data from this file
+ * into an array of bytes.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because
+ * the end of the file has been reached.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int read(byte b[], int off, int len) throws IOException {
+ return XFAread(b, off, len);
+ }
+
+
+ /**
+ * Returns the number of bytes yet to be read from this file.
+ *
+ * @return the number of bytes yet to be read from this file
+ * without blocking.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int available() throws IOException {
+ return (int)(xfa.length() - fp);
+ }
+
+
+ /**
+ * Skips over and discards n
bytes of data from the
+ * file.
+ *
+ * The skip
method may, for a variety of
+ * reasons, end up skipping over some smaller number of bytes,
+ * possibly 0
.
+ * The actual number of bytes skipped is returned.
+ *
+ * @param n the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public long skip(long n) throws IOException {
+ if (n < 0)
+ throw new IllegalArgumentException("illegal skip: " + n);
+
+ fp += n;
+
+ return n;
+ }
+
+
+ /**
+ * Closes this file input stream and releases any system resources
+ * associated with the stream.
+ *
+ * After the file is closed further I/O operations may
+ * throw IOException.
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void close() throws IOException {
+ xfa.close();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileOutputStream.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileOutputStream.java
new file mode 100644
index 0000000000..ad83d23623
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileOutputStream.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.io.*;
+
+/**
+ * An XFile output stream is an output stream for writing data to an
+ * XFile
.
+ */
+public class XFileOutputStream extends OutputStream {
+
+ private long fp; /* File Pointer */
+
+ /*
+ * File Accessor that implements the underlying filesystem
+ */
+ private XFileAccessor xfa;
+
+
+ /**
+ * Creates an XFile output stream to write to the specified
+ * XFile
object.
+ *
+ * @param file the XFile to be opened for writing.
+ * @exception java.io.IOException if the XFile could not
+ * be opened for writing.
+ */
+ public XFileOutputStream(XFile xfile) throws IOException {
+
+ xfa = xfile.newAccessor();
+
+ if (xfa.open(xfile, true, false)) { // serial, not readonly
+ if (!xfa.isFile())
+ throw new IOException("not a file");
+
+ if (!xfa.canWrite())
+ throw new IOException("no write permission");
+ }
+
+ if (!xfa.mkfile())
+ throw new IOException("no write permission");
+ }
+
+ /**
+ * Creates an output XFile stream to write to the file with the
+ * specified name.
+ *
+ * @param name the system-dependent filename.
+ * @exception java.io.IOException if the file could
+ * not be opened for writing.
+ */
+ public XFileOutputStream(String name) throws IOException {
+ this(new XFile(name));
+ }
+
+
+ /**
+ * Creates an output file for the specified XFile object.
+ *
+ * @param xfile the XFile to be opened for writing.
+ * @param append true if writes begin at the end of the file
+ * @exception java.io.IOException If the file is not found.
+ */
+ public XFileOutputStream(XFile xfile, boolean append)
+ throws IOException {
+
+ boolean isExist;
+
+ xfa = xfile.newAccessor();
+
+ if ((isExist = xfa.open(xfile, true, false))) { // serial, not readonly
+ if (!xfa.isFile())
+ throw new IOException("not a file");
+
+ if (!xfa.canWrite())
+ throw new IOException("no write permission");
+ }
+
+ /*
+ * If file doesn't exist or append is False create the file
+ */
+ if (!isExist || !append) {
+ if (!xfa.mkfile())
+ throw new IOException("no write permission");
+ }
+
+ if (append)
+ fp = xfa.length();
+ }
+
+
+ /**
+ * Creates an output file with the specified name or URL.
+ *
+ * @param name the native name or URL
+ * @param append true if writes begin at the end of the file
+ * @exception java.io.IOException If the file is not found.
+ */
+ public XFileOutputStream(String name, boolean append)
+ throws IOException {
+
+ this(new XFile(name), append);
+
+ }
+
+
+ /*
+ * All writes to the Accessor go through here.
+ */
+ synchronized private void XFAwrite(byte b[], int off, int len)
+ throws IOException {
+
+ if (b == null)
+ throw new NullPointerException();
+
+ if (len == 0)
+ return;
+
+ if (off < 0 || len < 0 || off >= b.length || (off + len) > b.length)
+ throw new IllegalArgumentException("Invalid argument");
+
+ xfa.write(b, off, len, fp);
+ fp += len;
+ }
+
+
+ /**
+ * Writes the specified byte to this file output stream.
+ *
+ * @param b the byte to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void write(int b) throws IOException {
+ XFAwrite(new byte[] {(byte)b}, 0, 1);
+ }
+
+
+ /**
+ * Writes b.length
bytes from the specified byte array
+ * to this file output stream.
+ *
+ * @param b the data.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void write(byte b[]) throws IOException {
+ XFAwrite(b, 0, b.length);
+ }
+
+
+ /**
+ * Writes len
bytes from the specified byte array
+ * starting at offset off
to this XFile output stream.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void write(byte b[], int off, int len) throws IOException {
+ XFAwrite(b, off, len);
+ }
+
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be written out.
+ * close
method of this XFile
+ * output stream is called when there are no more references
+ * to this stream.
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ * @see com.sun.xfile.XFileInputStream#close()
+ */
+ protected void finalize() throws IOException {
+ close();
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileReader.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileReader.java
new file mode 100644
index 0000000000..4068cea979
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileReader.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.io.*;
+
+/**
+ * Convenience class for reading character files.
+ *
+ * The constructors of this class assume that the default character
+ * encoding and the default byte-buffer size are appropriate.
+ * To specify these values yourself, construct an
+ * InputStreamReader on a FileInputStream.
+ *
+ * @see InputStreamReader
+ * @see XFileInputStream
+ *
+ */
+public class XFileReader extends InputStreamReader {
+
+ public XFileReader(String fileName) throws IOException {
+ super(new XFileInputStream(fileName));
+ }
+
+ public XFileReader(XFile file) throws IOException {
+ super(new XFileInputStream(file));
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileWriter.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileWriter.java
new file mode 100644
index 0000000000..28f32db0e9
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFileWriter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.io.*;
+
+/**
+ * Convenience class for writing character files.
+ *
+ * The constructors of this class assume that the default character
+ * encoding and the default byte-buffer size are acceptable.
+ * To specify these values yourself, construct an
+ * OutputStreamWriter on a FileOutputStream.
+ *
+ * @see OutputStreamWriter
+ * @see XFileOutputStream
+ */
+public class XFileWriter extends OutputStreamWriter {
+
+ public XFileWriter(String fileName) throws IOException {
+ super(new XFileOutputStream(fileName));
+ }
+
+ public XFileWriter(String fileName, boolean append) throws IOException {
+ super(new XFileOutputStream(fileName, append));
+ }
+
+ public XFileWriter(XFile file) throws IOException {
+ super(new XFileOutputStream(file));
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFilenameFilter.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFilenameFilter.java
new file mode 100644
index 0000000000..34e41ac3af
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFilenameFilter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+/**
+ * Instances of classes that implement this interface are used to
+ * filter filenames. These instances are used to filter directory
+ * listings in the list
method of class
+ * XFile
.
+ *
+ * @see com.sun.xfile.XFile#list(com.sun.xfile.XFilenameFilter)
+ */
+public interface XFilenameFilter {
+
+ /**
+ * Tests if a specified file should be included in a file list.
+ *
+ * @param dir the directory in which the file was found.
+ * @param name the name of the file.
+ * @return true
if the name should be included in
+ * the file list; false
otherwise.
+ */
+ boolean accept(XFile dir, String name);
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFurl.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFurl.java
new file mode 100644
index 0000000000..e6a9b107a4
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XFurl.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 1999, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.net.MalformedURLException;
+
+/**
+ * This is just a dumb URL parser class.
+ * I wrote it because I got fed up with the
+ * JDK URL class calling NFS URL's "invalid"
+ * simply because the Handler wasn't installed.
+ *
+ * @author Brent Callaghan
+ */
+class XFurl {
+
+ private String url;
+ private String protocol;
+ private String location;
+ private String path;
+
+ XFurl(String url) throws MalformedURLException {
+ int p, q, r;
+
+ url = url.trim(); // remove leading & trailing spaces
+ this.url = url;
+ int end = url.length();
+
+ p = url.indexOf(':');
+ if (p < 0)
+ throw new MalformedURLException("colon expected");
+ protocol = url.substring(0, p);
+ q = p;
+ p++; // skip colon
+ if (url.regionMatches(p, "//", 0, 2)) { // have hostname
+ p += 2;
+ q = url.indexOf('/', p);
+ if (q < 0)
+ q = end;
+ location = url.substring(p, q);
+ }
+
+ path = q < end ? url.substring(q + 1, end) : "";
+
+ // Remove trailing slashes from path
+
+ while (path.endsWith("/"))
+ path = path.substring(0, path.length()-1);
+ }
+
+ XFurl(XFurl base, String rpath) throws MalformedURLException {
+
+ protocol = base.getProtocol();
+ location = base.getLocation();
+ path = base.getPath();
+
+ rpath = rpath.trim();
+
+ if (rpath.indexOf("://") > 0) { // URL - ignore base
+ url = rpath;
+ XFurl u = new XFurl(rpath);
+ protocol = u.getProtocol();
+ location = u.getLocation();
+ path = u.getPath();
+
+ } else if (rpath.startsWith("/")) { // absolute path
+ path = rpath.substring(1);
+
+ } else {
+
+ /*
+ * Escape any "%" characters in the name
+ * e.g. "%markup" -> "%25markup"
+ */
+ String npath = "";
+ int len = rpath.length();
+ int p1 = 0, p2;
+
+ while (true) {
+ p2 = rpath.indexOf('%', p1); // look for %
+ if (p2 < 0)
+ p2 = len;
+
+ npath += rpath.substring(p1, p2);
+ if (p2 >= len)
+ break;
+
+ npath += "%25"; // replace % with %25
+ p1 = p2 + 1;
+ }
+ rpath = npath;
+ len = rpath.length();
+
+ /*
+ * Combine base path with relative path
+ * according to rules in RFCs 1808 & 2054
+ *
+ * e.g. /a/b/c + x = /a/b/c/x
+ * /a/b/c + /y = /y
+ * /a/b/c + ../z = /a/b/z
+ * /a/b/c + d/. = /a/b/c/d
+ */
+ String bpath = base.getPath();
+ p1 = 0;
+
+ while (p1 <= len) {
+ p2 = rpath.indexOf("/", p1);
+ if (p2 < 0)
+ p2 = len;
+ String component = rpath.substring(p1, p2);
+
+ if (component.equals(".") || component.equals("")) {
+ // ignore
+ } else if (component.equals("..")) {
+ int q = bpath.lastIndexOf("/");
+ bpath = q < 0 ? "" : bpath.substring(0, q);
+ } else {
+ if (bpath.equals(""))
+ bpath = component;
+ else
+ bpath += "/" + component;
+ }
+ p1 = p2 + 1;
+ }
+ path = bpath;
+ }
+ }
+
+ String getProtocol() {
+ return (protocol);
+ }
+
+ String getLocation() {
+ return (location);
+ }
+
+ String getPath() {
+ return (path);
+ }
+
+ String getParent() {
+
+ if (path.equals(""))
+ return null; // no parent
+
+ String s = protocol + ":";
+
+ if (location != null)
+ s += "//" + location;
+
+ int index = path.lastIndexOf('/');
+ if (index >= 0)
+ s += "/" + path.substring(0, index);
+
+ return s;
+ }
+
+ String getName() {
+ int index = path.lastIndexOf('/');
+ return index < 0 ? path : path.substring(index + 1);
+ }
+
+ public String toString() {
+ String s = protocol + ":";
+
+ if (location != null)
+ s += "//" + location;
+
+ if (path != null)
+ s += "/" + path;
+
+ return (s);
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XRandomAccessFile.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XRandomAccessFile.java
new file mode 100644
index 0000000000..2aeda3ecd6
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfile/XRandomAccessFile.java
@@ -0,0 +1,912 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfile;
+
+import java.io.*;
+
+/**
+ * Instances of this class support both reading and writing to a
+ * random access file. An application can modify the position in the
+ * file at which the next read or write occurs.
+ * This class provides a sense of security
+ * by offering methods that allow specified mode accesses of
+ * read-only or read-write to files.
+ *
+ */
+public class XRandomAccessFile implements DataOutput, DataInput {
+
+ private long fp; /* File Pointer */
+
+ private boolean readOnly;
+
+ /*
+ * File Accessor that implements the underlying filesystem
+ */
+ private XFileAccessor xfa;
+
+
+ /**
+ * Creates a random access file stream to read from, and optionally
+ * to write to, the file specified by the XFile
+ * argument.
+ *
+ * The mode argument must either be equal to "r"
or to
+ * "rw"
, indicating either to open the file for input,
+ * or for both input and output, respectively.
+ *
+ * @param xf the XFile object.
+ * @param mode the access mode.
+ * @exception java.lang.IllegalArgumentException if the mode
+ * argument is not equal to "r"
or
+ * to "rw"
.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public XRandomAccessFile(XFile xf, String mode) throws IOException {
+
+
+ if (! (mode.equals("r") || mode.equals("rw")))
+ throw new IllegalArgumentException("mode must be r or rw");
+ readOnly = mode.equals("r");
+ xfa = xf.newAccessor();
+ xfa.open(xf, false, readOnly);
+
+ if (xfa.exists()) {
+ if (readOnly && ! xfa.canRead())
+ throw new IOException("no read permission");
+ if (! readOnly && ! xfa.canWrite())
+ throw new IOException("no write permission");
+ } else {
+ if (readOnly)
+ throw new IOException("no such file or directory");
+
+ if (! xfa.mkfile())
+ throw new IOException("no write permission");
+ }
+ }
+
+ /**
+ * Creates a random access file to read from, and optionally
+ * to write to, a file with the specified name.
+ * "r"
or
+ * "rw"
, indicating either to open the file for input
+ * or for both input and output.
+ *
+ * @param name the native or URL file name.
+ * @param mode the access mode.
+ * @exception java.lang.IllegalArgumentException if the mode
+ * argument is not equal to "r"
or to
+ * "rw"
.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public XRandomAccessFile(String name, String mode) throws IOException {
+ this(new XFile(name), mode);
+ }
+
+
+ // 'Read' primitives
+
+ private int XFAread(byte b[], int off, int len) throws IOException {
+
+ if (b == null)
+ throw new NullPointerException();
+
+ if (len == 0)
+ return 0;
+
+ if (off < 0 || len < 0 || off >= b.length || (off + len) > b.length)
+ throw new IllegalArgumentException("Invalid argument");
+
+ int c = xfa.read(b, off, len, fp);
+
+ if (c >= 0)
+ fp += c;
+
+ return c;
+ }
+
+
+ /**
+ * Reads a byte of data from this file.
+ *
+ * @return the next byte of data, or -1
if the
+ * end of the file is reached.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int read() throws IOException {
+ byte[] b = new byte[1];
+
+ if (XFAread(b, 0, 1) != 1)
+ return (-1);
+
+ return b[0] & 0xff;
+ }
+
+
+ /**
+ * Reads up to len
bytes of data from this file into
+ * an array of bytes.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because
+ * the end of the file has been reached.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int read(byte b[], int off, int len) throws IOException {
+ return XFAread(b, off, len);
+ }
+
+
+ /**
+ * Reads up to b.length
bytes of data from this file
+ * into an array of bytes.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because
+ * the end of this file has been reached.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int read(byte b[]) throws IOException {
+ return XFAread(b, 0, b.length);
+ }
+
+
+ /**
+ * Reads b.length
bytes from this file into the byte
+ * array.
+ *
+ * @param b the buffer into which the data is read.
+ * @exception java.io.EOFException if this file reaches
+ * the end before reading all the bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void readFully(byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+
+ /**
+ * Reads exactly len
bytes from this file into
+ * the byte array.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the number of bytes to read.
+ * @exception java.io.EOFException if this file reaches the
+ * end before reading all the bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void readFully(byte b[], int off, int len)
+ throws IOException {
+
+ if (XFAread(b, off, len) < len)
+ throw new EOFException();
+ }
+
+
+ /**
+ * Skips exactly n
bytes of input.
+ * n
.
+ * @exception java.io.EOFException if this file reaches the end
+ * before skipping all the bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public int skipBytes(int n) throws IOException {
+
+ if (fp + n > xfa.length())
+ throw new EOFException();
+
+ seek(fp + n);
+
+ return n;
+ }
+
+
+ // 'Write' primitives
+
+ private void XFAwrite(byte b[], int off, int len)
+ throws IOException {
+
+ if (b == null)
+ throw new NullPointerException();
+
+ if (readOnly)
+ throw new IOException("Read only file");
+
+ if (off < 0 || len < 0)
+ throw new IllegalArgumentException("Invalid argument");
+
+ xfa.write(b, off, len, fp);
+ fp += len;
+ }
+
+
+ /**
+ * Writes the specified byte to this file.
+ *
+ * @param b the byte
to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void write(int b) throws IOException {
+ XFAwrite(new byte[]{(byte)b}, 0, 1);
+ }
+
+
+ /**
+ * Writes a sub array as a sequence of bytes.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @exception java.io.IOException If an I/O error has occurred.
+ */
+ private void writeBytes(byte b[], int off, int len)
+ throws IOException {
+
+ XFAwrite(b, off, len);
+ }
+
+
+ /**
+ * Writes b.length
bytes from the specified byte array
+ * starting at offset off
to this file.
+ *
+ * @param b the data.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void write(byte b[]) throws IOException {
+ writeBytes(b, 0, b.length);
+ }
+
+
+ /**
+ * Writes len
bytes from the specified byte array
+ * starting at offset off
to this file.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void write(byte b[], int off, int len) throws IOException {
+ writeBytes(b, off, len);
+ }
+
+
+ // 'Random access' stuff
+
+ /**
+ * Returns the current offset in this file.
+ *
+ * @return the offset from the beginning of the file, in bytes,
+ * at which the next read or write occurs.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public long getFilePointer() throws IOException {
+ return fp;
+ }
+
+
+ /**
+ * Sets the offset from the beginning of this file at which
+ * the next read or write occurs.
+ *
+ * @param pos the absolute position.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void seek(long pos) throws IOException {
+
+ if ( pos < 0 || (readOnly && pos >= xfa.length()))
+ throw new IOException("illegal seek" + pos);
+
+ fp = pos;
+ }
+
+
+ /**
+ * Returns the length of this file.
+ *
+ * @return the length of this file.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public long length() throws IOException {
+ return xfa.length();
+ }
+
+
+ /**
+ * Forces any buffered output bytes to be written out.
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void flush() throws IOException {
+
+ if (readOnly)
+ throw new IOException("Read only file");
+
+ xfa.flush();
+ }
+
+
+ /**
+ * Closes this random access file and flushes any
+ * unwritten data to the file.
+ *
+ * After the file is closed further I/O operations may
+ * throw IOException.
+ *
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public void close() throws IOException {
+ xfa.close();
+ }
+
+
+ //
+ // Some "reading/writing Java data types" methods stolen from
+ // DataInputStream and DataOutputStream.
+ //
+
+ /**
+ * Reads a boolean
from this file. This method reads a
+ * single byte from the file. A value of 0
represents
+ * false
. Any other value represents true
.
+ * This method blocks until the byte is read, the end of the stream
+ * is detected, or an exception is thrown.
+ *
+ * @return the boolean
value read.
+ * @exception java.io.EOFException if this file has reached the end.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final boolean readBoolean() throws IOException {
+ int ch = this.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (ch != 0);
+ }
+
+
+ /**
+ * Reads a signed 8-bit value from this file. This method reads a
+ * byte from the file. If the byte read is b
, where
+ * 0 <= b <= 255
,
+ * then the result is:
+ *
+ *
+ * (byte)(b)
+ *
byte
.
+ * @exception java.io.EOFException if this file has reached the end.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final byte readByte() throws IOException {
+ int ch = this.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (byte)(ch);
+ }
+
+
+ /**
+ * Reads an unsigned 8-bit number from this file. This method reads
+ * a byte from this file and returns that byte.
+ * b1
and b2
, where each of the two values is
+ * between 0
and 255
, inclusive, then the
+ * result is equal to:
+ *
+ *
+ * (short)((b1 << 8) | b2)
+ *
b1
and b2
, where
+ * 0 <= b1, b2 <= 255
,
+ * then the result is equal to:
+ *
+ *
+ * (b1 << 8) | b2
+ *
b1
and b2
, where
+ * 0 <= b1, b2 <= 255
,
+ * then the result is equal to:
+ *
+ *
+ * (char)((b1 << 8) | b2)
+ *
b1
,
+ * b2
, b3
, and b4
, where
+ * 0 <= b1, b2, b3, b4 <= 255
,
+ * then the result is equal to:
+ *
+ *
+ * (b1 << 24) | (b2 << 16) + (b3 << 8) + b4
+ *
int
.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * four bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final int readInt() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ int ch3 = this.read();
+ int ch4 = this.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0)
+ throw new EOFException();
+ return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
+ }
+
+
+ /**
+ * Reads a signed 64-bit integer from this file. This method reads eight
+ * bytes from the file. If the bytes read, in order, are
+ * b1
, b2
, b3
,
+ * b4
, b5
, b6
,
+ * b7
, and b8,
where:
+ *
+ *
+ * 0 <= b1, b2, b3, b4, b5, b6, b7, b8 <=255,
+ *
+ *
+ * ((long)b1 << 56) + ((long)b2 << 48)
+ * + ((long)b3 << 40) + ((long)b4 << 32)
+ * + ((long)b5 << 24) + ((long)b6 << 16)
+ * + ((long)b7 << 8) + b8
+ *
long
.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * eight bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final long readLong() throws IOException {
+ return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
+ }
+
+
+ /**
+ * Reads a float
from this file. This method reads an
+ * int
value as if by the readInt
method
+ * and then converts that int
to a float
+ * using the intBitsToFloat
method in class
+ * Float
.
+ * float
.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * four bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ * @see com.sun.xfile.XRandomAccessFile#readInt()
+ * @see java.lang.Float#intBitsToFloat(int)
+ */
+ public final float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+
+ /**
+ * Reads a double
from this file. This method reads a
+ * long
value as if by the readLong
method
+ * and then converts that long
to a double
+ * using the longBitsToDouble
method in
+ * class Double
.
+ * double
.
+ * @exception java.io.EOFException if this file reaches the end before reading
+ * eight bytes.
+ * @exception java.io.IOException if an I/O error occurs.
+ * @see com.sun.xfile.XRandomAccessFile#readLong()
+ * @see java.lang.Double#longBitsToDouble(long)
+ */
+ public final double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+
+ /**
+ * Reads the next line of text from this file. This method
+ * successively reads bytes from the file until it reaches the end of
+ * a line of text.
+ * '\r'
), a newline character ('\n'
), a
+ * carriage-return character immediately followed by a newline
+ * character, or the end of the input stream. The line-terminating
+ * character(s), if any, are included as part of the string returned.
+ * readUnsignedShort
. This value gives the number of
+ * following bytes that are in the encoded string, not
+ * the length of the resulting string. The following bytes are then
+ * interpreted as bytes encoding characters in the UTF-8 format
+ * and are converted into characters.
+ * boolean
to the file as a 1-byte value. The
+ * value true
is written out as the value
+ * (byte)1
; the value false
is written out
+ * as the value (byte)0
.
+ *
+ * @param v a boolean
value to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeBoolean(boolean v) throws IOException {
+ write(v ? 1 : 0);
+ //written++;
+ }
+
+
+ /**
+ * Writes a byte
to the file as a 1-byte value.
+ *
+ * @param v a byte
value to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeByte(int v) throws IOException {
+ write(v);
+ //written++;
+ }
+
+
+ /**
+ * Writes a short
to the file as two bytes, high byte first.
+ *
+ * @param v a short
to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeShort(int v) throws IOException {
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ //written += 2;
+ }
+
+
+ /**
+ * Writes a char
to the file as a 2-byte value, high
+ * byte first.
+ *
+ * @param v a char
value to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeChar(int v) throws IOException {
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ //written += 2;
+ }
+
+
+ /**
+ * Writes an int
to the file as four bytes, high byte first.
+ *
+ * @param v an int
to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeInt(int v) throws IOException {
+ write((v >>> 24) & 0xFF);
+ write((v >>> 16) & 0xFF);
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ //written += 4;
+ }
+
+
+ /**
+ * Writes a long
to the file as eight bytes, high byte first.
+ *
+ * @param v a long
to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeLong(long v) throws IOException {
+ write((int)(v >>> 56) & 0xFF);
+ write((int)(v >>> 48) & 0xFF);
+ write((int)(v >>> 40) & 0xFF);
+ write((int)(v >>> 32) & 0xFF);
+ write((int)(v >>> 24) & 0xFF);
+ write((int)(v >>> 16) & 0xFF);
+ write((int)(v >>> 8) & 0xFF);
+ write((int)(v >>> 0) & 0xFF);
+ //written += 8;
+ }
+
+
+ /**
+ * Converts the float argument to an int
using the
+ * floatToIntBits
method in class Float
,
+ * and then writes that int
value to the file as a
+ * 4-byte quantity, high byte first.
+ *
+ * @param v a float
value to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ * @see java.lang.Float#floatToIntBits(float)
+ */
+ public final void writeFloat(float v) throws IOException {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+
+ /**
+ * Converts the double argument to a long
using the
+ * doubleToLongBits
method in class Double
,
+ * and then writes that long
value to the file as an
+ * 8-byte quantity, high byte first.
+ *
+ * @param v a double
value to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ * @see java.lang.Double#doubleToLongBits(double)
+ */
+ public final void writeDouble(double v) throws IOException {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ /**
+ * Writes the string to the file as a sequence of bytes. Each
+ * character in the string is written out, in sequence, by discarding
+ * its high eight bits.
+ *
+ * @param s a string of bytes to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeBytes(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0 ; i < len ; i++) {
+ write((byte)s.charAt(i));
+ }
+ //written += len;
+ }
+
+
+ /**
+ * Writes a string to the file as a sequence of characters. Each
+ * character is written to the data output stream as if by the
+ * writeChar
method.
+ *
+ * @param s a String
value to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ * @see com.sun.xfile.XRandomAccessFile#writeChar(int)
+ */
+ public final void writeChars(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0 ; i < len ; i++) {
+ int v = s.charAt(i);
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ }
+ //written += len * 2;
+ }
+
+
+ /**
+ * Writes a string to the file using UTF-8 encoding in a
+ * machine-independent manner.
+ * writeShort
method giving the number of bytes to
+ * follow. This value is the number of bytes actually written out,
+ * not the length of the string. Following the length, each character
+ * of the string is output, in sequence, using the UTF-8 encoding
+ * for each character.
+ *
+ * @param str a string to be written.
+ * @exception java.io.IOException if an I/O error occurs.
+ */
+ public final void writeUTF(String str) throws IOException {
+ int strlen = str.length();
+ int utflen = 0;
+
+ for (int i = 0 ; i < strlen ; i++) {
+ int c = str.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ utflen++;
+ } else if (c > 0x07FF) {
+ utflen += 3;
+ } else {
+ utflen += 2;
+ }
+ }
+
+ if (utflen > 65535)
+ throw new UTFDataFormatException();
+
+ write((utflen >>> 8) & 0xFF);
+ write((utflen >>> 0) & 0xFF);
+ for (int i = 0 ; i < strlen ; i++) {
+ int c = str.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ write(c);
+ } else if (c > 0x07FF) {
+ write(0xE0 | ((c >> 12) & 0x0F));
+ write(0x80 | ((c >> 6) & 0x3F));
+ write(0x80 | ((c >> 0) & 0x3F));
+ //written += 2;
+ } else {
+ write(0xC0 | ((c >> 6) & 0x1F));
+ write(0x80 | ((c >> 0) & 0x3F));
+ //written += 1;
+ }
+ }
+ //written += strlen + 2;
+ }
+}
diff --git a/mucommander-protocol-nfs/src/main/java/com/sun/xfilechooser/BeanXFile.java b/mucommander-protocol-nfs/src/main/java/com/sun/xfilechooser/BeanXFile.java
new file mode 100644
index 0000000000..3370cd0284
--- /dev/null
+++ b/mucommander-protocol-nfs/src/main/java/com/sun/xfilechooser/BeanXFile.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 1998, 2007 Sun Microsystems, Inc.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS
+ * SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE
+ * AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE
+ * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE
+ * LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
+ * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+ * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed,licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.sun.xfilechooser;
+
+import java.io.File;
+import com.sun.xfile.*;
+import java.io.IOException;
+
+/**
+ * The BeanXFile class is the interface that makes an XFile object
+ * look like a File object. This class is needed to support the
+ * UI of the JFileChooser which accesses file objects.
+ * Thus all the methods would call the corresponding XFile methods.
+ *
+ * @see #XFile
+ */
+public class BeanXFile extends File {
+
+ private XFile beanXF;
+
+ /*
+ * BeanXFile constructors which mirror the File I/O constructors.
+ */
+ public BeanXFile(String path) {
+ super(path);
+ beanXF = new XFile(path);
+ }
+
+ public BeanXFile(File dir, String name) {
+ super(dir, name);
+
+ XFile parentXF = new XFile(dir.getAbsolutePath());
+ beanXF = new XFile(parentXF, name);
+ }
+
+ /*
+ * XFile Methods that can be accessed.
+ */
+ public String getPath() {
+ String path = beanXF.getPath();
+
+ // For nfs URLs, if the url is nfs://\n/
\n
\n");
+ } else {
+ buf.append("\n" + path + "
\n
\n");
+ }
+
+ // Display a URL link to the parent directory if this is
+ // not the root directory
+ if (!root_directory) {
+ String parentURL = url.toString();
+ int limit = parentURL.length() - 1;
+ if (url.getFile() != null) {
+ if (parentURL.endsWith("/")) {
+ limit--;
+ }
+
+ parentURL = parentURL.substring(0,
+ parentURL.lastIndexOf('/', limit));
+ buf.append("");
+ buf.append("Go To Parent Directory
\n
\n");
+ }
+ }
+
+ // Display the list of files in the directory
+ dirList = nfsFile.list();
+ if (dirList != null) {
+
+ // Sort the entries in the directory list
+ StringCompare strComp = new StringCompare();
+ //Sort.quicksort(dirList, strComp);
+ Arrays.sort(dirList, strComp);
+
+ boolean hideDotFiles = Boolean.getBoolean("file.hidedotfiles");
+
+ for (int i = 0 ; i < dirList.length ; i++) {
+ XFile dirEntry;
+
+ // Don't display the ".." or "." directory entries
+ if (dirList[i].equals("..") || dirList[i].equals(".")) {
+ continue;
+ }
+
+ // Skip files beginning with '.' if the file.hidedotfiles
+ // property is set
+ if (hideDotFiles) {
+ if (dirList[i].charAt(0) == '.') {
+ continue;
+ }
+ }
+
+ // Display an image file for each directory entry
+ buf.append("\n");
+ } else if (dirEntry.isFile()) {
+ String imageFileName = MimeEntry_defaultImagePath + "/file.gif";
+
+ // Find the file image to use using the file's .suffix
+ entry = mt.findByFileName(dirList[i]);
+ if (entry != null) {
+ String realImageName = entry.getImageFileName();
+ if (realImageName != null) {
+ imageFileName = realImageName;
+ }
+ }
+
+ buf.append(imageFileName);
+ buf.append("\" WIDTH=" + iconWidth + " HEIGHT=" + iconHeight +
+ ">\n");
+ } else {
+ // Entry is a symbolic link. Use the default file image for now.
+ buf.append(MimeEntry_defaultImagePath +
+ "/file.gif\" WIDTH=" + iconWidth +
+ " HEIGHT=" + iconHeight + ">\n");
+ }
+
+ //dirEntry.close();
+
+ // Display the directory entry's name
+ buf.append("");
+ buf.append(dirList[i] + "\n
");
+
+ }
+ }
+
+ // Finish the HTML document
+ buf.append("\n