├── safs-core ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── llamalab │ │ │ └── safs │ │ │ ├── OpenOption.java │ │ │ ├── CopyOption.java │ │ │ ├── PathMatcher.java │ │ │ ├── attributes │ │ │ ├── GroupPrincipal.java │ │ │ ├── AttributeView.java │ │ │ ├── FileAttributeView.java │ │ │ ├── FileAttribute.java │ │ │ ├── UserPrincipal.java │ │ │ ├── PosixFilePermission.java │ │ │ ├── PosixFileAttributes.java │ │ │ ├── FileOwnerAttributeView.java │ │ │ ├── BasicFileAttributeView.java │ │ │ ├── PosixFileAttributeView.java │ │ │ ├── UserPrincipalNotFoundException.java │ │ │ ├── UserPrincipalLookupService.java │ │ │ ├── BasicFileAttributes.java │ │ │ ├── FileTime.java │ │ │ └── PosixFilePermissions.java │ │ │ ├── FileVisitOption.java │ │ │ ├── FileVisitResult.java │ │ │ ├── internal │ │ │ ├── FileType.java │ │ │ ├── DefaultFileSystem.java │ │ │ ├── WatchEventKind.java │ │ │ ├── PartialBasicFileAttributes.java │ │ │ ├── BasicFileAttributeValue.java │ │ │ ├── CompleteBasicFileAttributes.java │ │ │ ├── UserPrincipalFactory.java │ │ │ ├── PathDescender.java │ │ │ ├── AbstractDirectoryStream.java │ │ │ ├── BasicFileAttribute.java │ │ │ ├── AttributeParser.java │ │ │ ├── AbstractWatchService.java │ │ │ ├── SearchSet.java │ │ │ ├── AbstractWatchKey.java │ │ │ ├── SegmentEntry.java │ │ │ └── AbstractFileSystemProvider.java │ │ │ ├── ClosedFileSystemException.java │ │ │ ├── ClosedWatchServiceException.java │ │ │ ├── LinkOption.java │ │ │ ├── ReadOnlyFileSystemException.java │ │ │ ├── StandardCopyOption.java │ │ │ ├── NoSuchFileException.java │ │ │ ├── NotDirectoryException.java │ │ │ ├── FileSystemLoopException.java │ │ │ ├── DirectoryNotEmptyException.java │ │ │ ├── FileAlreadyExistsException.java │ │ │ ├── WatchKey.java │ │ │ ├── DirectoryStream.java │ │ │ ├── StandardOpenOption.java │ │ │ ├── ProviderMismatchException.java │ │ │ ├── ProviderNotFoundException.java │ │ │ ├── AtomicMoveNotSupportedException.java │ │ │ ├── FileSystemNotFoundException.java │ │ │ ├── FileSystemAlreadyExistsException.java │ │ │ ├── Watchable.java │ │ │ ├── WatchService.java │ │ │ ├── NotLinkException.java │ │ │ ├── AccessDeniedException.java │ │ │ ├── WatchEvent.java │ │ │ ├── spi │ │ │ ├── FileTypeDetector.java │ │ │ └── FileSystemProvider.java │ │ │ ├── channels │ │ │ └── SeekableByteChannel.java │ │ │ ├── FileStore.java │ │ │ ├── DirectoryIteratorException.java │ │ │ ├── FileVisitor.java │ │ │ ├── StandardWatchEventKinds.java │ │ │ ├── unix │ │ │ ├── AbstractUnixFileSystemProvider.java │ │ │ └── AbstractUnixFileSystem.java │ │ │ ├── SimpleFileVisitor.java │ │ │ ├── FileSystem.java │ │ │ ├── java │ │ │ ├── DefaultJavaFileSystemProvider.java │ │ │ ├── SeekableByteChannelWrapper.java │ │ │ └── JavaFileSystem.java │ │ │ ├── Paths.java │ │ │ ├── InvalidPathException.java │ │ │ ├── Path.java │ │ │ ├── FileSystemException.java │ │ │ └── FileSystems.java │ └── test │ │ └── java │ │ └── com │ │ └── llamalab │ │ └── safs │ │ └── MiscTests.java ├── README.md └── build.gradle ├── safs-android-app ├── .gitignore ├── proguard-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── llamalab │ │ └── safs │ │ └── android │ │ └── app │ │ └── OpenDocumentTreeActivity.java ├── build.gradle └── README.md ├── safs-android ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── llamalab │ │ └── safs │ │ └── android │ │ ├── FileStoreNotFoundException.java │ │ ├── AndroidOpenOption.java │ │ ├── NotDocumentUriException.java │ │ ├── AndroidWatchEventKind.java │ │ ├── AndroidPath.java │ │ ├── PrimaryFileStore.java │ │ ├── StatBasicFileAttributes.java │ │ ├── StorageVolumeFileStore.java │ │ ├── AndroidFileStore.java │ │ ├── AndroidFiles.java │ │ ├── AndroidWatchEventKinds.java │ │ ├── SeekableByteChannelWrapper.java │ │ └── AndroidWatchService.java ├── proguard-rules.pro ├── build.gradle └── README.md ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── vcs.xml ├── misc.xml ├── runConfigurations.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml └── codeStyles │ └── Project.xml ├── .gitignore ├── gradle.properties ├── bintray.gradle ├── README.md ├── gradlew.bat └── gradlew /safs-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /safs-android-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /safs-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':safs-android-app', ':safs-core', ':safs-android' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henrik-lindqvist/safs/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Apr 11 23:12:57 CEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /safs-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/OpenOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public interface OpenOption {} 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/CopyOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public interface CopyOption { 20 | } 21 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/PathMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public interface PathMatcher { 20 | public boolean matches (Path path); 21 | } 22 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/GroupPrincipal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | public interface GroupPrincipal extends UserPrincipal { 20 | } 21 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/AttributeView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | public interface AttributeView { 20 | public String name (); 21 | } 22 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/FileAttributeView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | public interface FileAttributeView extends AttributeView { 20 | } 21 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileVisitOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public enum FileVisitOption { 20 | /** Follow symbolic links. */ 21 | FOLLOW_LINKS, 22 | } 23 | -------------------------------------------------------------------------------- /safs-android-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileVisitResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public enum FileVisitResult { 20 | CONTINUE, 21 | TERMINATE, 22 | SKIP_SUBTREE, 23 | SKIP_SIBLINGS, 24 | } -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/FileType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | public enum FileType { 20 | DIRECTORY, 21 | REGULAR_FILE, 22 | SYMBOLIC_LINK, 23 | OTHER, 24 | } 25 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/ClosedFileSystemException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class ClosedFileSystemException extends IllegalStateException { 21 | } 22 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/ClosedWatchServiceException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class ClosedWatchServiceException extends IllegalStateException {} 21 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/FileAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | public interface FileAttribute { 20 | public String name (); 21 | public T value (); 22 | } 23 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/UserPrincipal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.security.Principal; 20 | 21 | public interface UserPrincipal extends Principal { 22 | } 23 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/LinkOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public enum LinkOption implements OpenOption, CopyOption { 20 | /** Do not follow symbolic links. */ 21 | NOFOLLOW_LINKS, 22 | } 23 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/ReadOnlyFileSystemException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class ReadOnlyFileSystemException extends UnsupportedOperationException { 21 | } 22 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/StandardCopyOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public enum StandardCopyOption implements CopyOption { 20 | REPLACE_EXISTING, 21 | COPY_ATTRIBUTES, 22 | ATOMIC_MOVE, 23 | } 24 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/DefaultFileSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.Path; 20 | 21 | public interface DefaultFileSystem { 22 | public Path getCacheDirectory (); 23 | } 24 | -------------------------------------------------------------------------------- /safs-core/README.md: -------------------------------------------------------------------------------- 1 | safs-core 2 | ========= 3 | 4 | [![Download](https://api.bintray.com/packages/hlindqvi/safs/safs/images/download.svg)](https://bintray.com/hlindqvi/safs/safs/_latestVersion) 5 | 6 | The core package use the [java.io.File](https://docs.oracle.com/javase/6/docs/api/java/io/File.html) to access the file-system. 7 | Only support OS'es using `/` as path separator, e.g. Unix/Linux. 8 | 9 | ### Getting started 10 | Add to Gradle project `dependencies`: 11 | ```groovy 12 | implementation 'com.llamalab.safs:safs-core:0.2.0' 13 | ``` 14 | 15 | ### Usage 16 | Same as [java.nio.file](https://docs.oracle.com/javase/7/docs/api/java/nio/file/package-summary.html) packages except located in `com.llamalab.safs`, 17 | and instead of [java.nio.channels.SeekableByteChannel](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/SeekableByteChannel.html) 18 | use `com.llamalab.safs.channels.SeekableByteChannel`. 19 | -------------------------------------------------------------------------------- /safs-android-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/NoSuchFileException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class NoSuchFileException extends FileSystemException { 21 | 22 | public NoSuchFileException (String file) { 23 | super(file); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bintray.gradle: -------------------------------------------------------------------------------- 1 | 2 | Properties localProperties = new Properties() 3 | localProperties.load(project.rootProject.file('local.properties').newDataInputStream()) 4 | 5 | bintray { 6 | user = localProperties.getProperty('bintray.user') 7 | key = localProperties.getProperty('bintray.key') 8 | publications = [ 'maven' ] 9 | dryRun = false 10 | publish = false 11 | override = false 12 | pkg { 13 | repo = 'safs' 14 | name = 'safs' 15 | licenses = [ 'Apache-2.0' ] 16 | vcsUrl = 'https://github.com/henrik-lindqvist/safs.git' 17 | issueTrackerUrl = 'https://github.com/henrik-lindqvist/safs/issues' 18 | websiteUrl = 'https://github.com/henrik-lindqvist/safs' 19 | version { 20 | name = rootProject.ext.releaseVersion 21 | //vcsTag = rootProject.ext.releaseVersion 22 | gpg { 23 | passphrase = localProperties.getProperty('bintray.gpg.passphrase') 24 | sign = true 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/NotDirectoryException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class NotDirectoryException extends FileSystemException { 21 | 22 | public NotDirectoryException (String file) { 23 | super(file); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileSystemLoopException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class FileSystemLoopException extends FileSystemException { 21 | 22 | public FileSystemLoopException (String file) { 23 | super(file); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/DirectoryNotEmptyException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class DirectoryNotEmptyException extends FileSystemException { 21 | 22 | public DirectoryNotEmptyException (String file) { 23 | super(file); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class FileAlreadyExistsException extends FileSystemException { 21 | 22 | public FileAlreadyExistsException (String file) { 23 | super(file); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'com.jfrog.bintray' 4 | apply from: '../bintray.gradle' 5 | 6 | // Support Java 6 which lack java.nio.file: 7 | //noinspection GroovyUnusedAssignment 8 | sourceCompatibility = 1.6 9 | //noinspection GroovyUnusedAssignment 10 | targetCompatibility = 1.6 11 | 12 | dependencies { 13 | implementation fileTree(dir: 'libs', include: ['*.jar']) 14 | testImplementation 'junit:junit:4.12' 15 | } 16 | 17 | publishing { 18 | publications { 19 | maven(MavenPublication) { 20 | groupId = 'com.llamalab.safs' 21 | artifactId = 'safs-core' 22 | version = rootProject.ext.releaseVersion 23 | from components.java 24 | artifact sourcesJar // required by jcentral 25 | } 26 | } 27 | } 28 | 29 | task sourcesJar (type: Jar, dependsOn: classes) { 30 | from sourceSets.main.allSource 31 | classifier = 'sources' 32 | } 33 | 34 | artifacts { 35 | archives sourcesJar 36 | } 37 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/WatchKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.util.List; 20 | 21 | public interface WatchKey { 22 | public Watchable watchable (); 23 | public boolean isValid (); 24 | public boolean reset (); 25 | public void cancel (); 26 | public List> pollEvents (); 27 | } 28 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/DirectoryStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | 22 | public interface DirectoryStream extends Iterable, Closeable { 23 | public interface Filter { 24 | public boolean accept (T entry) throws IOException; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/PosixFilePermission.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | public enum PosixFilePermission { 20 | OWNER_READ, 21 | OWNER_WRITE, 22 | OWNER_EXECUTE, 23 | GROUP_READ, 24 | GROUP_WRITE, 25 | GROUP_EXECUTE, 26 | OTHERS_READ, 27 | OTHERS_WRITE, 28 | OTHERS_EXECUTE 29 | } 30 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/StandardOpenOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public enum StandardOpenOption implements OpenOption { 20 | READ, 21 | WRITE, 22 | APPEND, 23 | TRUNCATE_EXISTING, 24 | CREATE, 25 | CREATE_NEW, 26 | /** Unsupported. */ 27 | DELETE_ON_CLOSE, 28 | SPARSE, 29 | SYNC, 30 | DSYNC, 31 | } 32 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/PosixFileAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.util.Set; 20 | 21 | public interface PosixFileAttributes extends BasicFileAttributes { 22 | public GroupPrincipal group (); 23 | public UserPrincipal owner (); 24 | public Set permissions (); 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/ProviderMismatchException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class ProviderMismatchException extends IllegalArgumentException { 21 | 22 | public ProviderMismatchException () {} 23 | 24 | public ProviderMismatchException (String message) { 25 | super(message); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/ProviderNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class ProviderNotFoundException extends RuntimeException { 21 | 22 | public ProviderNotFoundException () {} 23 | 24 | public ProviderNotFoundException (String message) { 25 | super(message); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/FileOwnerAttributeView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.io.IOException; 20 | 21 | public interface FileOwnerAttributeView extends FileAttributeView { 22 | public UserPrincipal getOwner () throws IOException; 23 | public void setOwner (UserPrincipal owner) throws IOException; 24 | } 25 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/AtomicMoveNotSupportedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class AtomicMoveNotSupportedException extends FileSystemException { 21 | 22 | public AtomicMoveNotSupportedException (String source, String target, String reason) { 23 | super(source, target, reason); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileSystemNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class FileSystemNotFoundException extends RuntimeException { 21 | 22 | public FileSystemNotFoundException () {} 23 | 24 | public FileSystemNotFoundException (String message) { 25 | super(message); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/FileStoreNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import com.llamalab.safs.FileSystemException; 20 | 21 | @SuppressWarnings("serial") 22 | public class FileStoreNotFoundException extends FileSystemException { 23 | 24 | public FileStoreNotFoundException (String volume) { 25 | super(volume); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileSystemAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class FileSystemAlreadyExistsException extends RuntimeException { 21 | 22 | public FileSystemAlreadyExistsException () {} 23 | 24 | public FileSystemAlreadyExistsException (String message) { 25 | super(message); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/Watchable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.IOException; 20 | 21 | public interface Watchable { 22 | public WatchKey register (WatchService service, WatchEvent.Kind[] kinds, WatchEvent.Modifier... modifiers) throws IOException; 23 | public WatchKey register (WatchService service, WatchEvent.Kind... kinds) throws IOException; 24 | } 25 | -------------------------------------------------------------------------------- /safs-android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class com.llamalab.safs.spi.FileSystemProvider { *; } 24 | -keep class * extends com.llamalab.safs.spi.FileSystemProvider { *; } 25 | -keep class * extends com.llamalab.safs.spi.FileTypeDetector { *; } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/WatchService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.Closeable; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | public interface WatchService extends Closeable { 23 | public WatchKey poll (); 24 | public WatchKey poll (long timeout, TimeUnit unit) throws InterruptedException; 25 | public WatchKey take () throws InterruptedException; 26 | } 27 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/NotLinkException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class NotLinkException extends FileSystemException { 21 | 22 | public NotLinkException (String file) { 23 | super(file); 24 | } 25 | 26 | public NotLinkException (String file, String otherFile, String reason) { 27 | super(file, otherFile, reason); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/BasicFileAttributeView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.io.IOException; 20 | 21 | public interface BasicFileAttributeView extends FileAttributeView { 22 | public BasicFileAttributes readAttributes () throws IOException; 23 | public void setTimes (FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException; 24 | } 25 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/AccessDeniedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class AccessDeniedException extends FileSystemException { 21 | 22 | public AccessDeniedException (String file) { 23 | super(file); 24 | } 25 | 26 | public AccessDeniedException (String file, String otherFile, String reason) { 27 | super(file, otherFile, reason); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/WatchEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | public interface WatchEvent { 20 | 21 | public static interface Kind { 22 | public String name (); 23 | public Class type (); 24 | } 25 | 26 | public static interface Modifier { 27 | public String name (); 28 | } 29 | 30 | public Kind kind (); 31 | public T context (); 32 | public int count (); 33 | } 34 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidOpenOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import com.llamalab.safs.OpenOption; 20 | 21 | public enum AndroidOpenOption implements OpenOption { 22 | /** 23 | * Bypass document check for a minor performance improvement. Use if the file is known to be 24 | * located in internal (private app) storage or primary external storage. 25 | */ 26 | NODOCUMENT, 27 | } 28 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/PosixFileAttributeView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.io.IOException; 20 | import java.util.Set; 21 | 22 | public interface PosixFileAttributeView extends BasicFileAttributeView, FileOwnerAttributeView { 23 | public void setGroup (GroupPrincipal group) throws IOException; 24 | public void setPermissions (Set perms) throws IOException; 25 | } 26 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/UserPrincipalNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.io.IOException; 20 | 21 | public class UserPrincipalNotFoundException extends IOException { 22 | 23 | private final String name; 24 | 25 | public UserPrincipalNotFoundException (String name) { 26 | this.name = name; 27 | } 28 | 29 | public String getName (){ 30 | return name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/spi/FileTypeDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.spi; 18 | 19 | import com.llamalab.safs.Path; 20 | 21 | import java.io.IOException; 22 | 23 | /** 24 | * System.setProperty("content.types.user.table", "/path/to/property/file"​); 25 | */ 26 | public abstract class FileTypeDetector { 27 | 28 | protected FileTypeDetector () {} 29 | 30 | public abstract String probeContentType (Path path) throws IOException; 31 | } 32 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/UserPrincipalLookupService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import java.io.IOException; 20 | 21 | public abstract class UserPrincipalLookupService { 22 | 23 | protected UserPrincipalLookupService () {} 24 | 25 | public abstract UserPrincipal lookupPrincipalByName (String name) throws IOException; 26 | public abstract GroupPrincipal lookupPrincipalByGroupName (String group) throws IOException; 27 | } 28 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/NotDocumentUriException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.net.Uri; 20 | 21 | @SuppressWarnings("serial") 22 | public class NotDocumentUriException extends IllegalArgumentException { 23 | 24 | private final Uri uri; 25 | 26 | public NotDocumentUriException (Uri uri) { 27 | super(uri.toString()); 28 | this.uri = uri; 29 | } 30 | 31 | public Uri getUri () { 32 | return uri; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/BasicFileAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | public interface BasicFileAttributes { 20 | public Object fileKey (); 21 | public boolean isDirectory (); 22 | public boolean isOther (); 23 | public boolean isRegularFile (); 24 | public boolean isSymbolicLink (); 25 | public long size (); 26 | public FileTime creationTime (); 27 | public FileTime lastModifiedTime (); 28 | public FileTime lastAccessTime (); 29 | } 30 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidWatchEventKind.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import com.llamalab.safs.internal.WatchEventKind; 20 | 21 | final class AndroidWatchEventKind extends WatchEventKind { 22 | 23 | private final int event; 24 | 25 | public AndroidWatchEventKind (String name, Class type, int event) { 26 | super(name, type); 27 | this.event = event; 28 | } 29 | 30 | public final int event () { 31 | return event; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/channels/SeekableByteChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.channels; 18 | 19 | import java.io.IOException; 20 | import java.nio.channels.ByteChannel; 21 | 22 | public interface SeekableByteChannel extends ByteChannel { 23 | public long position () throws IOException; 24 | public SeekableByteChannel position (long newPosition) throws IOException; 25 | public long size () throws IOException; 26 | public SeekableByteChannel truncate (long size) throws IOException; 27 | } 28 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.IOException; 20 | 21 | public abstract class FileStore { 22 | protected FileStore () {} 23 | public abstract String name (); 24 | public abstract String type (); 25 | public abstract boolean isReadOnly (); 26 | public abstract long getTotalSpace () throws IOException; 27 | public abstract long getUsableSpace () throws IOException; 28 | public abstract long getUnallocatedSpace () throws IOException; 29 | } 30 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/DirectoryIteratorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.IOException; 20 | import java.util.ConcurrentModificationException; 21 | 22 | @SuppressWarnings("serial") 23 | public class DirectoryIteratorException extends ConcurrentModificationException { 24 | 25 | public DirectoryIteratorException (IOException cause) { 26 | initCause(cause); 27 | } 28 | 29 | @Override 30 | public final IOException getCause () { 31 | return (IOException)super.getCause(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.attributes.BasicFileAttributes; 20 | 21 | import java.io.IOException; 22 | 23 | public interface FileVisitor { 24 | public FileVisitResult preVisitDirectory (T dir, BasicFileAttributes attrs) throws IOException; 25 | public FileVisitResult postVisitDirectory (T dir, IOException e) throws IOException; 26 | public FileVisitResult visitFile (T file, BasicFileAttributes attrs) throws IOException; 27 | public FileVisitResult visitFileFailed (T file, IOException e) throws IOException; 28 | } 29 | -------------------------------------------------------------------------------- /safs-android-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId 'com.llamalab.safs.android.app' 7 | minSdkVersion 14 8 | //noinspection OldTargetApi 9 | targetSdkVersion 26 10 | versionCode 2 11 | versionName rootProject.ext.releaseVersion 12 | 13 | buildConfigField 'int', 'TARGET_SDK_INT', targetSdkVersion.apiLevel.toString() 14 | 15 | //testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' 16 | } 17 | compileOptions { 18 | sourceCompatibility JavaVersion.VERSION_1_7 19 | targetCompatibility JavaVersion.VERSION_1_7 20 | } 21 | buildTypes { 22 | release { 23 | //minifyEnabled false 24 | //proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | implementation project(':safs-android') 32 | implementation 'com.android.support:support-v13:27.1.1' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 35 | //androidTestImplementation 'com.android.support.test:rules:1.0.2' 36 | //androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 37 | } 38 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/WatchEventKind.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.WatchEvent; 20 | 21 | public class WatchEventKind implements WatchEvent.Kind { 22 | 23 | private final String name; 24 | private final Class type; 25 | 26 | public WatchEventKind (String name, Class type) { 27 | this.name = name; 28 | this.type = type; 29 | } 30 | 31 | @Override 32 | public final String name () { 33 | return name; 34 | } 35 | 36 | @Override 37 | public final Class type () { 38 | return type; 39 | } 40 | 41 | @Override 42 | public final String toString () { 43 | return name; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/StandardWatchEventKinds.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.internal.WatchEventKind; 20 | 21 | public final class StandardWatchEventKinds { 22 | 23 | public static final WatchEvent.Kind OVERFLOW = new WatchEventKind("OVERFLOW", Object.class); 24 | public static final WatchEvent.Kind ENTRY_CREATE = new WatchEventKind("ENTRY_CREATE", Path.class); 25 | public static final WatchEvent.Kind ENTRY_DELETE = new WatchEventKind("ENTRY_DELETE", Path.class); 26 | public static final WatchEvent.Kind ENTRY_MODIFY = new WatchEventKind("ENTRY_MODIFY", Path.class); 27 | 28 | private StandardWatchEventKinds() {} 29 | 30 | } 31 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidPath.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import com.llamalab.safs.ProviderMismatchException; 20 | import com.llamalab.safs.WatchEvent; 21 | import com.llamalab.safs.WatchKey; 22 | import com.llamalab.safs.WatchService; 23 | import com.llamalab.safs.unix.UnixPath; 24 | 25 | import java.io.IOException; 26 | 27 | final class AndroidPath extends UnixPath { 28 | 29 | AndroidPath (AndroidFileSystem fs, String path) { 30 | super(fs, path); 31 | } 32 | 33 | @Override 34 | public WatchKey register (WatchService service, WatchEvent.Kind[] kinds, WatchEvent.Modifier... modifiers) throws IOException { 35 | if (!(service instanceof AndroidWatchService)) 36 | throw new ProviderMismatchException(); 37 | return ((AndroidWatchService)service).register(this, kinds, modifiers); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/unix/AbstractUnixFileSystemProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.unix; 18 | 19 | import com.llamalab.safs.Path; 20 | import com.llamalab.safs.internal.AbstractFileSystemProvider; 21 | import com.llamalab.safs.internal.Utils; 22 | 23 | import java.net.URI; 24 | 25 | public abstract class AbstractUnixFileSystemProvider extends AbstractFileSystemProvider { 26 | 27 | @Override 28 | protected Class getPathType () { 29 | return UnixPath.class; 30 | } 31 | 32 | @Override 33 | public Path getPath (URI uri) { 34 | if (!uri.isAbsolute() || uri.isOpaque() || uri.getAuthority() != null || uri.getFragment() != null || uri.getQuery() != null) 35 | throw new IllegalArgumentException(); 36 | return new UnixPath((AbstractUnixFileSystem)getFileSystem(uri), UnixPath.sanitize(uri.getPath(), Utils.EMPTY_STRING_ARRAY)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/SimpleFileVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.attributes.BasicFileAttributes; 20 | 21 | import java.io.IOException; 22 | 23 | public class SimpleFileVisitor implements FileVisitor { 24 | 25 | @Override 26 | public FileVisitResult preVisitDirectory (T dir, BasicFileAttributes attrs) throws IOException { 27 | return FileVisitResult.CONTINUE; 28 | } 29 | 30 | @Override 31 | public FileVisitResult postVisitDirectory (T dir, IOException e) throws IOException { 32 | if (e != null) 33 | throw e; 34 | return FileVisitResult.CONTINUE; 35 | } 36 | 37 | @Override 38 | public FileVisitResult visitFile (T file, BasicFileAttributes attrs) throws IOException { 39 | return FileVisitResult.CONTINUE; 40 | } 41 | 42 | @Override 43 | public FileVisitResult visitFileFailed (T file, IOException e) throws IOException { 44 | throw e; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /safs-android-app/README.md: -------------------------------------------------------------------------------- 1 | safs-android-app 2 | ================ 3 | 4 | A package for Android integration testing of [safs-android](../safs-android). 5 | 6 | Run [AndroidFileSystemTests.java](src/androidTest/java/com/llamalab/safs/android/app/AndroidFileSystemTests.java). 7 | 8 | 9 | ### Known limitations 10 | There's sadly some Android issues which can't be worked around: 11 | * [SAF](http://www.androiddocs.com/guide/topics/providers/document-provider.html) trim prefix/suffix whitespace and "filter" certain characters in filenames, 12 | an `FileAlreadyExistsException` is thrown if that occur when creating a file. 13 | * Volumes/mounts not using `sdcardfs` are unable to change file last-modified time, so don't rely on 14 | [StandardCopyOption.COPY_ATTRIBUTES](https://docs.oracle.com/javase/7/docs/api/java/nio/file/StandardCopyOption.html#COPY_ATTRIBUTES), 15 | [Files.setLastModifiedTime()](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#setLastModifiedTime(java.nio.file.Path,%20java.nio.file.attribute.FileTime)) 16 | nor [Files.html.setAttribute()](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#setAttribute(java.nio.file.Path,%20java.lang.String,%20java.lang.Object,%20java.nio.file.LinkOption...)) 17 | See [report](https://code.google.com/p/android/issues/detail?id=18624). 18 | * [WatchService](https://docs.oracle.com/javase/7/docs/api/java/nio/file/WatchService.html), 19 | which use [FileObserver](https://developer.android.com/reference/android/os/FileObserver), doesn't seem to work on 20 | _secondary external storage_ when accessed through 21 | [SAF](http://www.androiddocs.com/guide/topics/providers/document-provider.html). 22 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/PrimaryFileStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.os.Environment; 20 | 21 | import com.llamalab.safs.Path; 22 | 23 | final class PrimaryFileStore extends AndroidFileStore { 24 | 25 | private final Path path; 26 | 27 | public PrimaryFileStore (Path path) { 28 | this.path = path; 29 | } 30 | 31 | @Override 32 | public String name () { 33 | return PRIMARY_NAME; 34 | } 35 | 36 | @Override 37 | public Path path () { 38 | return path; 39 | } 40 | 41 | @Override 42 | public boolean isPrimary () { 43 | return true; 44 | } 45 | 46 | @Override 47 | public boolean isEmulated () { 48 | return Environment.isExternalStorageEmulated(); 49 | } 50 | 51 | @Override 52 | public boolean isRemovable () { 53 | return Environment.isExternalStorageRemovable(); 54 | } 55 | 56 | @Override 57 | public String state () { 58 | return Environment.getExternalStorageState(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.attributes.UserPrincipalLookupService; 20 | import com.llamalab.safs.spi.FileSystemProvider; 21 | 22 | import java.io.Closeable; 23 | import java.io.IOException; 24 | import java.util.Set; 25 | 26 | public abstract class FileSystem implements Closeable { 27 | public abstract FileSystemProvider provider (); 28 | public abstract boolean isOpen (); 29 | public abstract boolean isReadOnly (); 30 | public abstract Set supportedFileAttributeViews(); 31 | public abstract String getSeparator (); 32 | public abstract Path getPath (String first, String... more); 33 | public abstract PathMatcher getPathMatcher (String syntaxAndPattern); 34 | public abstract Iterable getFileStores (); 35 | public abstract Iterable getRootDirectories (); 36 | public abstract UserPrincipalLookupService getUserPrincipalLookupService (); 37 | public abstract WatchService newWatchService () throws IOException; 38 | } 39 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/java/DefaultJavaFileSystemProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.java; 18 | 19 | import com.llamalab.safs.FileSystem; 20 | import com.llamalab.safs.FileSystemAlreadyExistsException; 21 | import com.llamalab.safs.Path; 22 | 23 | import java.io.IOException; 24 | import java.net.URI; 25 | import java.util.Map; 26 | 27 | public final class DefaultJavaFileSystemProvider extends JavaFileSystemProvider { 28 | 29 | private final FileSystem fileSystem = new JavaFileSystem(this); 30 | 31 | @Override 32 | public FileSystem getFileSystem (URI uri) { 33 | checkUri(uri); 34 | return fileSystem; 35 | } 36 | 37 | @Override 38 | public FileSystem newFileSystem (URI uri, Map env) throws IOException { 39 | checkUri(uri); 40 | throw new FileSystemAlreadyExistsException(); 41 | } 42 | 43 | @Override 44 | public FileSystem newFileSystem (Path path, Map env) throws IOException { 45 | checkPath(path); 46 | throw new FileSystemAlreadyExistsException(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/Paths.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.spi.FileSystemProvider; 20 | 21 | import java.io.File; 22 | import java.net.URI; 23 | 24 | public final class Paths { 25 | 26 | private Paths () {} 27 | 28 | public static Path get (String first, String... more) { 29 | return FileSystems.getDefault().getPath(first, more); 30 | } 31 | 32 | public static Path get (URI uri) { 33 | final String scheme = uri.getScheme(); 34 | if (scheme == null) 35 | throw new IllegalArgumentException("No scheme"); 36 | for (final FileSystemProvider provider: FileSystemProvider.installedProviders()) { 37 | if (scheme.equalsIgnoreCase(provider.getScheme())) 38 | return provider.getPath(uri); 39 | } 40 | throw new FileSystemNotFoundException(scheme); 41 | } 42 | 43 | /** 44 | * Non-standard. Since we can't add toPath to {@link java.io.File}. 45 | */ 46 | public static Path get (File file) { 47 | return get(file.toString()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/PartialBasicFileAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.attributes.BasicFileAttributes; 20 | 21 | public abstract class PartialBasicFileAttributes implements BasicFileAttributes { 22 | 23 | private final FileType type; 24 | private final long size; 25 | 26 | protected PartialBasicFileAttributes (FileType type, long size) { 27 | this.type = type; 28 | this.size = size; 29 | } 30 | 31 | @Override 32 | public final boolean isDirectory () { 33 | return FileType.DIRECTORY == type; 34 | } 35 | 36 | @Override 37 | public final boolean isRegularFile () { 38 | return FileType.REGULAR_FILE == type; 39 | } 40 | 41 | @Override 42 | public final boolean isSymbolicLink () { 43 | return FileType.SYMBOLIC_LINK == type; 44 | } 45 | 46 | @Override 47 | public final boolean isOther () { 48 | return FileType.OTHER == type; 49 | } 50 | 51 | @Override 52 | public final long size () { 53 | return size; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/InvalidPathException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | @SuppressWarnings("serial") 20 | public class InvalidPathException extends IllegalArgumentException { 21 | 22 | private final String input; 23 | private final int index; 24 | 25 | public InvalidPathException (String input, String reason) { 26 | this(input, reason, -1); 27 | } 28 | 29 | public InvalidPathException (String input, String reason, int index) { 30 | super(reason); 31 | this.input = input; 32 | this.index = index; 33 | } 34 | 35 | public String getInput () { 36 | return input; 37 | } 38 | 39 | public String getReason () { 40 | return super.getMessage(); 41 | } 42 | 43 | public int getIndex () { 44 | return index; 45 | } 46 | 47 | @Override 48 | public String getMessage () { 49 | final StringBuilder sb = new StringBuilder().append(getReason()); 50 | if (index >= 0) 51 | sb.append(" at index ").append(index); 52 | return sb.append(": ").append(input).toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/BasicFileAttributeValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.attributes.FileAttribute; 20 | 21 | public final class BasicFileAttributeValue implements FileAttribute { 22 | 23 | private final BasicFileAttribute type; 24 | private final Object value; 25 | 26 | public BasicFileAttributeValue (BasicFileAttribute type, Object value) { 27 | if (type == null || value == null) 28 | throw new NullPointerException(); 29 | this.type = type; 30 | this.value = value; 31 | } 32 | 33 | @Override 34 | public String name () { 35 | return BasicFileAttribute.VIEW_NAME + ":" + type; 36 | } 37 | 38 | @Override 39 | public Object value () { 40 | return value; 41 | } 42 | 43 | public BasicFileAttribute type () { 44 | return type; 45 | } 46 | 47 | @Override 48 | public int hashCode () { 49 | return type.hashCode(); 50 | } 51 | 52 | @Override 53 | public boolean equals (Object other) { 54 | return other instanceof BasicFileAttributeValue 55 | && type == ((BasicFileAttributeValue)other).type; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/Path.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.URI; 22 | 23 | public interface Path extends Comparable, Iterable, Watchable { 24 | public FileSystem getFileSystem (); 25 | 26 | public boolean isAbsolute (); 27 | public Path getRoot (); 28 | public Path getParent (); 29 | public Path getFileName (); 30 | public int getNameCount (); 31 | public Path getName (int index); 32 | public Path subpath (int beginIndex, int endIndex); 33 | public boolean startsWith (Path other); 34 | public boolean startsWith (String other); 35 | public boolean endsWith (Path other); 36 | public boolean endsWith (String other); 37 | 38 | public Path normalize (); 39 | public Path relativize (Path other); 40 | public Path resolve (Path other); 41 | public Path resolve (String other); 42 | public Path resolveSibling (Path other); 43 | public Path resolveSibling (String other); 44 | 45 | public Path toAbsolutePath (); 46 | public Path toRealPath (LinkOption... options) throws IOException; 47 | public File toFile (); 48 | public URI toUri (); 49 | } 50 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /safs-android/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | // https://github.com/wupdigital/android-maven-publish 3 | id 'digital.wup.android-maven-publish' version '3.6.2' 4 | } 5 | apply plugin: 'com.android.library' 6 | apply plugin: 'com.jfrog.bintray' 7 | apply from: '../bintray.gradle' 8 | 9 | android { 10 | compileSdkVersion 27 11 | defaultConfig { 12 | minSdkVersion 14 13 | //noinspection OldTargetApi 14 | targetSdkVersion 26 15 | //versionCode 2 16 | //versionName rootProject.ext.releaseVersion 17 | 18 | //testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_7 22 | targetCompatibility JavaVersion.VERSION_1_7 23 | } 24 | buildTypes { 25 | release { 26 | //minifyEnabled false 27 | //proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | api project(':safs-core') // api for compile 35 | testImplementation 'junit:junit:4.12' 36 | //androidTestImplementation 'com.android.support.test:runner:1.0.2' 37 | //androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 38 | } 39 | 40 | publishing { 41 | publications { 42 | maven(MavenPublication) { 43 | groupId = 'com.llamalab.safs' 44 | artifactId = 'safs-android' 45 | version = rootProject.ext.releaseVersion //android.defaultConfig.versionName 46 | from components.android 47 | //artifact sourcesJar // required by jcentral, android-maven-publish already include it?! 48 | } 49 | } 50 | } 51 | 52 | task sourcesJar (type: Jar) { 53 | from android.sourceSets.main.java.srcDirs 54 | classifier = 'sources' 55 | } 56 | 57 | artifacts { 58 | archives sourcesJar 59 | } 60 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/CompleteBasicFileAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.attributes.FileTime; 20 | 21 | public class CompleteBasicFileAttributes extends PartialBasicFileAttributes { 22 | 23 | private final Object fileKey; 24 | private final FileTime creationTime; 25 | private final FileTime lastModifiedTime; 26 | private final FileTime lastAccessTime; 27 | 28 | public CompleteBasicFileAttributes (Object fileKey, FileType type, long size, FileTime creationTime, FileTime lastModifiedTime, FileTime lastAccessTime) { 29 | super(type, size); 30 | this.fileKey = fileKey; 31 | this.creationTime = creationTime; 32 | this.lastModifiedTime = lastModifiedTime; 33 | this.lastAccessTime = lastAccessTime; 34 | } 35 | 36 | @Override 37 | public final Object fileKey () { 38 | return fileKey; 39 | } 40 | 41 | @Override 42 | public final FileTime creationTime () { 43 | return creationTime; 44 | } 45 | 46 | @Override 47 | public final FileTime lastModifiedTime () { 48 | return lastModifiedTime; 49 | } 50 | 51 | @Override 52 | public final FileTime lastAccessTime () { 53 | return lastAccessTime; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileSystemException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import java.io.IOException; 20 | 21 | @SuppressWarnings("serial") 22 | public class FileSystemException extends IOException { 23 | 24 | private final String file; 25 | private final String otherFile; 26 | 27 | public FileSystemException (String file) { 28 | this(file, null, null); 29 | } 30 | 31 | public FileSystemException (String file, String otherFile, String reason) { 32 | super(reason); 33 | this.file = file; 34 | this.otherFile = otherFile; 35 | } 36 | 37 | public String getFile () { 38 | return file; 39 | } 40 | 41 | public String getOtherFile () { 42 | return otherFile; 43 | } 44 | 45 | public String getReason () { 46 | return super.getMessage(); 47 | } 48 | 49 | @Override 50 | public String getMessage() { 51 | if (file == null && otherFile == null) 52 | return getReason(); 53 | final StringBuilder sb = new StringBuilder(); 54 | if (file != null) 55 | sb.append(file); 56 | if (otherFile != null) 57 | sb.append(" -> ").append(otherFile); 58 | if (getReason() != null) 59 | sb.append(": ").append(getReason()); 60 | return sb.toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Storage Access File System (SAFS) 2 | ==== 3 | 4 | [![Download](https://api.bintray.com/packages/hlindqvi/safs/safs/images/download.svg)](https://bintray.com/hlindqvi/safs/safs/_latestVersion) 5 | 6 | A "backport" of the [java.nio.file](https://docs.oracle.com/javase/7/docs/api/java/nio/file/package-summary.html) packages, 7 | primarily intended for Android ([safs-android](#safs-android)), but its core package ([safs-core](#safs-core)) may work on any Java 6+ Unix/Linux JVM. 8 | 9 | Some parts are unsupported or incomplete, mostly related to symlinks and storage volumes. 10 | 11 | 12 | # safs-android 13 | Android didn't get `java.nio.file` support until Android 8, so it will probably be quite some time before apps 14 | supporting older Android can use it. When that time finally come it should be easy to switch over, simply changing `import`. 15 | 16 | The major feature of `safs-android` is its ability to seamlessly use the Android 5.1+ 17 | [Storage Access Framework (SAF)](http://www.androiddocs.com/guide/topics/providers/document-provider.html) 18 | when accessing _secondary external storage_ volumes, i.e. removable SD cards, once granted access by the user using an 19 | [ACTION_OPEN_DOCUMENT_TREE](https://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT_TREE) intent. 20 | 21 | 22 | #### Possible benefits 23 | * Use a mature file-system API, i.e. [java.nio.file.Files](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html). 24 | * A unified API for accessing files on both internal, primary and secondary storage, from Android 4 through 9. Q support is planned. 25 | * Makes it easier to "port" existing, or implement new, `FileSystemProvider`, e.g. for accessing zip files, FTP, Google Drive. 26 | * A layer for working around Android issues. 27 | 28 | 29 | For more information, see [safs-android](safs-android). 30 | 31 | 32 | # safs-core 33 | The core package uses the [java.io.File](https://docs.oracle.com/javase/6/docs/api/index.html?java/io/File.html) to access the file-system. 34 | Only support OS'es using `/` as path separator, e.g. Unix/Linux. 35 | 36 | For more information, see [safs-core](safs-core). 37 | 38 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/java/SeekableByteChannelWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.java; 18 | 19 | import com.llamalab.safs.channels.SeekableByteChannel; 20 | 21 | import java.io.IOException; 22 | import java.nio.ByteBuffer; 23 | import java.nio.channels.FileChannel; 24 | 25 | final class SeekableByteChannelWrapper implements SeekableByteChannel { 26 | 27 | private final FileChannel fc; 28 | private final boolean append; 29 | 30 | public SeekableByteChannelWrapper (FileChannel fc, boolean append) { 31 | this.fc = fc; 32 | this.append = append; 33 | } 34 | 35 | @Override 36 | public void close () throws IOException { 37 | fc.close(); 38 | } 39 | 40 | @Override 41 | public boolean isOpen () { 42 | return fc.isOpen(); 43 | } 44 | 45 | @Override 46 | public int read (ByteBuffer dst) throws IOException { 47 | return fc.read(dst); 48 | } 49 | 50 | @Override 51 | public int write (ByteBuffer src) throws IOException { 52 | if (append) 53 | fc.position(fc.size()); 54 | return fc.write(src); 55 | } 56 | 57 | @Override 58 | public long position () throws IOException { 59 | return fc.position(); 60 | } 61 | 62 | @Override 63 | public SeekableByteChannel position (long newPosition) throws IOException { 64 | fc.position(newPosition); 65 | return this; 66 | } 67 | 68 | @Override 69 | public long size () throws IOException { 70 | return fc.size(); 71 | } 72 | 73 | @Override 74 | public SeekableByteChannel truncate (long size) throws IOException { 75 | fc.truncate(size); 76 | return this; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/UserPrincipalFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.attributes.GroupPrincipal; 20 | import com.llamalab.safs.attributes.UserPrincipal; 21 | import com.llamalab.safs.attributes.UserPrincipalLookupService; 22 | 23 | import java.io.IOException; 24 | 25 | public class UserPrincipalFactory extends UserPrincipalLookupService { 26 | 27 | @Override 28 | public UserPrincipal lookupPrincipalByName (String name) throws IOException { 29 | return new User(name); 30 | } 31 | 32 | @Override 33 | public GroupPrincipal lookupPrincipalByGroupName (String group) throws IOException { 34 | return new Group(group); 35 | } 36 | 37 | protected static class User implements UserPrincipal { 38 | 39 | private final String name; 40 | 41 | public User (String name) { 42 | if (name == null) 43 | throw new NullPointerException(); 44 | this.name = name; 45 | } 46 | 47 | @Override 48 | public String getName () { 49 | return name; 50 | } 51 | 52 | @Override 53 | public String toString () { 54 | return super.toString()+"[name="+name+"]"; 55 | } 56 | 57 | @Override 58 | public boolean equals (Object other) { 59 | return other instanceof User && name.equals(((User)other).name); 60 | } 61 | 62 | @Override 63 | public int hashCode () { 64 | return name.hashCode(); 65 | } 66 | 67 | } 68 | 69 | protected static class Group extends User implements GroupPrincipal { 70 | 71 | public Group (String name) { 72 | super(name); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/PathDescender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import java.util.Iterator; 20 | 21 | public class PathDescender implements Iterator { 22 | 23 | public enum Event { 24 | DIRECTORY, 25 | FILE, 26 | MISSING_DIRECTORY, 27 | MISSING_FILE 28 | } 29 | 30 | private final Iterator segments; 31 | private SegmentEntry parent; 32 | private String segment; 33 | private int index; 34 | 35 | public PathDescender (T root, Iterator segments) { 36 | this.parent = root; 37 | this.segments = segments; 38 | } 39 | 40 | @Override 41 | public boolean hasNext () { 42 | return index >= 0 && segments.hasNext(); 43 | } 44 | 45 | @Override 46 | public Event next () { 47 | if (segment != null) 48 | parent = parent.children[index]; 49 | if ((index = parent.binarySearch(segment = segments.next())) >= 0) 50 | return segments.hasNext() ? Event.DIRECTORY : Event.FILE; 51 | else 52 | return segments.hasNext() ? Event.MISSING_DIRECTORY : Event.MISSING_FILE; 53 | } 54 | 55 | @Override 56 | public void remove () { 57 | parent.remove(index); 58 | index = -1; 59 | } 60 | 61 | public void set (T entry) { 62 | entry.segment = segment; 63 | index = parent.put(index, entry); 64 | } 65 | 66 | public String segment () { 67 | return segment; 68 | } 69 | 70 | @SuppressWarnings("unchecked") 71 | public T parent () { 72 | return (T)parent; 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | public T entry () { 77 | return (T)parent.children[index]; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/FileTime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import com.llamalab.safs.internal.Utils; 20 | 21 | import java.util.concurrent.TimeUnit; 22 | 23 | public final class FileTime implements Comparable { 24 | 25 | private static final FileTime ZERO = new FileTime(0); 26 | 27 | private final long value; 28 | 29 | private FileTime (long value) { 30 | this.value = value; 31 | } 32 | 33 | /** 34 | * YYYY-MM-DDThh:mm:ss[.s+]Z 35 | */ 36 | @Override 37 | public String toString () { 38 | return Utils.formatRfc3339(value); 39 | } 40 | 41 | @Override 42 | public int hashCode () { 43 | return (int)(value ^ (value >>> 32)); 44 | } 45 | 46 | @Override 47 | public boolean equals (Object obj) { 48 | if (this == obj) 49 | return true; 50 | if (!(obj instanceof FileTime)) 51 | return false; 52 | return value == ((FileTime)obj).value; 53 | } 54 | 55 | @Override 56 | public int compareTo (FileTime other) { 57 | final long lhs = this.value; 58 | final long rhs = other.value; 59 | //noinspection UseCompareMethod 60 | return (lhs < rhs) ? -1 : (lhs == rhs) ? 0 : 1; 61 | } 62 | 63 | public long to (TimeUnit unit) { 64 | return unit.convert(value, TimeUnit.MILLISECONDS); 65 | } 66 | 67 | public long toMillis () { 68 | return to(TimeUnit.MILLISECONDS); 69 | } 70 | 71 | public static FileTime from (long value, TimeUnit unit) { 72 | if (unit == null) 73 | throw new NullPointerException("unit"); 74 | return fromMillis(TimeUnit.MILLISECONDS.convert(value, unit)); 75 | } 76 | 77 | public static FileTime fromMillis (long value) { 78 | return (value != 0) ? new FileTime(value) : ZERO; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/AbstractDirectoryStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.DirectoryIteratorException; 20 | import com.llamalab.safs.DirectoryStream; 21 | 22 | import java.io.IOException; 23 | import java.util.Iterator; 24 | import java.util.NoSuchElementException; 25 | 26 | public abstract class AbstractDirectoryStream implements DirectoryStream, Iterator { 27 | 28 | private T next; 29 | private boolean started; 30 | private boolean closed; 31 | 32 | /** 33 | * Make sure to call super.close(); 34 | */ 35 | @Override 36 | public final void close () throws IOException { 37 | if (!closed) { 38 | closed = true; 39 | implCloseStream(); 40 | } 41 | } 42 | 43 | protected void implCloseStream () throws IOException {} 44 | 45 | @SuppressWarnings("NullableProblems") 46 | @Override 47 | public final Iterator iterator () { 48 | if (started) 49 | throw new IllegalStateException(); 50 | started = true; 51 | return this; 52 | } 53 | 54 | @Override 55 | public final boolean hasNext () { 56 | if (next != null) 57 | return true; 58 | if (closed) 59 | return false; 60 | try { 61 | return (next = advance()) != null; 62 | } 63 | catch (IOException e) { 64 | throw new DirectoryIteratorException(e); 65 | } 66 | } 67 | 68 | @Override 69 | public final T next () { 70 | if (closed || !hasNext()) 71 | throw new NoSuchElementException(); 72 | final T result = next; 73 | next = null; 74 | return result; 75 | } 76 | 77 | @Override 78 | public final void remove () { 79 | throw new UnsupportedOperationException(); 80 | } 81 | 82 | protected abstract T advance () throws IOException; 83 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/java/JavaFileSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.java; 18 | 19 | import com.llamalab.safs.LinkOption; 20 | import com.llamalab.safs.NoSuchFileException; 21 | import com.llamalab.safs.Path; 22 | import com.llamalab.safs.internal.DefaultFileSystem; 23 | import com.llamalab.safs.spi.FileSystemProvider; 24 | import com.llamalab.safs.unix.AbstractUnixFileSystem; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | 29 | /** 30 | * Only support {@link com.llamalab.safs.unix.UnixPath}. 31 | */ 32 | public class JavaFileSystem extends AbstractUnixFileSystem implements DefaultFileSystem { 33 | 34 | protected volatile Path cacheDirectory; 35 | protected volatile Path currentDirectory; 36 | 37 | public JavaFileSystem (FileSystemProvider provider) { 38 | super(provider); 39 | } 40 | 41 | @Override 42 | public final void close () throws IOException { 43 | throw new UnsupportedOperationException(); 44 | } 45 | 46 | @Override 47 | public final boolean isOpen () { 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean isReadOnly () { 53 | return false; 54 | } 55 | 56 | @Override 57 | public Path getCacheDirectory () { 58 | if (cacheDirectory == null) 59 | cacheDirectory = getPathSanitized(System.getProperty("java.io.tmpdir")); 60 | return cacheDirectory; 61 | } 62 | 63 | public final Path getCurrentDirectory () { 64 | if (currentDirectory == null) 65 | currentDirectory = getPathSanitized(System.getProperty("user.dir")); 66 | return currentDirectory; 67 | } 68 | 69 | @Override 70 | protected Path toRealPath (Path path, LinkOption... options) throws IOException { 71 | final File file = path.toFile(); 72 | if (!file.exists()) 73 | throw new NoSuchFileException(path.toString()); 74 | for (final LinkOption option : options) { 75 | if (LinkOption.NOFOLLOW_LINKS == option) 76 | return path.toAbsolutePath().normalize(); 77 | } 78 | return getPathSanitized(file.getCanonicalPath()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/BasicFileAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.attributes.BasicFileAttributes; 20 | import com.llamalab.safs.attributes.FileAttribute; 21 | 22 | public enum BasicFileAttribute { 23 | fileKey { 24 | @Override 25 | public Object valueOf (BasicFileAttributes attrs) { 26 | return attrs.fileKey(); 27 | } 28 | }, 29 | isDirectory { 30 | @Override 31 | public Object valueOf (BasicFileAttributes attrs) { 32 | return attrs.isDirectory(); 33 | } 34 | }, 35 | isRegularFile { 36 | @Override 37 | public Object valueOf (BasicFileAttributes attrs) { 38 | return attrs.isRegularFile(); 39 | } 40 | }, 41 | isSymbolicLink { 42 | @Override 43 | public Object valueOf (BasicFileAttributes attrs) { 44 | return attrs.isSymbolicLink(); 45 | } 46 | }, 47 | isOther { 48 | @Override 49 | public Object valueOf (BasicFileAttributes attrs) { 50 | return attrs.isOther(); 51 | } 52 | }, 53 | size { 54 | @Override 55 | public Object valueOf (BasicFileAttributes attrs) { 56 | return attrs.size(); 57 | } 58 | }, 59 | creationTime { 60 | @Override 61 | public Object valueOf (BasicFileAttributes attrs) { 62 | return attrs.creationTime(); 63 | } 64 | }, 65 | lastModifiedTime { 66 | @Override 67 | public Object valueOf (BasicFileAttributes attrs) { 68 | return attrs.lastModifiedTime(); 69 | } 70 | }, 71 | lastAccessTime { 72 | @Override 73 | public Object valueOf (BasicFileAttributes attrs) { 74 | return attrs.lastAccessTime(); 75 | } 76 | }; 77 | 78 | public static final String VIEW_NAME = "basic"; 79 | 80 | public abstract Object valueOf (BasicFileAttributes attrs); 81 | 82 | public FileAttribute newFileAttribute (Object value) { 83 | return new BasicFileAttributeValue(this, value); 84 | } 85 | 86 | public static Iterable parse (String attributes) { 87 | return new AttributeParser(BasicFileAttribute.class, attributes, VIEW_NAME, true); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/FileSystems.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.java.DefaultJavaFileSystemProvider; 20 | import com.llamalab.safs.spi.FileSystemProvider; 21 | 22 | import java.io.IOException; 23 | import java.net.URI; 24 | import java.util.Map; 25 | 26 | public final class FileSystems { 27 | 28 | private FileSystems () {} 29 | 30 | public static FileSystem getDefault () { 31 | return DefaultFileSystemHolder.fileSystem; 32 | } 33 | 34 | public static FileSystem getFileSystem (URI uri) { 35 | for (final FileSystemProvider provider : FileSystemProvider.installedProviders()) { 36 | if (provider.getScheme().equalsIgnoreCase(uri.getScheme())) 37 | return provider.getFileSystem(uri); 38 | } 39 | throw new ProviderNotFoundException(uri.getScheme()); 40 | } 41 | 42 | public static FileSystem newFileSystem (URI uri, Map env) throws IOException { 43 | for (final FileSystemProvider provider : FileSystemProvider.installedProviders()) { 44 | if (provider.getScheme().equalsIgnoreCase(uri.getScheme())) 45 | return provider.newFileSystem(uri, env); 46 | } 47 | throw new ProviderNotFoundException(uri.getScheme()); 48 | } 49 | 50 | 51 | private static final class DefaultFileSystemHolder { 52 | 53 | static final FileSystem fileSystem = loadDefaultProvider().getFileSystem(URI.create("file:///")); 54 | 55 | private static FileSystemProvider loadDefaultProvider () { 56 | FileSystemProvider provider = new DefaultJavaFileSystemProvider(); 57 | final String value = System.getProperty("com.llamalab.safs.spi.DefaultFileSystemProvider"); 58 | if (value != null) { 59 | try { 60 | for (final String className : value.split(",")) { 61 | provider = (FileSystemProvider)Class.forName(className) 62 | .getDeclaredConstructor(FileSystemProvider.class) 63 | .newInstance(provider); 64 | } 65 | } 66 | catch (Throwable t) { 67 | throw new Error(t); 68 | } 69 | } 70 | return provider; 71 | } 72 | 73 | } // class DefaultFileSystemHolder 74 | 75 | } 76 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/StatBasicFileAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.annotation.TargetApi; 20 | import android.os.Build; 21 | import android.system.OsConstants; 22 | import android.system.StructStat; 23 | 24 | import com.llamalab.safs.attributes.BasicFileAttributes; 25 | import com.llamalab.safs.attributes.FileTime; 26 | import com.llamalab.safs.internal.Utils; 27 | 28 | import java.util.concurrent.TimeUnit; 29 | 30 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 31 | final class StatBasicFileAttributes implements BasicFileAttributes { 32 | 33 | private final StructStat stat; 34 | private volatile FileTime lastModifiedTime; 35 | private volatile FileTime lastAccessTime; 36 | 37 | public StatBasicFileAttributes (StructStat stat) { 38 | this.stat = stat; 39 | } 40 | 41 | @Override 42 | public Object fileKey () { 43 | return stat.st_ino; 44 | } 45 | 46 | @Override 47 | public boolean isDirectory () { 48 | return OsConstants.S_ISDIR(stat.st_mode); 49 | } 50 | 51 | @Override 52 | public boolean isOther () { 53 | //return !isDirectory() && !isRegularFile() && !isSymbolicLink(); 54 | final int fmt = stat.st_mode & OsConstants.S_IFMT; 55 | return OsConstants.S_IFDIR != fmt 56 | && OsConstants.S_IFREG != fmt 57 | && OsConstants.S_IFLNK != fmt; 58 | } 59 | 60 | @Override 61 | public boolean isRegularFile () { 62 | return OsConstants.S_ISREG(stat.st_mode); 63 | } 64 | 65 | @Override 66 | public boolean isSymbolicLink () { 67 | return OsConstants.S_ISLNK(stat.st_mode); 68 | } 69 | 70 | @Override 71 | public long size () { 72 | return stat.st_size; 73 | } 74 | 75 | @Override 76 | public FileTime creationTime () { 77 | return Utils.ZERO_TIME; 78 | } 79 | 80 | @Override 81 | public FileTime lastModifiedTime () { 82 | if (lastModifiedTime == null) 83 | lastModifiedTime = FileTime.from(stat.st_mtime, TimeUnit.SECONDS); 84 | return lastModifiedTime; 85 | } 86 | 87 | @Override 88 | public FileTime lastAccessTime () { 89 | if (lastAccessTime == null) 90 | lastAccessTime = FileTime.from(stat.st_atime, TimeUnit.SECONDS); 91 | return lastAccessTime; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /safs-android-app/src/main/java/com/llamalab/safs/android/app/OpenDocumentTreeActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android.app; 18 | 19 | import android.Manifest; 20 | import android.annotation.SuppressLint; 21 | import android.annotation.TargetApi; 22 | import android.app.Activity; 23 | import android.content.Intent; 24 | import android.content.pm.PackageManager; 25 | import android.os.Build; 26 | import android.os.Bundle; 27 | import android.support.annotation.NonNull; 28 | 29 | public final class OpenDocumentTreeActivity extends Activity { 30 | 31 | private static final int REQUEST_CODE_REQUEST_PERMISSIONS = 0; 32 | private static final int REQUEST_CODE_OPEN_DOCUMENT = 1; 33 | 34 | public Intent resultIntent; 35 | 36 | @SuppressLint("NewApi") 37 | @Override 38 | protected void onCreate (Bundle state) { 39 | super.onCreate(state); 40 | //noinspection ConstantConditions 41 | if ( Build.VERSION_CODES.M <= BuildConfig.TARGET_SDK_INT 42 | && Build.VERSION_CODES.M <= Build.VERSION.SDK_INT) { 43 | requestPermissions(new String[] { 44 | Manifest.permission.READ_EXTERNAL_STORAGE, 45 | Manifest.permission.WRITE_EXTERNAL_STORAGE }, 46 | REQUEST_CODE_REQUEST_PERMISSIONS); 47 | } 48 | else if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) 49 | openDocumentTree(); 50 | } 51 | 52 | @TargetApi(Build.VERSION_CODES.M) 53 | @Override 54 | public void onRequestPermissionsResult (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 55 | switch (requestCode) { 56 | case REQUEST_CODE_REQUEST_PERMISSIONS: 57 | if (grantResults.length == 0) 58 | finish(); // cancelled 59 | else { 60 | for (final int grantResult : grantResults) { 61 | if (PackageManager.PERMISSION_GRANTED != grantResult) { 62 | finish(); 63 | return; 64 | } 65 | } 66 | openDocumentTree(); 67 | } 68 | break; 69 | default: 70 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 71 | } 72 | } 73 | 74 | @Override 75 | protected void onActivityResult (int requestCode, int resultCode, Intent resultIntent) { 76 | switch (requestCode) { 77 | case REQUEST_CODE_OPEN_DOCUMENT: 78 | if (RESULT_OK == resultCode) 79 | this.resultIntent = resultIntent; 80 | else 81 | finish(); 82 | break; 83 | default: 84 | super.onActivityResult(requestCode, resultCode, resultIntent); 85 | } 86 | } 87 | 88 | @TargetApi(Build.VERSION_CODES.M) 89 | private void openDocumentTree () { 90 | startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQUEST_CODE_OPEN_DOCUMENT); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/AttributeParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import java.util.EnumSet; 20 | import java.util.Iterator; 21 | 22 | final class AttributeParser> implements Iterable { 23 | 24 | private final Class enumType; 25 | private final String attributes; 26 | private final String viewName; 27 | private final boolean isDefault; 28 | 29 | public AttributeParser (Class enumType, String attributes, String viewName, boolean isDefault) { 30 | this.enumType = enumType; 31 | this.attributes = attributes; 32 | this.viewName = viewName; 33 | this.isDefault = isDefault; 34 | } 35 | 36 | @SuppressWarnings("NullableProblems") 37 | @Override 38 | public Iterator iterator () { 39 | final int length = attributes.length(); 40 | int start = 0, index = 0; 41 | boolean prefixed = false; 42 | loop: while (index < length) { 43 | switch (attributes.charAt(index)) { 44 | case ':': 45 | if (prefixed) 46 | throw new IllegalArgumentException(); 47 | if (index != viewName.length() || !attributes.startsWith(viewName)) 48 | return Utils.emptyIterator(); 49 | start = ++index; 50 | prefixed = true; 51 | break; 52 | case '*': 53 | if (index != start || ++index != length) 54 | throw new IllegalArgumentException(); 55 | if (!prefixed && !isDefault) 56 | return Utils.emptyIterator(); 57 | return EnumSet.allOf(enumType).iterator(); 58 | case ',': 59 | break loop; 60 | default: 61 | ++index; 62 | } 63 | } 64 | if (!prefixed && !isDefault) 65 | return Utils.emptyIterator(); 66 | return new CommaSeparatedIterator(start, index); 67 | } 68 | 69 | private final class CommaSeparatedIterator implements Iterator { 70 | 71 | private int start; 72 | private int index; 73 | 74 | public CommaSeparatedIterator (int start, int index) { 75 | this.start = start; 76 | this.index = index; 77 | } 78 | 79 | @Override 80 | public boolean hasNext () { 81 | return start < index; 82 | } 83 | 84 | @Override 85 | public E next () { 86 | final E element = Enum.valueOf(enumType, attributes.substring(start, index)); 87 | index = attributes.indexOf(',', start = index + 1); 88 | if (index == -1) 89 | index = attributes.length(); 90 | return element; 91 | } 92 | 93 | @Override 94 | public void remove () { 95 | throw new UnsupportedOperationException(); 96 | } 97 | 98 | } // class CommaSeparatedIterator 99 | } 100 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/AbstractWatchService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.ClosedWatchServiceException; 20 | import com.llamalab.safs.WatchEvent; 21 | import com.llamalab.safs.WatchKey; 22 | import com.llamalab.safs.WatchService; 23 | import com.llamalab.safs.Watchable; 24 | 25 | import java.io.IOException; 26 | import java.util.List; 27 | import java.util.concurrent.LinkedBlockingDeque; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | 31 | public abstract class AbstractWatchService implements WatchService { 32 | 33 | private final WatchKey CLOSE_KEY = new WatchKey() { 34 | 35 | @Override 36 | public Watchable watchable () { 37 | return null; 38 | } 39 | 40 | @Override 41 | public boolean isValid () { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean reset () { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void cancel () { 52 | } 53 | 54 | @Override 55 | public List> pollEvents () { 56 | return null; 57 | } 58 | }; 59 | private final LinkedBlockingDeque pendingKeys = new LinkedBlockingDeque(); 60 | private final AtomicBoolean closed = new AtomicBoolean(); 61 | 62 | final void offer (WatchKey key) { 63 | pendingKeys.offer(key); 64 | } 65 | 66 | @Override 67 | public final WatchKey poll () { 68 | checkOpen(); 69 | return checkKey(pendingKeys.poll()); 70 | } 71 | 72 | @Override 73 | public final WatchKey poll (long timeout, TimeUnit unit) throws InterruptedException { 74 | checkOpen(); 75 | return checkKey(pendingKeys.poll(timeout, unit)); 76 | } 77 | 78 | @Override 79 | public final WatchKey take () throws InterruptedException { 80 | checkOpen(); 81 | return checkKey(pendingKeys.take()); 82 | } 83 | 84 | @Override 85 | public final void close () throws IOException { 86 | if (closed.compareAndSet(false, true)) { 87 | try { 88 | implCloseService(); 89 | } 90 | finally { 91 | pendingKeys.clear(); 92 | pendingKeys.offer(CLOSE_KEY); 93 | } 94 | } 95 | } 96 | 97 | protected abstract void implCloseService () throws IOException; 98 | 99 | public final boolean isOpen () { 100 | return !closed.get(); 101 | } 102 | 103 | private void checkOpen () { 104 | if (closed.get()) 105 | throw new ClosedWatchServiceException(); 106 | } 107 | 108 | private WatchKey checkKey (WatchKey key) { 109 | if (CLOSE_KEY == key) 110 | pendingKeys.offer(key); 111 | checkOpen(); 112 | return key; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/SearchSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import java.util.AbstractSet; 20 | import java.util.Arrays; 21 | import java.util.Collection; 22 | import java.util.Comparator; 23 | import java.util.Iterator; 24 | import java.util.NoSuchElementException; 25 | 26 | /** 27 | * Values must MUST implement Comparable. 28 | */ 29 | public class SearchSet extends AbstractSet implements Comparator { 30 | 31 | private final Object[] elements; 32 | 33 | @SafeVarargs 34 | public SearchSet (T... elements) { 35 | this.elements = elements; 36 | Arrays.sort(elements, this); 37 | } 38 | 39 | @SuppressWarnings("NullableProblems") 40 | @Override 41 | public Iterator iterator () { 42 | return new Iterator() { 43 | private int index; 44 | 45 | @Override 46 | public boolean hasNext () { 47 | return index < elements.length; 48 | } 49 | 50 | @SuppressWarnings("unchecked") 51 | @Override 52 | public T next () { 53 | if (index >= elements.length) 54 | throw new NoSuchElementException(); 55 | return (T)elements[index++]; 56 | } 57 | 58 | @Override 59 | public void remove () { 60 | throw new UnsupportedOperationException(); 61 | } 62 | }; 63 | } 64 | 65 | @Override 66 | public int size () { 67 | return elements.length; 68 | } 69 | 70 | @SuppressWarnings("NullableProblems") 71 | @Override 72 | public boolean addAll (Collection collection) { 73 | throw new UnsupportedOperationException(); 74 | } 75 | 76 | @Override 77 | public void clear () { 78 | throw new UnsupportedOperationException(); 79 | } 80 | 81 | @Override 82 | public boolean contains (Object object) { 83 | return Arrays.binarySearch(elements, object, this) >= 0; 84 | } 85 | 86 | @Override 87 | public boolean remove (Object object) { 88 | throw new UnsupportedOperationException(); 89 | } 90 | 91 | @SuppressWarnings("NullableProblems") 92 | @Override 93 | public boolean retainAll (Collection collection) { 94 | throw new UnsupportedOperationException(); 95 | } 96 | 97 | @Override 98 | public boolean removeAll (Collection collection) { 99 | throw new UnsupportedOperationException(); 100 | } 101 | 102 | @SuppressWarnings("unchecked") 103 | @Override 104 | public int compare (Object lhs, Object rhs) { 105 | if (lhs == rhs) 106 | return 0; 107 | if (lhs == null) 108 | return 1; 109 | if (rhs == null) 110 | return -1; 111 | final Class lc = lhs.getClass(); 112 | final Class rc = rhs.getClass(); 113 | return (lc != rc) ? lc.getName().compareTo(rc.getName()) : ((Comparable)lhs).compareTo(rhs); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/attributes/PosixFilePermissions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.attributes; 18 | 19 | import com.llamalab.safs.internal.SearchSet; 20 | 21 | import java.util.EnumSet; 22 | import java.util.Set; 23 | 24 | public final class PosixFilePermissions { 25 | 26 | private PosixFilePermissions () {} 27 | 28 | public static String toString (Set perms) { 29 | final StringBuilder sb = new StringBuilder(9); 30 | append(sb, 31 | perms.contains(PosixFilePermission.OWNER_READ), 32 | perms.contains(PosixFilePermission.OWNER_WRITE), 33 | perms.contains(PosixFilePermission.OWNER_EXECUTE)); 34 | append(sb, 35 | perms.contains(PosixFilePermission.GROUP_READ), 36 | perms.contains(PosixFilePermission.GROUP_WRITE), 37 | perms.contains(PosixFilePermission.GROUP_EXECUTE)); 38 | append(sb, 39 | perms.contains(PosixFilePermission.OTHERS_READ), 40 | perms.contains(PosixFilePermission.OTHERS_WRITE), 41 | perms.contains(PosixFilePermission.OTHERS_EXECUTE)); 42 | return sb.toString(); 43 | } 44 | 45 | private static void append (StringBuilder sb, boolean r, boolean w, boolean x) { 46 | sb.append(r ? 'r' : '-').append(w ? 'w' : '-').append(x ? 'x' : '-'); 47 | } 48 | 49 | public static Set fromString (String mode) { 50 | if (mode.length() != 9) 51 | throw new IllegalArgumentException(); 52 | final Set set = EnumSet.noneOf(PosixFilePermission.class); 53 | add(set, mode, 0, 'r', PosixFilePermission.OWNER_READ); 54 | add(set, mode, 1, 'w', PosixFilePermission.OWNER_WRITE); 55 | add(set, mode, 2, 'x', PosixFilePermission.OWNER_EXECUTE); 56 | 57 | add(set, mode, 3, 'r', PosixFilePermission.GROUP_READ); 58 | add(set, mode, 4, 'w', PosixFilePermission.GROUP_WRITE); 59 | add(set, mode, 5, 'x', PosixFilePermission.GROUP_EXECUTE); 60 | 61 | add(set, mode, 6, 'r', PosixFilePermission.OTHERS_READ); 62 | add(set, mode, 7, 'w', PosixFilePermission.OTHERS_WRITE); 63 | add(set, mode, 8, 'x', PosixFilePermission.OTHERS_EXECUTE); 64 | return set; 65 | } 66 | 67 | private static void add (Set set, String mode, int index, char flag, PosixFilePermission perm) { 68 | final char c = mode.charAt(index); 69 | if (flag == c) 70 | set.add(perm); 71 | else if ('-' != c) 72 | throw new IllegalArgumentException(); 73 | } 74 | 75 | public static FileAttribute> asFileAttribute (Set perms) { 76 | //noinspection ToArrayCallWithZeroLengthArrayArgument 77 | final Set value = new SearchSet(perms.toArray(new PosixFilePermission[perms.size()])); 78 | return new FileAttribute>() { 79 | 80 | @Override 81 | public String name() { 82 | return "posix:permissions"; 83 | } 84 | 85 | @Override 86 | public Set value () { 87 | return value; 88 | } 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/AbstractWatchKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.StandardWatchEventKinds; 20 | import com.llamalab.safs.WatchEvent; 21 | import com.llamalab.safs.WatchKey; 22 | import com.llamalab.safs.Watchable; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | public abstract class AbstractWatchKey implements WatchKey { 28 | 29 | private enum State { 30 | READY, 31 | SIGNALLED, 32 | } 33 | private final Object eventLock = new Object(); 34 | private final AbstractWatchService service; 35 | private final Watchable watchable; 36 | private final int overflowLimit; 37 | private List> events = new ArrayList>(); 38 | private State state = State.READY; 39 | 40 | public AbstractWatchKey (AbstractWatchService service, Watchable watchable, int overflowLimit) { 41 | this.service = service; 42 | this.watchable = watchable; 43 | this.overflowLimit = overflowLimit; 44 | } 45 | 46 | public final AbstractWatchService service () { 47 | return service; 48 | } 49 | 50 | @Override 51 | public final Watchable watchable () { 52 | return watchable; 53 | } 54 | 55 | @Override 56 | public boolean isValid () { 57 | return service.isOpen(); 58 | } 59 | 60 | @Override 61 | public final boolean reset () { 62 | synchronized (eventLock) { 63 | if (!isValid()) 64 | return false; 65 | if (State.SIGNALLED == state) { 66 | if (events.isEmpty()) 67 | state = State.READY; 68 | else 69 | service.offer(this); 70 | } 71 | return true; 72 | } 73 | } 74 | 75 | @Override 76 | public final List> pollEvents () { 77 | synchronized (eventLock) { 78 | final List> result = events; 79 | events = new ArrayList>(); 80 | return result; 81 | } 82 | } 83 | 84 | protected final void signalEvent (WatchEvent.Kind kind, T context) { 85 | synchronized (eventLock) { 86 | final int size = events.size(); 87 | if (size > 0) { 88 | final Event event = (Event)events.get(size - 1); 89 | if ( StandardWatchEventKinds.OVERFLOW == event.kind 90 | || (event.kind == kind && Utils.equals(event.context, context))) { 91 | ++event.count; 92 | return; 93 | } 94 | } 95 | if (size < overflowLimit) 96 | events.add(new Event(kind, context)); 97 | else 98 | events.add(new Event(StandardWatchEventKinds.OVERFLOW, null)); 99 | if (State.READY == state) { 100 | state = State.SIGNALLED; 101 | service.offer(this); 102 | } 103 | } 104 | } 105 | 106 | private static final class Event implements WatchEvent { 107 | 108 | private final WatchEvent.Kind kind; 109 | private final T context; 110 | private int count = 1; 111 | 112 | public Event (WatchEvent.Kind kind, T context) { 113 | this.kind = kind; 114 | this.context = context; 115 | } 116 | 117 | @Override 118 | public Kind kind () { 119 | return kind; 120 | } 121 | 122 | @Override 123 | public T context () { 124 | return context; 125 | } 126 | 127 | @Override 128 | public int count () { 129 | return count; 130 | } 131 | 132 | @Override 133 | public String toString () { 134 | return super.toString()+"[kind="+kind+", context="+context+", count="+count+"]"; 135 | } 136 | 137 | } // class Event 138 | } 139 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/unix/AbstractUnixFileSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.unix; 18 | 19 | import com.llamalab.safs.FileStore; 20 | import com.llamalab.safs.FileSystem; 21 | import com.llamalab.safs.LinkOption; 22 | import com.llamalab.safs.Path; 23 | import com.llamalab.safs.PathMatcher; 24 | import com.llamalab.safs.WatchService; 25 | import com.llamalab.safs.attributes.UserPrincipalLookupService; 26 | import com.llamalab.safs.internal.BasicFileAttribute; 27 | import com.llamalab.safs.internal.Utils; 28 | import com.llamalab.safs.spi.FileSystemProvider; 29 | 30 | import java.io.IOException; 31 | import java.util.Collections; 32 | import java.util.Set; 33 | import java.util.regex.Pattern; 34 | 35 | public abstract class AbstractUnixFileSystem extends FileSystem { 36 | 37 | protected final FileSystemProvider provider; 38 | private volatile Path emptyDirectory; 39 | private volatile Path rootDirectory; 40 | 41 | public AbstractUnixFileSystem (FileSystemProvider provider) { 42 | this.provider = provider; 43 | } 44 | 45 | @Override 46 | public final FileSystemProvider provider () { 47 | return provider; 48 | } 49 | 50 | @Override 51 | public final Path getPath (String first, String... more) { 52 | return getPathSanitized(UnixPath.sanitize(first, more)); 53 | } 54 | 55 | public final Path getPath (String first) { 56 | return getPathSanitized(UnixPath.sanitize(first, Utils.EMPTY_STRING_ARRAY)); 57 | } 58 | 59 | protected Path getPathSanitized (String path) { 60 | return new UnixPath(this, path); 61 | } 62 | 63 | @Override 64 | public PathMatcher getPathMatcher (String syntaxAndPattern) { 65 | final Pattern pattern; 66 | if (syntaxAndPattern.startsWith("regex:")) 67 | pattern = Pattern.compile(syntaxAndPattern.substring(6)); 68 | else if (syntaxAndPattern.startsWith("glob:")) 69 | pattern = Pattern.compile(Utils.globToRegex(syntaxAndPattern, 5, syntaxAndPattern.length())); 70 | else 71 | throw new UnsupportedOperationException(syntaxAndPattern); 72 | return new PathMatcher() { 73 | @Override 74 | public boolean matches (Path path) { 75 | return pattern.matcher(path.toString()).matches(); 76 | } 77 | }; 78 | } 79 | 80 | protected abstract Path toRealPath (Path path, LinkOption... options) throws IOException; 81 | 82 | @Override 83 | public final String getSeparator() { 84 | return "/"; 85 | } 86 | 87 | @Override 88 | public Set supportedFileAttributeViews() { 89 | return Collections.singleton(BasicFileAttribute.VIEW_NAME); 90 | } 91 | 92 | @Override 93 | public Iterable getFileStores () { 94 | return Collections.emptySet(); 95 | } 96 | 97 | @Override 98 | public final Iterable getRootDirectories () { 99 | return Collections.singleton(getRootDirectory()); 100 | } 101 | 102 | @Override 103 | public UserPrincipalLookupService getUserPrincipalLookupService () { 104 | throw new UnsupportedOperationException(); 105 | } 106 | 107 | @Override 108 | public WatchService newWatchService () throws IOException { 109 | throw new UnsupportedOperationException(); 110 | } 111 | 112 | public abstract Path getCurrentDirectory (); 113 | 114 | public final Path getEmptyDirectory () { 115 | if (emptyDirectory == null) 116 | emptyDirectory = getPathSanitized(""); 117 | return emptyDirectory; 118 | } 119 | 120 | public final Path getRootDirectory () { 121 | if (rootDirectory == null) 122 | rootDirectory = getPathSanitized("/"); 123 | return rootDirectory; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/StorageVolumeFileStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.os.Build; 21 | import android.os.Environment; 22 | import android.os.storage.StorageVolume; 23 | 24 | import com.llamalab.safs.Path; 25 | 26 | import java.lang.reflect.InvocationTargetException; 27 | import java.lang.reflect.Method; 28 | 29 | @SuppressWarnings("JavaReflectionMemberAccess") 30 | @SuppressLint("NewApi") 31 | final class StorageVolumeFileStore extends AndroidFileStore { 32 | 33 | // https://github.com/aosp-mirror/platform_frameworks_base/blob/ics-mr0-release/core/java/android/os/storage/StorageVolume.java 34 | private static final int PRIMARY_STORAGE_ID = 0x00010001; 35 | 36 | @SuppressWarnings("CanBeFinal") 37 | private static Method StorageVolume_getStorageId; 38 | static { 39 | if (Build.VERSION_CODES.LOLLIPOP > Build.VERSION.SDK_INT) { 40 | try { 41 | StorageVolume_getStorageId = StorageVolume.class.getMethod("getStorageId"); 42 | } 43 | catch (Throwable t) { 44 | // ignore 45 | } 46 | } 47 | } 48 | 49 | private final Path path; 50 | private final StorageVolume volume; 51 | 52 | public StorageVolumeFileStore (Path path, StorageVolume volume) { 53 | this.path = path; 54 | this.volume = volume; 55 | } 56 | 57 | @Override 58 | public String name () { 59 | if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) { 60 | final String uuid = volume.getUuid(); 61 | if (uuid != null) 62 | return uuid; 63 | if (volume.isPrimary()) 64 | return PRIMARY_NAME; 65 | } 66 | else if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) { 67 | if (volume.isPrimary()) 68 | return PRIMARY_NAME; 69 | final int storageId = getStorageId(); 70 | if (storageId != -1) 71 | return Integer.toString(storageId); 72 | } 73 | else { 74 | final int storageId = getStorageId(); 75 | if (PRIMARY_STORAGE_ID == storageId) 76 | return PRIMARY_NAME; 77 | if (storageId != -1) 78 | return Integer.toString(storageId); 79 | } 80 | return "unknown"; 81 | } 82 | 83 | @Override 84 | public Path path () { 85 | return path; 86 | } 87 | 88 | /** 89 | * @return Volume UUID on LOLLIPOP/21 and above, if available, otherwise null. 90 | */ 91 | @Override 92 | String uuid () { 93 | if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) 94 | return volume.getUuid(); 95 | else 96 | return null; 97 | } 98 | 99 | @Override 100 | public boolean isPrimary () { 101 | if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) 102 | return volume.isPrimary(); 103 | else 104 | return PRIMARY_STORAGE_ID == getStorageId(); 105 | } 106 | 107 | @Override 108 | public boolean isEmulated () { 109 | return volume.isEmulated(); 110 | } 111 | 112 | @Override 113 | public boolean isRemovable () { 114 | return volume.isRemovable(); 115 | } 116 | 117 | @Override 118 | public String state () { 119 | if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) 120 | return volume.getState(); 121 | if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) { 122 | //noinspection deprecation 123 | return Environment.getStorageState(path.toFile()); 124 | } 125 | else { 126 | try { 127 | return ((AndroidFileSystem)path.getFileSystem()).getVolumeState(path); 128 | } 129 | catch (Throwable t) { 130 | return isPrimary() ? Environment.getExternalStorageState() : Environment.MEDIA_UNKNOWN; 131 | } 132 | } 133 | } 134 | 135 | private int getStorageId () { 136 | try { 137 | return (int)StorageVolume_getStorageId.invoke(volume); 138 | } 139 | catch (InvocationTargetException e) { 140 | throw (RuntimeException)e.getTargetException(); 141 | } 142 | catch (IllegalAccessException e) { 143 | return -1; 144 | } 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/SegmentEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.unix.UnixPath; 20 | 21 | import java.lang.reflect.Array; 22 | import java.util.Arrays; 23 | import java.util.Iterator; 24 | import java.util.NoSuchElementException; 25 | 26 | public class SegmentEntry> implements Iterable { 27 | 28 | private static final SegmentEntry[] EMPTY = new SegmentEntry[0]; 29 | 30 | String segment; 31 | SegmentEntry[] children = EMPTY; 32 | int size; 33 | 34 | @SuppressWarnings("NullableProblems") 35 | @Override 36 | public Iterator iterator () { 37 | return new Iterator() { 38 | 39 | private int index; 40 | 41 | @Override 42 | public boolean hasNext () { 43 | return index < size; 44 | } 45 | 46 | @SuppressWarnings("unchecked") 47 | @Override 48 | public T next () { 49 | if (index >= size) 50 | throw new NoSuchElementException(); 51 | return (T)children[index++]; 52 | } 53 | 54 | @Override 55 | public void remove () { 56 | if (index == 0) 57 | throw new IllegalStateException(); 58 | SegmentEntry.this.remove(--index); 59 | } 60 | }; 61 | } 62 | 63 | public final boolean isEmpty () { 64 | return size == 0; 65 | } 66 | 67 | public final void clear () { 68 | size = 0; 69 | Arrays.fill(children, null); 70 | } 71 | 72 | @SuppressWarnings({ "unchecked", "SuspiciousSystemArraycopy" }) 73 | public T[] toArray (T[] a) { 74 | if (size > a.length) 75 | a = (T[])Array.newInstance(a.getClass().getComponentType(), size); 76 | System.arraycopy(children, 0, a, 0, size); 77 | return a; 78 | } 79 | 80 | public T getDescendant (UnixPath path) { 81 | return getDescendant(path.stringIterator()); 82 | } 83 | 84 | @SuppressWarnings("unchecked") 85 | public T getDescendant (Iterator segments) { 86 | SegmentEntry parent = this; 87 | while (segments.hasNext()) { 88 | final int index = parent.binarySearch(segments.next()); 89 | if (index < 0) 90 | return null; 91 | parent = parent.children[index]; 92 | } 93 | return (T)parent; 94 | } 95 | 96 | public final PathDescender descentor (UnixPath path) { 97 | return descentor(path.stringIterator()); 98 | } 99 | 100 | @SuppressWarnings("unchecked") 101 | public final PathDescender descentor (Iterator segments) { 102 | return new PathDescender((T)this, segments); 103 | } 104 | 105 | final int binarySearch (String segment) { 106 | final SegmentEntry[] c = children; 107 | int low = 0; 108 | int high = size - 1; 109 | while (low <= high) { 110 | final int mid = (low + high) >>> 1; 111 | final int cmp = c[mid].segment.compareTo(segment); 112 | if (cmp < 0) 113 | low = mid + 1; 114 | else if (cmp > 0) 115 | high = mid - 1; 116 | else 117 | return mid; // key found 118 | } 119 | return -(low + 1); // key not found. 120 | } 121 | 122 | final int put (int index, SegmentEntry child) { 123 | if (index < 0) { 124 | index = ~index; 125 | final SegmentEntry[] oc = children; 126 | final int s = size++; 127 | if (s == oc.length) { 128 | final SegmentEntry[] nc = new SegmentEntry[1 << (32 - Integer.numberOfLeadingZeros(s))]; 129 | System.arraycopy(oc, 0, nc, 0, index); 130 | System.arraycopy(oc, index, nc, index + 1, s - index); 131 | children = nc; 132 | nc[index] = child; 133 | } 134 | else { 135 | System.arraycopy(oc, index, oc, index + 1, s - index); 136 | oc[index] = child; 137 | } 138 | } 139 | else 140 | children[index] = child; 141 | return index; 142 | } 143 | 144 | final void remove (int index) { 145 | if (index < 0 || size <= index) 146 | throw new IndexOutOfBoundsException(); 147 | System.arraycopy(children, index + 1, children, index, --size - index); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidFileStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.annotation.TargetApi; 21 | import android.os.Build; 22 | import android.os.Environment; 23 | import android.os.StatFs; 24 | import android.system.ErrnoException; 25 | import android.system.Os; 26 | import android.system.OsConstants; 27 | import android.system.StructStatVfs; 28 | 29 | import com.llamalab.safs.AccessDeniedException; 30 | import com.llamalab.safs.FileStore; 31 | import com.llamalab.safs.Path; 32 | 33 | import java.io.IOException; 34 | 35 | public abstract class AndroidFileStore extends FileStore { 36 | 37 | static final String PRIMARY_NAME = "primary"; 38 | 39 | @Override 40 | public boolean isReadOnly () { 41 | return Environment.MEDIA_MOUNTED_READ_ONLY.equals(state()); 42 | } 43 | 44 | public abstract Path path (); 45 | public abstract String state (); 46 | 47 | public abstract boolean isPrimary (); 48 | public abstract boolean isEmulated (); 49 | public abstract boolean isRemovable (); 50 | 51 | public boolean isMounted () { 52 | final String state = state(); 53 | return Environment.MEDIA_MOUNTED.equals(state) 54 | || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state); 55 | } 56 | 57 | @Override 58 | public final String type () { 59 | return null; 60 | } 61 | 62 | String uuid () { 63 | return null; 64 | } 65 | 66 | @Override 67 | public String toString () { 68 | return super.toString()+"[path="+path()+", name="+name()+", primary="+isPrimary()+"]"; 69 | } 70 | 71 | @SuppressWarnings("deprecation") 72 | @SuppressLint("NewApi") 73 | @Override 74 | public final long getTotalSpace () throws IOException { 75 | if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) { 76 | final StructStatVfs stat = newStructStatVfs(); 77 | return stat.f_blocks * stat.f_bsize; 78 | } 79 | final StatFs stat = new StatFs(path().toString()); 80 | if (Build.VERSION_CODES.JELLY_BEAN_MR2 <= Build.VERSION.SDK_INT) 81 | return stat.getTotalBytes(); 82 | return (long)stat.getBlockCount() * stat.getBlockSize(); 83 | } 84 | 85 | @SuppressWarnings("deprecation") 86 | @SuppressLint("NewApi") 87 | @Override 88 | public final long getUsableSpace () throws IOException { 89 | if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) { 90 | final StructStatVfs stat = newStructStatVfs(); 91 | return stat.f_bavail * stat.f_bsize; 92 | } 93 | final StatFs stat = new StatFs(path().toString()); 94 | if (Build.VERSION_CODES.JELLY_BEAN_MR2 <= Build.VERSION.SDK_INT) 95 | return stat.getAvailableBytes(); 96 | return (long)stat.getAvailableBlocks() * stat.getBlockSize(); 97 | } 98 | 99 | @SuppressWarnings("deprecation") 100 | @SuppressLint("NewApi") 101 | @Override 102 | public final long getUnallocatedSpace () throws IOException { 103 | if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) { 104 | final StructStatVfs stat = newStructStatVfs(); 105 | return stat.f_bfree * stat.f_bsize; 106 | } 107 | final StatFs stat = new StatFs(path().toString()); 108 | if (Build.VERSION_CODES.JELLY_BEAN_MR2 <= Build.VERSION.SDK_INT) 109 | return stat.getFreeBytes(); 110 | return (long)stat.getFreeBlocks() * stat.getBlockSize(); 111 | } 112 | 113 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 114 | private StructStatVfs newStructStatVfs () throws IOException { 115 | try { 116 | return Os.statvfs(path().toString()); 117 | } 118 | catch (RuntimeException e) { 119 | throw e; 120 | } 121 | catch (Exception e) { 122 | // BUG: https://code.google.com/p/android/issues/detail?id=209129 123 | final ErrnoException ene = (ErrnoException)e; 124 | if (OsConstants.ENOENT == ene.errno) 125 | throw new FileStoreNotFoundException(name()); 126 | if (OsConstants.EACCES == ene.errno) 127 | throw new AccessDeniedException(path().toString()); 128 | //noinspection UnnecessaryInitCause 129 | throw (IOException)new IOException(ene.getMessage()).initCause(ene); 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/spi/FileSystemProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.spi; 18 | 19 | import com.llamalab.safs.CopyOption; 20 | import com.llamalab.safs.DirectoryStream; 21 | import com.llamalab.safs.FileStore; 22 | import com.llamalab.safs.FileSystem; 23 | import com.llamalab.safs.FileSystems; 24 | import com.llamalab.safs.LinkOption; 25 | import com.llamalab.safs.NoSuchFileException; 26 | import com.llamalab.safs.OpenOption; 27 | import com.llamalab.safs.Path; 28 | import com.llamalab.safs.attributes.BasicFileAttributes; 29 | import com.llamalab.safs.attributes.FileAttribute; 30 | import com.llamalab.safs.attributes.FileAttributeView; 31 | import com.llamalab.safs.channels.SeekableByteChannel; 32 | 33 | import java.io.IOException; 34 | import java.io.InputStream; 35 | import java.io.OutputStream; 36 | import java.net.URI; 37 | import java.util.ArrayList; 38 | import java.util.Collections; 39 | import java.util.List; 40 | import java.util.Map; 41 | import java.util.ServiceLoader; 42 | import java.util.Set; 43 | 44 | public abstract class FileSystemProvider { 45 | 46 | protected FileSystemProvider () {} 47 | 48 | public static List installedProviders () { 49 | return InstalledFileSystemProvidersHolder.providers; 50 | } 51 | 52 | public abstract String getScheme (); 53 | public abstract Path getPath (URI uri); 54 | public abstract FileSystem getFileSystem (URI uri); 55 | public abstract FileSystem newFileSystem (Path path, Map env) throws IOException; 56 | public abstract FileSystem newFileSystem (URI uri, Map env) throws IOException; 57 | public abstract FileStore getFileStore (Path path) throws IOException; 58 | public abstract boolean isSameFile (Path path1, Path path2) throws IOException; 59 | public abstract boolean isHidden (Path path) throws IOException; 60 | public abstract void createDirectory (Path dir, FileAttribute... attrs) throws IOException; 61 | public abstract void delete (Path path) throws IOException; 62 | public boolean deleteIfExists (Path path) throws IOException { 63 | try { 64 | delete(path); 65 | return true; 66 | } 67 | catch (NoSuchFileException e) { 68 | return false; 69 | } 70 | } 71 | public abstract void copy (Path source, Path target, CopyOption... options) throws IOException; 72 | public abstract void move (Path source,Path target, CopyOption... options) throws IOException; 73 | public abstract InputStream newInputStream (Path path, OpenOption... options) throws IOException; 74 | public abstract OutputStream newOutputStream (Path path, OpenOption... options) throws IOException; 75 | public abstract SeekableByteChannel newByteChannel (Path path, Set options, FileAttribute... attrs) throws IOException; 76 | /* 77 | public void createSymbolicLink (Path link, Path target) throws IOException { 78 | throw new UnsupportedOperationException(); 79 | } 80 | */ 81 | public Path readSymbolicLink (Path link) throws IOException { 82 | throw new UnsupportedOperationException(); 83 | } 84 | public abstract A readAttributes (Path path, Class type, LinkOption... options) throws IOException; 85 | public abstract Map readAttributes (Path path, String attributes, LinkOption... options) throws IOException; 86 | public abstract void setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException; 87 | public abstract V getFileAttributeView (Path path, Class type, LinkOption... options); 88 | 89 | public abstract DirectoryStream newDirectoryStream (Path dir, DirectoryStream.Filter filter) throws IOException; 90 | 91 | private static final class InstalledFileSystemProvidersHolder { 92 | 93 | static final List providers = loadInstalledProviders(); 94 | 95 | private static List loadInstalledProviders () { 96 | final List providers = new ArrayList(); 97 | providers.add(FileSystems.getDefault().provider()); 98 | for (final FileSystemProvider provider : ServiceLoader.load(FileSystemProvider.class, FileSystemProvider.class.getClassLoader())) { 99 | if (!"file".equalsIgnoreCase(provider.getScheme())) 100 | providers.add(provider); 101 | } 102 | return Collections.unmodifiableList(providers); 103 | } 104 | 105 | } // class InstalledFileSystemProvidersHolder 106 | } 107 | -------------------------------------------------------------------------------- /safs-android/README.md: -------------------------------------------------------------------------------- 1 | safs-android 2 | ============ 3 | 4 | [![Download](https://api.bintray.com/packages/hlindqvi/safs/safs/images/download.svg)](https://bintray.com/hlindqvi/safs/safs/_latestVersion) 5 | 6 | 7 | ### Known limitations 8 | There's sadly some Android issues which can't be worked around: 9 | * [SAF](http://www.androiddocs.com/guide/topics/providers/document-provider.html) trim prefix/suffix whitespace and "filter" certain characters in filenames, 10 | an `FileAlreadyExistsException` is thrown if that occur when creating a file. 11 | * Volumes/mounts not using `sdcardfs` are unable to change file last-modified time, so don't rely on 12 | [StandardCopyOption.COPY_ATTRIBUTES](https://docs.oracle.com/javase/7/docs/api/java/nio/file/StandardCopyOption.html#COPY_ATTRIBUTES), 13 | [Files.setLastModifiedTime()](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#setLastModifiedTime(java.nio.file.Path,%20java.nio.file.attribute.FileTime)) 14 | nor [Files.html.setAttribute()](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#setAttribute(java.nio.file.Path,%20java.lang.String,%20java.lang.Object,%20java.nio.file.LinkOption...)) 15 | See [report](https://code.google.com/p/android/issues/detail?id=18624). 16 | * [WatchService](https://docs.oracle.com/javase/7/docs/api/java/nio/file/WatchService.html), 17 | which use [FileObserver](https://developer.android.com/reference/android/os/FileObserver), doesn't seem to work on 18 | _secondary external storage_ when accessed through 19 | [SAF](http://www.androiddocs.com/guide/topics/providers/document-provider.html). 20 | 21 | See [safs-android-app](../safs-android-app) for integration tests. 22 | 23 | ### Getting started 24 | Add to Gradle project `dependencies`: 25 | ```groovy 26 | implementation 'com.llamalab.safs:safs-android:0.2.0' 27 | ``` 28 | 29 | Add to `AndroidManifest.xml`: 30 | ```xml-fragment 31 | 32 | 33 | ``` 34 | 35 | Add to `proguard-rules.pro`: 36 | ````proguard 37 | -keep class com.llamalab.safs.spi.FileSystemProvider { *; } 38 | -keep class * extends com.llamalab.safs.spi.FileSystemProvider { *; } 39 | -keep class * extends com.llamalab.safs.spi.FileTypeDetector { *; } 40 | ```` 41 | 42 | 43 | ### Usage 44 | Same as [java.nio.file](https://docs.oracle.com/javase/7/docs/api/java/nio/file/package-summary.html) packages except located in `com.llamalab.safs`, 45 | and instead of [java.nio.channels.SeekableByteChannel](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/SeekableByteChannel.html) 46 | use `com.llamalab.safs.channels.SeekableByteChannel`. 47 | 48 | In addition to the standard [Files](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html) 49 | there's [com.llamalab.safs.android.AndroidFiles](src/main/java/com/llamalab/safs/android/AndroidFiles.java) with some convenience methods, and 50 | [com.llamalab.safs.android.AndroidWatchEventKinds](src/main/java/com/llamalab/safs/android/AndroidWatchEventKinds.java) with Android specific 51 | [WatchEvent.Kind](https://docs.oracle.com/javase/7/docs/api/java/nio/file/WatchEvent.Kind.html). 52 | 53 | Before use, `safs-android` need to be told to use the `com.llamalab.safs.android.AndroidFileSystemProvider` as default, 54 | otherwise it will use the [safs-core](../safs-core) provider. 55 | It also need a reference to a `Context`, assigned once, so it's easiest to do early in `Application`. 56 | 57 | ```java 58 | import com.llamalab.safs.FileSystems; 59 | import com.llamalab.safs.android.AndroidFileSystem; 60 | 61 | public class MyApplication extends Application { 62 | 63 | static { 64 | System.setProperty("com.llamalab.safs.spi.DefaultFileSystemProvider", AndroidFileSystemProvider.class.getName()); 65 | } 66 | 67 | public void onCreate () { 68 | ((AndroidFileSystem)FileSystems.getDefault()).setContext(this); 69 | super.onCreate(); 70 | } 71 | } 72 | ``` 73 | 74 | Before accessing a _secondary external storage_ volume, i.e. removable SD card, on Android 5.1+, 75 | the app has to be granted permission to it by the user, then `safs-android` has to be informed. 76 | That's done with a [ACTION_OPEN_DOCUMENT_TREE](https://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT_TREE) intent. 77 | ```java 78 | public class MyActivity extends Activity { 79 | 80 | private static final int REQUEST_CODE_OPEN_DOCUMENT = 1; 81 | 82 | @Override 83 | protected void onCreate (Bundle state) { 84 | super.onCreate(state); 85 | // Ask user to grant permission to an storage volume, or subfolder thereof 86 | startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQUEST_CODE_OPEN_DOCUMENT); 87 | } 88 | 89 | @Override 90 | protected void onActivityResult (int requestCode, int resultCode, Intent resultIntent) { 91 | if (REQUEST_CODE_OPEN_DOCUMENT != requestCode) 92 | super.onActivityResult(requestCode, resultCode, resultIntent); 93 | else if (RESULT_OK == resultCode) { 94 | // Take permission grant 95 | ((AndroidFileSystem)FileSystems.getDefault()).takePersistableUriPermission(resultIntent); 96 | } 97 | } 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidFiles.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.content.Context; 20 | import android.net.Uri; 21 | import android.os.Environment; 22 | import android.os.ParcelFileDescriptor; 23 | 24 | import com.llamalab.safs.FileSystem; 25 | import com.llamalab.safs.FileSystems; 26 | import com.llamalab.safs.OpenOption; 27 | import com.llamalab.safs.Path; 28 | import com.llamalab.safs.ProviderMismatchException; 29 | import com.llamalab.safs.spi.FileSystemProvider; 30 | import com.llamalab.safs.unix.UnixPath; 31 | 32 | import java.io.IOException; 33 | import java.util.Iterator; 34 | 35 | public final class AndroidFiles { 36 | 37 | private AndroidFiles () {} 38 | 39 | /** 40 | * @see android.content.Context#getCacheDir 41 | */ 42 | public static Path getCacheDirectory () { 43 | return fileSystem().getCacheDirectory(); 44 | } 45 | 46 | /** 47 | * @see android.os.Environment#getExternalStorageDirectory 48 | */ 49 | public static Path getExternalStorageDirectory () { 50 | return fileSystem().getExternalStorageDirectory(); 51 | } 52 | 53 | /** 54 | * @see android.content.Context#getFilesDir 55 | */ 56 | public static Path getFilesDirectory () { 57 | final AndroidFileSystem fs = fileSystem(); 58 | return fs.getPathSanitized(fs.getContext().getFilesDir().toString()); 59 | } 60 | 61 | /** 62 | * @see android.content.Context#getDir 63 | */ 64 | public static Path getDirectory (String name) { 65 | final AndroidFileSystem fs = fileSystem(); 66 | return fs.getPathSanitized(fs.getContext().getDir(name, Context.MODE_PRIVATE).toString()); 67 | } 68 | 69 | /** 70 | * @see android.os.Environment#getExternalStoragePublicDirectory 71 | */ 72 | public static Path getExternalStoragePublicDirectory (String type) { 73 | return fileSystem().getPathSanitized(Environment.getExternalStoragePublicDirectory(type).toString()); 74 | } 75 | 76 | public static Path getDataDirectory () { 77 | final AndroidFileSystem fs = fileSystem(); 78 | return fs.getPathSanitized(fs.getContext().getApplicationInfo().dataDir); 79 | } 80 | 81 | /** 82 | * @see android.net.Uri#fromFile 83 | */ 84 | public static Uri toUri (Path path) { 85 | final Uri.Builder ub = new Uri.Builder() 86 | .scheme(provider(path).getScheme()) 87 | .encodedAuthority(""); 88 | final Iterator i = ((UnixPath)path).stringIterator(); 89 | if (path.isAbsolute() || !i.hasNext()) 90 | ub.appendEncodedPath(""); 91 | while (i.hasNext()) 92 | ub.appendPath(i.next()); 93 | return ub.build(); 94 | } 95 | 96 | /** 97 | * @see android.os.Environment#getExternalStorageState 98 | */ 99 | public static boolean isExternalStorageMounted () throws IOException { 100 | return isFileStoreMounted(getExternalStorageDirectory()); 101 | } 102 | 103 | /** 104 | * @see android.os.Environment#getExternalStorageState(java.io.File) 105 | */ 106 | public static boolean isFileStoreMounted (Path path) throws IOException { 107 | return provider(path).isFileStoreMounted(path); 108 | } 109 | 110 | /** 111 | * @see android.os.Environment#isExternalStorageEmulated() 112 | */ 113 | public static boolean isExternalStorageEmulated () throws IOException { 114 | return isFileStoreEmulated(getExternalStorageDirectory()); 115 | } 116 | 117 | /** 118 | * @see android.os.Environment#isExternalStorageEmulated(java.io.File) 119 | */ 120 | public static boolean isFileStoreEmulated (Path path) throws IOException { 121 | return provider(path).isFileStoreEmulated(path); 122 | } 123 | 124 | /** 125 | * @see android.os.Environment#isExternalStorageRemovable 126 | */ 127 | public static boolean isExternalStorageRemovable () throws IOException { 128 | return isFileStoreRemovable(getExternalStorageDirectory()); 129 | } 130 | 131 | /** 132 | * @see android.os.Environment#isExternalStorageRemovable(java.io.File) 133 | */ 134 | public static boolean isFileStoreRemovable (Path path) throws IOException { 135 | return provider(path).isFileStoreRemovable(path); 136 | } 137 | 138 | public static ParcelFileDescriptor newParcelFileDescriptor (Path path, OpenOption... options) throws IOException { 139 | return provider(path).newParcelFileDescriptor(path, options); 140 | } 141 | 142 | private static AndroidFileSystemProvider provider (Path path) { 143 | final FileSystemProvider provider = path.getFileSystem().provider(); 144 | if (!(provider instanceof AndroidFileSystemProvider)) 145 | throw new ProviderMismatchException(); 146 | return (AndroidFileSystemProvider)provider; 147 | } 148 | 149 | private static AndroidFileSystem fileSystem () { 150 | final FileSystem fs = FileSystems.getDefault(); 151 | if (!(fs instanceof AndroidFileSystem)) 152 | throw new ProviderMismatchException("AndroidFileSystem not default"); 153 | return (AndroidFileSystem)fs; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /safs-core/src/test/java/com/llamalab/safs/MiscTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs; 18 | 19 | import com.llamalab.safs.attributes.FileTime; 20 | import com.llamalab.safs.internal.Utils; 21 | 22 | import junit.framework.TestCase; 23 | 24 | import java.util.Calendar; 25 | import java.util.GregorianCalendar; 26 | import java.util.Locale; 27 | import java.util.TimeZone; 28 | import java.util.regex.Pattern; 29 | import java.util.regex.PatternSyntaxException; 30 | 31 | public class MiscTests extends TestCase { 32 | 33 | public void testFileTime () { 34 | assertEquals("1970-01-01T00:00:00Z", FileTime.fromMillis(0).toString()); 35 | assertEquals("1970-01-01T00:00:00.001Z", FileTime.fromMillis(1).toString()); 36 | } 37 | 38 | 39 | public void testRfc3339 () { 40 | assertEquals(0L, Utils.parseRfc3339("1970-01-01T00:00:00Z")); 41 | assertEquals(datetime(1985,4,12, 23,20,50,52,"UTC"), Utils.parseRfc3339("1985-04-12T23:20:50.52Z")); 42 | assertEquals(datetime(1996,12,19, 16,39,57,0,"-8:00"), Utils.parseRfc3339("1996-12-19T16:39:57-08:00")); 43 | assertEquals("1970-01-01T00:00:00Z", Utils.formatRfc3339(0)); 44 | assertEquals("1985-04-12T23:20:50.052Z", Utils.formatRfc3339(datetime(1985, 4, 12, 23, 20, 50, 52, "UTC"))); 45 | } 46 | 47 | private static long datetime (int year, int month, int day, int hour, int minute, int second, int millis, String tz) { 48 | final GregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone(tz), Locale.US); 49 | //noinspection MagicConstant 50 | gc.set(year, month - 1, day, hour, minute, second); 51 | gc.set(Calendar.MILLISECOND, millis); 52 | return gc.getTimeInMillis(); 53 | } 54 | 55 | 56 | public void testGlob () { 57 | 58 | assertMatches( compileGlob("foo"), "foo"); 59 | assertNotMatches(compileGlob("bar"), "foo"); 60 | assertNotMatches(compileGlob("foo"), "f"); 61 | 62 | assertMatches( compileGlob("*"), ""); 63 | assertMatches( compileGlob("*"), "foo"); 64 | assertMatches( compileGlob("f*"), "foo"); 65 | assertMatches( compileGlob("*o"), "foo"); 66 | assertMatches( compileGlob("f*r"), "foobar"); 67 | assertNotMatches(compileGlob("f*b"), "foobar"); 68 | assertMatches( compileGlob("f*b*r"), "foobar"); 69 | assertMatches( compileGlob("*foo*"), "\u200E\u202Afoobar\u202C\u200E"); // RTL 70 | 71 | assertNotMatches(compileGlob("?"), ""); 72 | assertMatches( compileGlob("???"), "foo"); 73 | assertNotMatches(compileGlob("??"), "foo"); 74 | assertMatches( compileGlob("f??"), "foo"); 75 | assertNotMatches(compileGlob("b??"), "foo"); 76 | assertNotMatches(compileGlob("f?"), "foo"); 77 | assertMatches( compileGlob("fo?"), "foo"); 78 | assertMatches( compileGlob("?oo"), "foo"); 79 | assertNotMatches(compileGlob("???"), "f/o"); 80 | 81 | assertMatches( compileGlob("f*/b*"), "foo/bar"); 82 | assertMatches( compileGlob("f**"), "foo/bar"); 83 | assertMatches( compileGlob("foo/**/baz"), "foo/bar/baz"); 84 | assertMatches( compileGlob("foo/**/bax"), "foo/bar/baz/bax"); 85 | assertNotMatches(compileGlob("foo/*/bax"), "foo/bar/baz/bax"); 86 | 87 | assertMatches( compileGlob("*.java"), "foo.java"); 88 | assertMatches( compileGlob("*.*"), "foo.java"); 89 | assertMatches( compileGlob("*.{java,class}"), "foo.java"); 90 | assertMatches( compileGlob("foo.?"), "foo.x"); 91 | assertMatches( compileGlob("/home/*/*"), "/home/gus/data"); 92 | assertMatches( compileGlob("/home/**"), "/home/gus"); 93 | assertMatches( compileGlob("/home/**"), "/home/gus/data"); 94 | 95 | assertNotMatches(compileGlob("*.{xml,png}"), "foo.java"); 96 | assertNotMatches(compileGlob("foo.?"), "foo."); 97 | 98 | assertMatches( compileGlob("*.{java,class}"), "foo.class"); 99 | assertNotMatches(compileGlob("*.{java,class}"), "foo.bar"); 100 | 101 | assertMatches( compileGlob("[f]oo"), "foo"); 102 | assertNotMatches(compileGlob("[!f]oo"), "foo"); 103 | assertMatches( compileGlob("[!f]oo"), "boo"); 104 | assertMatches( compileGlob("[!a-y]oo"), "zoo"); 105 | assertMatches( compileGlob("[a-y]oo"), "xoo"); 106 | assertMatches( compileGlob("[-]oo"), "-oo"); 107 | assertNotMatches(compileGlob("[!-]oo"), "-oo"); 108 | 109 | assertMatches( compileGlob("b\\*z"), "b*z"); 110 | assertNotMatches(compileGlob("b\\*z"), "baz"); 111 | assertMatches( compileGlob("b\\?z"), "b?z"); 112 | assertNotMatches(compileGlob("b\\?z"), "baz"); 113 | 114 | assertMatches( compileGlob("b\\z"), "bz"); 115 | assertMatches( compileGlob("b\\\\z"), "b\\z"); 116 | 117 | try { 118 | compileGlob("{foo,bar{baz}}"); 119 | fail(); 120 | } 121 | catch (PatternSyntaxException e) { 122 | // expected 123 | } 124 | try { 125 | compileGlob("[z-a]"); 126 | fail(); 127 | } 128 | catch (PatternSyntaxException e) { 129 | // expected 130 | } 131 | } 132 | 133 | private static Pattern compileGlob (String glob) { 134 | final String regex = Utils.globToRegex(glob, 0, glob.length()); 135 | //System.out.println(regex); 136 | return Pattern.compile(regex); 137 | } 138 | 139 | private static void assertMatches (Pattern pattern, CharSequence input) { 140 | assertTrue(pattern.matcher(input).matches()); 141 | } 142 | private static void assertNotMatches (Pattern pattern, CharSequence input) { 143 | assertFalse(pattern.matcher(input).matches()); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /safs-core/src/main/java/com/llamalab/safs/internal/AbstractFileSystemProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.internal; 18 | 19 | import com.llamalab.safs.LinkOption; 20 | import com.llamalab.safs.NoSuchFileException; 21 | import com.llamalab.safs.OpenOption; 22 | import com.llamalab.safs.Path; 23 | import com.llamalab.safs.ProviderMismatchException; 24 | import com.llamalab.safs.StandardOpenOption; 25 | import com.llamalab.safs.attributes.BasicFileAttributeView; 26 | import com.llamalab.safs.attributes.BasicFileAttributes; 27 | import com.llamalab.safs.attributes.FileAttribute; 28 | import com.llamalab.safs.attributes.FileAttributeView; 29 | import com.llamalab.safs.attributes.FileTime; 30 | import com.llamalab.safs.spi.FileSystemProvider; 31 | import com.llamalab.safs.unix.UnixPath; 32 | 33 | import java.io.FileNotFoundException; 34 | import java.io.IOException; 35 | import java.net.URI; 36 | import java.util.Collections; 37 | import java.util.EnumSet; 38 | import java.util.HashMap; 39 | import java.util.HashSet; 40 | import java.util.Map; 41 | import java.util.Set; 42 | 43 | /** 44 | * Only support {@link UnixPath}. 45 | */ 46 | public abstract class AbstractFileSystemProvider extends FileSystemProvider { 47 | 48 | protected static final Set DEFAULT_NEW_INPUT_STREAM_OPTIONS = EnumSet.of(StandardOpenOption.READ); 49 | protected static final Set DEFAULT_NEW_OUTPUT_STREAM_OPTIONS = EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); 50 | 51 | protected abstract Class getPathType (); 52 | 53 | protected void checkPath (Path path) { 54 | if (!getPathType().isInstance(path)) 55 | throw (path == null) ? new NullPointerException() : new ProviderMismatchException(); 56 | if (path.getFileSystem().provider() != this) 57 | throw new ProviderMismatchException(); 58 | } 59 | 60 | protected void checkUri (URI uri) { 61 | if (!getScheme().equalsIgnoreCase(uri.getScheme())) 62 | throw new ProviderMismatchException(); 63 | } 64 | 65 | protected IOException toProperException (IOException ioe, String file, String otherFile) { 66 | return (ioe instanceof FileNotFoundException) ? new NoSuchFileException(file) : ioe; 67 | } 68 | 69 | @Override 70 | public Map readAttributes (Path path, String attributes, LinkOption... options) throws IOException { 71 | final Map map = new HashMap(); 72 | BasicFileAttributes basic = null; 73 | for (final BasicFileAttribute attribute : BasicFileAttribute.parse(attributes)) { 74 | if (basic == null) 75 | basic = readAttributes(path, BasicFileAttributes.class, options); // read once 76 | map.put(attribute.toString(), attribute.valueOf(basic)); 77 | } 78 | return map; 79 | } 80 | 81 | @Override 82 | public void setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException { 83 | String viewName = BasicFileAttribute.VIEW_NAME; 84 | final int colon = attribute.indexOf(':'); 85 | if (colon != -1) { 86 | viewName = attribute.substring(0, colon); 87 | attribute = attribute.substring(colon + 1); 88 | } 89 | final FileAttribute attr = newFileAttribute(viewName, attribute, value); 90 | setAttributes(path, Collections.singleton(attr), options); 91 | } 92 | 93 | protected FileAttribute newFileAttribute (String viewName, String attribute, Object value) { 94 | if (BasicFileAttribute.VIEW_NAME.equals(viewName)) 95 | return BasicFileAttribute.valueOf(attribute).newFileAttribute(value); 96 | throw new UnsupportedOperationException("Attribute: "+viewName+":"+attribute); 97 | } 98 | 99 | protected abstract void setAttributes (Path path, Set> attrs, LinkOption... options) throws IOException; 100 | 101 | @SuppressWarnings("unchecked") 102 | public V getFileAttributeView (final Path path, Class type, final LinkOption... options) { 103 | checkPath(path); 104 | if (BasicFileAttributeView.class == type) 105 | return (V)new BasicFileAttributeViewImpl(path, options); 106 | return null; 107 | } 108 | 109 | 110 | protected class BasicFileAttributeViewImpl implements BasicFileAttributeView { 111 | 112 | protected final Path path; 113 | protected final LinkOption[] options; 114 | 115 | public BasicFileAttributeViewImpl (Path path, LinkOption[] options) { 116 | this.path = path; 117 | this.options = options; 118 | } 119 | 120 | @Override 121 | public String name () { 122 | return BasicFileAttribute.VIEW_NAME; 123 | } 124 | 125 | @Override 126 | public BasicFileAttributes readAttributes () throws IOException { 127 | return AbstractFileSystemProvider.this.readAttributes(path, BasicFileAttributes.class, options); 128 | } 129 | 130 | @Override 131 | public void setTimes (FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime) throws IOException { 132 | final Set> attrs = new HashSet>(); 133 | if (lastModifiedTime != null) 134 | attrs.add(BasicFileAttribute.lastModifiedTime.newFileAttribute(lastModifiedTime)); 135 | if (lastAccessTime != null) 136 | attrs.add(BasicFileAttribute.lastAccessTime.newFileAttribute(lastAccessTime)); 137 | if (creationTime != null) 138 | attrs.add(BasicFileAttribute.creationTime.newFileAttribute(creationTime)); 139 | AbstractFileSystemProvider.this.setAttributes(path, attrs, options); 140 | } 141 | 142 | } // class BasicFileAttributeViewImpl 143 | 144 | } 145 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidWatchEventKinds.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.os.FileObserver; 20 | 21 | import com.llamalab.safs.Path; 22 | import com.llamalab.safs.StandardWatchEventKinds; 23 | import com.llamalab.safs.WatchEvent; 24 | 25 | // http://linux.die.net/include/sys/inotify.h 26 | public final class AndroidWatchEventKinds { 27 | 28 | /** 29 | * File was accessed. 30 | * @see android.os.FileObserver#ACCESS 31 | */ 32 | public static final WatchEvent.Kind ACCESS = new AndroidWatchEventKind<>("ACCESS", Path.class, FileObserver.ACCESS); 33 | /** 34 | * File was modified. 35 | * @see android.os.FileObserver#MODIFY 36 | */ 37 | public static final WatchEvent.Kind MODIFY = new AndroidWatchEventKind<>("MODIFY", Path.class, FileObserver.MODIFY); 38 | /** 39 | * Metadata changed. 40 | * @see android.os.FileObserver#ATTRIB 41 | */ 42 | public static final WatchEvent.Kind ATTRIB = new AndroidWatchEventKind<>("ATTRIB", Path.class, FileObserver.ATTRIB); 43 | /** 44 | * File opened for writing was closed. 45 | * @see android.os.FileObserver#CLOSE_WRITE 46 | */ 47 | public static final WatchEvent.Kind CLOSE_WRITE = new AndroidWatchEventKind<>("CLOSE_WRITE", Path.class, FileObserver.CLOSE_WRITE); 48 | /** 49 | * File not opened for writing was closed. 50 | * @see android.os.FileObserver#CLOSE_NOWRITE 51 | */ 52 | public static final WatchEvent.Kind CLOSE_NOWRITE = new AndroidWatchEventKind<>("CLOSE_NOWRITE", Path.class, FileObserver.CLOSE_NOWRITE); 53 | /** 54 | * File was opened. 55 | * @see android.os.FileObserver#OPEN 56 | */ 57 | public static final WatchEvent.Kind OPEN = new AndroidWatchEventKind<>("OPEN", Path.class, FileObserver.OPEN); 58 | /** 59 | * File moved out of watched directory. 60 | * @see android.os.FileObserver#MOVED_FROM 61 | */ 62 | public static final WatchEvent.Kind MOVED_FROM = new AndroidWatchEventKind<>("MOVED_FROM", Path.class, FileObserver.MOVED_FROM); 63 | /** 64 | * File moved into watched directory 65 | * @see android.os.FileObserver#MOVED_TO 66 | */ 67 | public static final WatchEvent.Kind MOVED_TO = new AndroidWatchEventKind<>("MOVED_TO", Path.class, FileObserver.MOVED_TO); 68 | /** 69 | * File/directory created in watched directory. 70 | * @see android.os.FileObserver#CREATE 71 | */ 72 | public static final WatchEvent.Kind CREATE = new AndroidWatchEventKind<>("CREATE", Path.class, FileObserver.CREATE); 73 | /** 74 | * File/directory deleted from watched directory. 75 | * @see android.os.FileObserver#DELETE 76 | */ 77 | public static final WatchEvent.Kind DELETE = new AndroidWatchEventKind<>("DELETE", Path.class, FileObserver.DELETE); 78 | /** 79 | * Watched file/directory was itself deleted. 80 | * @see android.os.FileObserver#DELETE_SELF 81 | */ 82 | public static final WatchEvent.Kind DELETE_SELF = new AndroidWatchEventKind<>("DELETE_SELF", Void.class, FileObserver.DELETE_SELF); 83 | /** 84 | * Watched file/directory was itself moved. 85 | * @see android.os.FileObserver#MOVE_SELF 86 | */ 87 | public static final WatchEvent.Kind MOVE_SELF = new AndroidWatchEventKind<>("MOVE_SELF", Void.class, FileObserver.MOVE_SELF); 88 | /** 89 | * File system containing watched object was unmounted. 90 | * Not officially supported by Android, may not work! 91 | */ 92 | public static final WatchEvent.Kind UNMOUNT = new AndroidWatchEventKind<>("UNMOUNT", Void.class, 0x2000); 93 | /* 94 | * Watch was removed explicitly or automatically (file was deleted, or file system was unmounted). 95 | * Not officially supported by Android, may not work! 96 | */ 97 | //public static final WatchEvent.Kind IGNORED = new AndroidWatchEventKind("IGNORED", Void.class, 0x8000); 98 | 99 | private static final int ALL_MASK = FileObserver.ALL_EVENTS | 0x2000;// | 0x8000; 100 | 101 | private static final WatchEvent.Kind[] ALL_KINDS = { 102 | ACCESS, MODIFY, ATTRIB, CLOSE_WRITE, CLOSE_NOWRITE, OPEN, MOVED_FROM, MOVED_TO, CREATE, DELETE, DELETE_SELF, MOVE_SELF, 103 | null, UNMOUNT, //null, IGNORED 104 | }; 105 | 106 | private AndroidWatchEventKinds () {} 107 | 108 | /** 109 | * {@link AndroidWatchEventKinds} of event mask. 110 | */ 111 | public static WatchEvent.Kind[] of (int mask) { 112 | mask &= ALL_MASK; 113 | int o = Integer.bitCount(mask); 114 | final WatchEvent.Kind[] kinds = new WatchEvent.Kind[o]; 115 | for (int i = ALL_KINDS.length; --i >= 0;) { 116 | if ((mask & (1 << i)) != 0) 117 | kinds[--o] = ALL_KINDS[i]; 118 | } 119 | return kinds; 120 | } 121 | 122 | /** 123 | * Event mask for {@link AndroidWatchEventKinds} or {@link StandardWatchEventKinds}. 124 | */ 125 | public static int mask (WatchEvent.Kind kind) { 126 | if (kind instanceof AndroidWatchEventKind) 127 | return ((AndroidWatchEventKind)kind).event(); 128 | if (StandardWatchEventKinds.ENTRY_CREATE == kind) 129 | return FileObserver.CREATE | FileObserver.MOVED_TO; 130 | if (StandardWatchEventKinds.ENTRY_DELETE == kind) 131 | return FileObserver.DELETE | FileObserver.MOVED_FROM; 132 | if (StandardWatchEventKinds.ENTRY_MODIFY == kind) 133 | return FileObserver.MODIFY | FileObserver.ATTRIB; 134 | return 0; 135 | } 136 | 137 | /** 138 | * Event mask for {@link AndroidWatchEventKinds} and {@link StandardWatchEventKinds}. 139 | */ 140 | public static int mask (WatchEvent.Kind... kinds) { 141 | int mask = 0; 142 | for (final WatchEvent.Kind kind : kinds) 143 | mask |= mask(kind); 144 | return mask; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/SeekableByteChannelWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.annotation.TargetApi; 20 | import android.os.Build; 21 | import android.os.ParcelFileDescriptor; 22 | import android.system.ErrnoException; 23 | import android.system.Os; 24 | import android.system.OsConstants; 25 | 26 | import com.llamalab.safs.channels.SeekableByteChannel; 27 | 28 | import java.io.FileDescriptor; 29 | import java.io.IOException; 30 | import java.nio.ByteBuffer; 31 | import java.nio.channels.ClosedChannelException; 32 | import java.nio.channels.NonReadableChannelException; 33 | import java.nio.channels.NonWritableChannelException; 34 | import java.nio.channels.spi.AbstractInterruptibleChannel; 35 | 36 | // https://android.googlesource.com/platform/libcore/+/marshmallow-release/luni/src/main/java/java/nio/FileChannelImpl.java 37 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 38 | final class SeekableByteChannelWrapper extends AbstractInterruptibleChannel implements SeekableByteChannel { 39 | 40 | private final ParcelFileDescriptor pfd; 41 | private final FileDescriptor fd; 42 | private final int mode; 43 | 44 | /* 45 | public SeekableByteChannelWrapper (ParcelFileDescriptor pfd, String mode) { 46 | this(pfd, ParcelFileDescriptor.parseMode(mode)); 47 | } 48 | */ 49 | 50 | public SeekableByteChannelWrapper (ParcelFileDescriptor pfd, int mode) { 51 | this.pfd = pfd; 52 | this.fd = pfd.getFileDescriptor(); 53 | this.mode = mode; 54 | } 55 | 56 | private void checkOpen () throws ClosedChannelException { 57 | if (!isOpen()) 58 | throw new ClosedChannelException(); 59 | } 60 | 61 | private void checkReadable () { 62 | //if ((mode & OsConstants.O_ACCMODE) == OsConstants.O_WRONLY) 63 | if ((mode & ParcelFileDescriptor.MODE_READ_WRITE) == ParcelFileDescriptor.MODE_WRITE_ONLY) 64 | throw new NonReadableChannelException(); 65 | } 66 | 67 | private void checkWritable () { 68 | //if ((mode & OsConstants.O_ACCMODE) == OsConstants.O_RDONLY) 69 | if ((mode & ParcelFileDescriptor.MODE_READ_WRITE) == ParcelFileDescriptor.MODE_READ_ONLY) 70 | throw new NonWritableChannelException(); 71 | } 72 | 73 | @Override 74 | protected void implCloseChannel () throws IOException { 75 | pfd.close(); 76 | } 77 | 78 | @Override 79 | public int read (ByteBuffer dst) throws IOException { 80 | checkOpen(); 81 | checkReadable(); 82 | final int position = dst.position(); 83 | if (dst.limit() <= position) 84 | return 0; 85 | int bytesRead = 0; 86 | boolean completed = false; 87 | try { 88 | begin(); 89 | bytesRead = Os.read(fd, dst); 90 | if (bytesRead == 0) 91 | bytesRead = -1; 92 | completed = true; 93 | } 94 | catch (ErrnoException e) { 95 | if (OsConstants.EAGAIN != e.errno) { 96 | //noinspection UnnecessaryInitCause 97 | throw (IOException)new IOException(e.getMessage()).initCause(e); 98 | } 99 | bytesRead = 0; 100 | } 101 | finally { 102 | end(completed && bytesRead >= 0); 103 | } 104 | // BUG: Lollipop doesn't update position 105 | if (bytesRead > 0) 106 | dst.position(position + bytesRead); 107 | return bytesRead; 108 | } 109 | 110 | @Override 111 | public int write (ByteBuffer src) throws IOException { 112 | checkOpen(); 113 | checkWritable(); 114 | final int position = src.position(); 115 | if (src.limit() <= position) 116 | return 0; 117 | int bytesWritten; 118 | boolean completed = false; 119 | try { 120 | begin(); 121 | try { 122 | bytesWritten = Os.write(fd, src); 123 | } 124 | catch (ErrnoException e) { 125 | //noinspection UnnecessaryInitCause 126 | throw (IOException)new IOException(e.getMessage()).initCause(e); 127 | } 128 | completed = true; 129 | } 130 | finally { 131 | end(completed); 132 | } 133 | // BUG: Lollipop doesn't update position 134 | if (bytesWritten > 0) 135 | src.position(position + bytesWritten); 136 | return bytesWritten; 137 | } 138 | 139 | @Override 140 | public long position () throws IOException { 141 | checkOpen(); 142 | try { 143 | return Os.lseek(fd, 0L, OsConstants.SEEK_CUR); 144 | } 145 | catch (ErrnoException e) { 146 | //noinspection UnnecessaryInitCause 147 | throw (IOException)new IOException(e.getMessage()).initCause(e); 148 | } 149 | } 150 | 151 | @Override 152 | public SeekableByteChannel position (long newPosition) throws IOException { 153 | if (newPosition < 0) 154 | throw new IllegalArgumentException(); 155 | checkOpen(); 156 | try { 157 | Os.lseek(fd, newPosition, OsConstants.SEEK_SET); 158 | } 159 | catch (ErrnoException e) { 160 | //noinspection UnnecessaryInitCause 161 | throw (IOException)new IOException(e.getMessage()).initCause(e); 162 | } 163 | return this; 164 | } 165 | 166 | @Override 167 | public long size () throws IOException { 168 | checkOpen(); 169 | try { 170 | return Os.fstat(fd).st_size; 171 | } 172 | catch (ErrnoException e) { 173 | //noinspection UnnecessaryInitCause 174 | throw (IOException)new IOException(e.getMessage()).initCause(e); 175 | } 176 | } 177 | 178 | @Override 179 | public SeekableByteChannel truncate (long size) throws IOException { 180 | if (size < 0) 181 | throw new IllegalArgumentException(); 182 | checkOpen(); 183 | checkWritable(); 184 | if (size < size()) { 185 | try { 186 | Os.ftruncate(fd, size); 187 | } 188 | catch (ErrnoException e) { 189 | //noinspection UnnecessaryInitCause 190 | throw (IOException)new IOException(e.getMessage()).initCause(e); 191 | } 192 | } 193 | if (position() > size) 194 | position(size); 195 | return this; 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /safs-android/src/main/java/com/llamalab/safs/android/AndroidWatchService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Henrik Lindqvist 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.llamalab.safs.android; 18 | 19 | import android.os.FileObserver; 20 | import android.util.Log; 21 | 22 | import com.llamalab.safs.Files; 23 | import com.llamalab.safs.LinkOption; 24 | import com.llamalab.safs.NoSuchFileException; 25 | import com.llamalab.safs.Path; 26 | import com.llamalab.safs.ProviderMismatchException; 27 | import com.llamalab.safs.StandardWatchEventKinds; 28 | import com.llamalab.safs.WatchEvent; 29 | import com.llamalab.safs.Watchable; 30 | import com.llamalab.safs.internal.AbstractWatchKey; 31 | import com.llamalab.safs.internal.AbstractWatchService; 32 | 33 | import java.io.IOException; 34 | import java.util.ArrayList; 35 | import java.util.HashMap; 36 | import java.util.Iterator; 37 | import java.util.List; 38 | import java.util.Map; 39 | 40 | /** 41 | * BUG: https://code.google.com/p/android/issues/detail?id=92329 42 | */ 43 | final class AndroidWatchService extends AbstractWatchService { 44 | 45 | private static final String TAG = "AndroidWatchService"; 46 | private static final boolean DEBUG = BuildConfig.DEBUG; 47 | 48 | private static final int IN_Q_OVERFLOW = 0x4000; 49 | 50 | private final static Map observers = new HashMap<>(); 51 | 52 | private final AndroidFileSystem fs; 53 | 54 | AndroidWatchService (AndroidFileSystem fs) { 55 | this.fs = fs; 56 | } 57 | 58 | AbstractWatchKey register (AndroidPath path, WatchEvent.Kind[] kinds, WatchEvent.Modifier... modifiers) throws IOException { 59 | if (!fs.equals(path.getFileSystem())) 60 | throw new ProviderMismatchException(); 61 | if (!Files.exists(path, LinkOption.NOFOLLOW_LINKS)) 62 | throw new NoSuchFileException(path.toString()); 63 | final AndroidWatchKey key = new AndroidWatchKey(this, path, kinds); 64 | synchronized (observers) { 65 | PathObserver observer = observers.get(path); 66 | if (observer == null) 67 | observers.put(path, observer = new PathObserver(path)); 68 | observer.register(key); 69 | } 70 | return key; 71 | } 72 | 73 | private void cancel (AndroidWatchKey key) { 74 | final Path path = (Path)key.watchable(); 75 | synchronized (observers) { 76 | final PathObserver observer = observers.get(path); 77 | if (observer != null && !observer.cancel(key)) 78 | observers.remove(path); 79 | } 80 | } 81 | 82 | @Override 83 | protected void implCloseService () throws IOException { 84 | synchronized (observers) { 85 | for (Iterator i = observers.values().iterator(); i.hasNext();) { 86 | if (!i.next().cancel(this)) 87 | i.remove(); 88 | } 89 | } 90 | } 91 | 92 | private static final class PathObserver { 93 | 94 | // TODO: maybe make it a weak set? 95 | private final List keys = new ArrayList<>(); 96 | private final String path; 97 | private FileObserver observer; 98 | private int mask; 99 | 100 | public PathObserver (Path path) { 101 | this.path = path.toString(); 102 | } 103 | 104 | public synchronized void register (AndroidWatchKey key) { 105 | keys.add(key); 106 | listen(mask | key.mask); 107 | } 108 | 109 | public synchronized boolean cancel (AndroidWatchKey key) { 110 | int mask = 0; 111 | for (Iterator i = keys.iterator(); i.hasNext(); ) { 112 | final AndroidWatchKey k = i.next(); 113 | if (key == k) 114 | i.remove(); 115 | else 116 | mask |= k.mask; 117 | } 118 | return listen(mask); 119 | } 120 | 121 | public synchronized boolean cancel (AndroidWatchService service) { 122 | int mask = 0; 123 | for (Iterator i = keys.iterator(); i.hasNext(); ) { 124 | final AndroidWatchKey k = i.next(); 125 | if (service == k.service()) 126 | i.remove(); 127 | else 128 | mask |= k.mask; 129 | } 130 | return listen(mask); 131 | } 132 | 133 | private boolean listen (int newMask) { 134 | if (newMask == 0) { 135 | if (DEBUG) Log.d(TAG, "listen: stopping"); 136 | mask = 0; 137 | observer.stopWatching(); 138 | observer = null; 139 | return false; 140 | } 141 | if (newMask != mask) { 142 | if (DEBUG) Log.d(TAG, "listen: updating to 0x"+Integer.toHexString(newMask)); 143 | observer = new FileObserver(path, mask = newMask) { 144 | @Override 145 | public void onEvent (int event, String path) { 146 | if (DEBUG) Log.d("PathObserver", "onEvent: 0x"+Integer.toHexString(event)+", "+path); 147 | synchronized (PathObserver.this) { 148 | for (final AndroidWatchKey key : keys) 149 | key.onEvent(event, path); 150 | } 151 | } 152 | @Override 153 | protected void finalize () { 154 | // Prevent stopWatching call! 155 | } 156 | }; 157 | // RTFM Google: http://linux.die.net/man/7/inotify 158 | // startWatching will replace the previously started FileObserver for the same path, so 159 | // there's no need to call stopWatching. See the startWatching method in class ObserverThread: 160 | // https://github.com/android/platform_frameworks_base/blob/master/core/java/android/os/FileObserver.java#L94 161 | observer.startWatching(); 162 | } 163 | return true; 164 | } 165 | 166 | } // class PathObserver 167 | 168 | private static final class AndroidWatchKey extends AbstractWatchKey { 169 | 170 | private final WatchEvent.Kind[] kinds; 171 | private volatile int mask; 172 | 173 | public AndroidWatchKey (AbstractWatchService service, Watchable watchable, WatchEvent.Kind[] kinds) { 174 | super(service, watchable, 512); 175 | final int mask = AndroidWatchEventKinds.mask(kinds); 176 | if (mask == 0) 177 | throw new IllegalArgumentException("kinds"); 178 | this.mask = mask; 179 | this.kinds = kinds; 180 | } 181 | 182 | @Override 183 | public boolean isValid () { 184 | return mask != 0 && super.isValid(); 185 | } 186 | 187 | @Override 188 | public void cancel () { 189 | mask = 0; 190 | ((AndroidWatchService)service()).cancel(this); 191 | } 192 | 193 | @SuppressWarnings("unchecked") 194 | void onEvent (int event, String path) { 195 | if ((event & mask) != 0) { 196 | for (final WatchEvent.Kind kind : kinds) { 197 | if ((event & AndroidWatchEventKinds.mask(kind)) != 0) { 198 | if (DEBUG) Log.d("AndroidWatchKey", "onEvent: "+kind+", "+path); 199 | if (Path.class == kind.type() && path != null) 200 | signalEvent((WatchEvent.Kind)kind, ((AndroidWatchService)service()).fs.getPath(path)); 201 | else 202 | signalEvent(kind, null); 203 | } 204 | } 205 | } 206 | else if (IN_Q_OVERFLOW == event) 207 | signalEvent(StandardWatchEventKinds.OVERFLOW, null); 208 | } 209 | 210 | } // class AndroidWatchKey 211 | 212 | } 213 | --------------------------------------------------------------------------------