[Feature] Use SevenZipJBinding for opening archives, but it's buggy.

This commit is contained in:
Hai Zhang 2019-09-01 15:35:56 -07:00
parent 2012dc8d93
commit 8e48609e7b
21 changed files with 804 additions and 141 deletions

View File

@ -65,7 +65,7 @@ dependencies {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0-alpha03'

Binary file not shown.

View File

@ -15,6 +15,7 @@ import me.zhanghai.android.files.firebase.CrashlyticsUtils;
import me.zhanghai.android.files.provider.FileSystemProviders;
import me.zhanghai.android.files.theme.custom.CustomThemeHelper;
import me.zhanghai.android.files.theme.night.NightModeHelper;
import me.zhanghai.android.sevenzipjbinding.SevenZip;
public class AppApplication extends Application {
@ -38,6 +39,7 @@ public class AppApplication extends Application {
CrashlyticsUtils.init(this);
Stetho.initializeWithDefaults(this);
SevenZip.init();
FileSystemProviders.install();
FileSystemProviders.setOverflowWatchEvents(true);

View File

@ -44,8 +44,8 @@ public class FilePropertiesBasicTabFragment extends AppCompatDialogFragment {
ViewGroup mArchiveFileAndEntryLayout;
@BindView(R.id.archive_file)
TextView mArchiveFileText;
@BindView(R.id.archive_entry)
TextView mArchiveEntryText;
@BindView(R.id.archive_item)
TextView mArchiveItemText;
@BindView(R.id.type)
TextView mTypeText;
@BindView(R.id.symbolic_link_target_layout)
@ -113,7 +113,7 @@ public class FilePropertiesBasicTabFragment extends AppCompatDialogFragment {
Path archiveFile = ArchiveFileSystemProvider.getArchiveFile(path);
mArchiveFileText.setText(archiveFile.toFile().getPath());
ArchiveFileAttributes attributes = (ArchiveFileAttributes) mExtraFile.getAttributes();
mArchiveEntryText.setText(attributes.getEntryName());
mArchiveItemText.setText(attributes.getItemPath());
}
mTypeText.setText(getTypeText(mExtraFile));
boolean isSymbolicLink = mExtraFile.getAttributesNoFollowLinks().isSymbolicLink();

View File

@ -7,30 +7,29 @@ package me.zhanghai.android.files.provider.archive;
import android.os.Parcel;
import org.apache.commons.compress.archivers.ArchiveEntry;
import androidx.annotation.NonNull;
import java8.nio.file.Path;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveItem;
import me.zhanghai.android.files.provider.common.ParcelablePosixFileAttributes;
public class ArchiveFileAttributes extends ParcelablePosixFileAttributes {
@NonNull
private final String mEntryName;
private final String mItemPath;
ArchiveFileAttributes(@NonNull Path archiveFile, @NonNull ArchiveEntry entry) {
this(new ArchiveFileAttributesImpl(archiveFile, entry));
ArchiveFileAttributes(@NonNull Path archiveFile, @NonNull ArchiveItem item) {
this(new ArchiveFileAttributesImpl(archiveFile, item));
}
private ArchiveFileAttributes(@NonNull ArchiveFileAttributesImpl attributes) {
super(attributes);
mEntryName = attributes.getEntryName();
mItemPath = attributes.getItemPath();
}
@NonNull
public String getEntryName() {
return mEntryName;
public String getItemPath() {
return mItemPath;
}
@ -49,13 +48,13 @@ public class ArchiveFileAttributes extends ParcelablePosixFileAttributes {
protected ArchiveFileAttributes(Parcel in) {
super(in);
mEntryName = in.readString();
mItemPath = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(mEntryName);
dest.writeString(mItemPath);
}
}

View File

@ -5,11 +5,6 @@
package me.zhanghai.android.files.provider.archive;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.dump.DumpArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.threeten.bp.Instant;
import java.util.Set;
@ -18,9 +13,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java8.nio.file.Path;
import java8.nio.file.attribute.FileTime;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveItem;
import me.zhanghai.android.files.provider.common.ByteString;
import me.zhanghai.android.files.provider.common.PosixFileAttributes;
import me.zhanghai.android.files.provider.common.PosixFileMode;
import me.zhanghai.android.files.provider.common.PosixFileModeBit;
import me.zhanghai.android.files.provider.common.PosixFileType;
import me.zhanghai.android.files.provider.common.PosixFileTypes;
@ -32,41 +27,34 @@ class ArchiveFileAttributesImpl implements PosixFileAttributes {
@NonNull
private final Path mArchiveFile;
@NonNull
private final ArchiveEntry mEntry;
private final ArchiveItem mItem;
ArchiveFileAttributesImpl(@NonNull Path archiveFile, @NonNull ArchiveEntry entry) {
ArchiveFileAttributesImpl(@NonNull Path archiveFile, @NonNull ArchiveItem item) {
mArchiveFile = archiveFile;
mEntry = entry;
mItem = item;
}
@NonNull
public String getEntryName() {
return mEntry.getName();
public String getItemPath() {
return mItem.getPath();
}
@NonNull
@Override
public FileTime lastModifiedTime() {
return FileTime.from(Instant.ofEpochMilli(mEntry.getLastModifiedDate().getTime()));
Instant lastModifiedTime = mItem.getLastModifiedTime();
if (lastModifiedTime == null) {
lastModifiedTime = Instant.EPOCH;
}
return FileTime.from(lastModifiedTime);
}
@NonNull
@Override
public FileTime lastAccessTime() {
if (mEntry instanceof DumpArchiveEntry) {
DumpArchiveEntry dumpEntry = (DumpArchiveEntry) mEntry;
return FileTime.from(Instant.ofEpochMilli(dumpEntry.getAccessTime().getTime()));
} else if (mEntry instanceof SevenZArchiveEntry) {
SevenZArchiveEntry sevenZEntry = (SevenZArchiveEntry) mEntry;
if (sevenZEntry.getHasAccessDate()) {
return FileTime.from(Instant.ofEpochMilli(sevenZEntry.getAccessDate().getTime()));
}
} else if (mEntry instanceof TarArchiveEntry) {
TarArchiveEntry tarEntry = (TarArchiveEntry) mEntry;
Long atimeMillis = getTarEntryTimeMillis(tarEntry, "atime");
if (atimeMillis != null) {
return FileTime.from(Instant.ofEpochMilli(atimeMillis));
}
Instant lastAccessTime = mItem.getLastAccessTime();
if (lastAccessTime != null) {
return FileTime.from(lastAccessTime);
}
return lastModifiedTime();
}
@ -74,103 +62,48 @@ class ArchiveFileAttributesImpl implements PosixFileAttributes {
@NonNull
@Override
public FileTime creationTime() {
if (mEntry instanceof DumpArchiveEntry) {
DumpArchiveEntry dumpEntry = (DumpArchiveEntry) mEntry;
return FileTime.from(Instant.ofEpochMilli(dumpEntry.getCreationTime().getTime()));
} else if (mEntry instanceof SevenZArchiveEntry) {
SevenZArchiveEntry sevenZEntry = (SevenZArchiveEntry) mEntry;
if (sevenZEntry.getHasCreationDate()) {
return FileTime.from(Instant.ofEpochMilli(sevenZEntry.getCreationDate().getTime()));
}
} else if (mEntry instanceof TarArchiveEntry) {
TarArchiveEntry tarEntry = (TarArchiveEntry) mEntry;
Long ctimeMillis = getTarEntryTimeMillis(tarEntry, "ctime");
if (ctimeMillis != null) {
return FileTime.from(Instant.ofEpochMilli(ctimeMillis));
}
Instant creationTime = mItem.getCreationTime();
if (creationTime != null) {
return FileTime.from(creationTime);
}
return lastModifiedTime();
}
@Nullable
private static Long getTarEntryTimeMillis(@NonNull TarArchiveEntry entry,
@NonNull String name) {
String atime = entry.getExtraPaxHeader(name);
if (atime == null) {
return null;
}
double atimeSeconds;
try {
atimeSeconds = Double.parseDouble(atime);
} catch (NumberFormatException e) {
e.printStackTrace();
return null;
}
return (long) (atimeSeconds * 1000);
}
@NonNull
public PosixFileType type() {
return PosixFileTypes.fromArchiveEntry(mEntry);
return PosixFileTypes.fromArchiveItem(mItem);
}
@Override
public long size() {
return mEntry.getSize();
return mItem.getSize();
}
@NonNull
@Override
public ArchiveFileKey fileKey() {
return new ArchiveFileKey(mArchiveFile, mEntry.getName());
return new ArchiveFileKey(mArchiveFile, mItem.getPath());
}
@Nullable
@Override
public PosixUser owner() {
if (mEntry instanceof DumpArchiveEntry) {
DumpArchiveEntry dumpEntry = (DumpArchiveEntry) mEntry;
//noinspection deprecation
return new PosixUser(dumpEntry.getUserId(), null);
} else if (mEntry instanceof TarArchiveEntry) {
TarArchiveEntry tarEntry = (TarArchiveEntry) mEntry;
//noinspection deprecation
return new PosixUser(tarEntry.getUserId(), ByteString.fromStringOrNull(
tarEntry.getUserName()));
}
String owner = mItem.getOwner();
// TODO: Where is ID?
return null;
}
@Nullable
@Override
public PosixGroup group() {
if (mEntry instanceof DumpArchiveEntry) {
DumpArchiveEntry dumpEntry = (DumpArchiveEntry) mEntry;
//noinspection deprecation
return new PosixGroup(dumpEntry.getGroupId(), null);
} else if (mEntry instanceof TarArchiveEntry) {
TarArchiveEntry tarEntry = (TarArchiveEntry) mEntry;
//noinspection deprecation
return new PosixGroup(tarEntry.getGroupId(), ByteString.fromStringOrNull(
tarEntry.getGroupName()));
}
String group = mItem.getGroup();
// TODO: Where is ID?
return null;
}
@Nullable
public Set<PosixFileModeBit> mode() {
if (mEntry instanceof DumpArchiveEntry) {
DumpArchiveEntry dumpEntry = (DumpArchiveEntry) mEntry;
return PosixFileMode.fromInt(dumpEntry.getMode());
} else if (mEntry instanceof TarArchiveEntry) {
TarArchiveEntry tarEntry = (TarArchiveEntry) mEntry;
return PosixFileMode.fromInt(tarEntry.getMode());
} else if (mEntry instanceof ZipArchiveEntry) {
ZipArchiveEntry zipEntry = (ZipArchiveEntry) mEntry;
if (zipEntry.getPlatform() == ZipArchiveEntry.PLATFORM_UNIX) {
return PosixFileMode.fromInt(zipEntry.getUnixMode());
}
}
// TODO
return null;
}

View File

@ -8,14 +8,13 @@ package me.zhanghai.android.files.provider.archive;
import android.os.Parcel;
import android.os.Parcelable;
import org.apache.commons.compress.archivers.ArchiveEntry;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import androidx.annotation.NonNull;
import java8.nio.file.Path;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveItem;
import me.zhanghai.android.files.provider.common.ByteString;
import me.zhanghai.android.files.provider.common.ByteStringListPathFactory;
import me.zhanghai.android.files.provider.remote.RemoteFileSystemException;
@ -65,8 +64,8 @@ class ArchiveFileSystem extends RootableFileSystem implements ByteStringListPath
}
@NonNull
ArchiveEntry getEntryAsLocal(@NonNull Path path) throws IOException {
return getLocalFileSystem().getEntry(path);
ArchiveItem getItemAsLocal(@NonNull Path path) throws IOException {
return getLocalFileSystem().getItem(path);
}
@NonNull

View File

@ -5,8 +5,6 @@
package me.zhanghai.android.files.provider.archive;
import org.apache.commons.compress.archivers.ArchiveEntry;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
@ -18,6 +16,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java8.nio.file.Path;
import java8.nio.file.attribute.FileTime;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveItem;
import me.zhanghai.android.files.provider.common.ByteString;
import me.zhanghai.android.files.provider.common.PosixFileAttributeView;
import me.zhanghai.android.files.provider.common.PosixFileModeBit;
@ -48,8 +47,8 @@ public class LocalArchiveFileAttributeView implements PosixFileAttributeView {
@Override
public ArchiveFileAttributes readAttributes() throws IOException {
ArchiveFileSystem fileSystem = (ArchiveFileSystem) mPath.getFileSystem();
ArchiveEntry entry = fileSystem.getEntryAsLocal(mPath);
return new ArchiveFileAttributes(fileSystem.getArchiveFile(), entry);
ArchiveItem item = fileSystem.getItemAsLocal(mPath);
return new ArchiveFileAttributes(fileSystem.getArchiveFile(), item);
}
@Override

View File

@ -7,8 +7,6 @@ package me.zhanghai.android.files.provider.archive;
import android.util.Pair;
import org.apache.commons.compress.archivers.ArchiveEntry;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@ -28,7 +26,8 @@ import java8.nio.file.PathMatcher;
import java8.nio.file.WatchService;
import java8.nio.file.attribute.UserPrincipalLookupService;
import java8.nio.file.spi.FileSystemProvider;
import me.zhanghai.android.files.provider.archive.archiver.ArchiveReader;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveItem;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveReader;
import me.zhanghai.android.files.provider.common.ByteString;
import me.zhanghai.android.files.provider.common.ByteStringBuilder;
import me.zhanghai.android.files.provider.common.ByteStringListPathFactory;
@ -59,7 +58,7 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
private boolean mNeedRefresh = true;
private Map<Path, ArchiveEntry> mEntries;
private Map<Path, ArchiveItem> mItems;
private Map<Path, List<Path>> mTree;
@ -94,21 +93,21 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
}
@NonNull
ArchiveEntry getEntry(@NonNull Path path) throws IOException {
ArchiveItem getItem(@NonNull Path path) throws IOException {
synchronized (mLock) {
ensureEntriesLocked();
return getEntryLocked(path);
return getItemLocked(path);
}
}
@NonNull
private ArchiveEntry getEntryLocked(@NonNull Path path) throws IOException {
private ArchiveItem getItemLocked(@NonNull Path path) throws IOException {
synchronized (mLock) {
ArchiveEntry entry = mEntries.get(path);
if (entry == null) {
ArchiveItem item = mItems.get(path);
if (item == null) {
throw new NoSuchFileException(path.toString());
}
return entry;
return item;
}
}
@ -116,8 +115,8 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
InputStream newInputStream(@NonNull Path file) throws IOException {
synchronized (mLock) {
ensureEntriesLocked();
ArchiveEntry entry = getEntryLocked(file);
return ArchiveReader.newInputStream(mArchiveFile, entry);
ArchiveItem item = getItemLocked(file);
return ArchiveReader.newInputStream(mArchiveFile, item);
}
}
@ -125,8 +124,8 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
List<Path> getDirectoryChildren(@NonNull Path directory) throws IOException {
synchronized (mLock) {
ensureEntriesLocked();
ArchiveEntry entry = getEntryLocked(directory);
if (!entry.isDirectory()) {
ArchiveItem item = getItemLocked(directory);
if (!item.isDirectory()) {
throw new NotDirectoryException(directory.toString());
}
return mTree.get(directory);
@ -137,8 +136,8 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
String readSymbolicLink(@NonNull Path link) throws IOException {
synchronized (mLock) {
ensureEntriesLocked();
ArchiveEntry entry = getEntryLocked(link);
return ArchiveReader.readSymbolicLink(mArchiveFile, entry);
ArchiveItem item = getItemLocked(link);
return ArchiveReader.readSymbolicLink(mArchiveFile, item);
}
}
@ -156,10 +155,10 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
throw new ClosedFileSystemException();
}
if (mNeedRefresh) {
Pair<Map<Path, ArchiveEntry>, Map<Path, List<Path>>> entriesAndTree =
ArchiveReader.readEntries(mArchiveFile, mRootDirectory);
mEntries = entriesAndTree.first;
mTree = entriesAndTree.second;
Pair<Map<Path, ArchiveItem>, Map<Path, List<Path>>> itemsAndTree =
ArchiveReader.readItems(mArchiveFile, mRootDirectory);
mItems = itemsAndTree.first;
mTree = itemsAndTree.second;
mNeedRefresh = false;
}
}
@ -178,7 +177,7 @@ class LocalArchiveFileSystem extends FileSystem implements ByteStringListPathFac
}
mProvider.removeFileSystem(mFileSystem);
mNeedRefresh = false;
mEntries = null;
mItems = null;
mTree = null;
mOpen = false;
}

View File

@ -314,7 +314,7 @@ class LocalArchiveFileSystemProvider extends FileSystemProvider implements Searc
Objects.requireNonNull(modes);
AccessModes accessModes = AccessModes.fromArray(modes);
ArchiveFileSystem fileSystem = (ArchiveFileSystem) path.getFileSystem();
fileSystem.getEntryAsLocal(path);
fileSystem.getItemAsLocal(path);
if (accessModes.hasWrite() || accessModes.hasExecute()) {
throw new AccessDeniedException(path.toString());
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding;
import org.threeten.bp.Instant;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public interface ArchiveItem {
@NonNull
String getPath();
long getSize();
long getPackedSize();
boolean isDirectory();
int getAttributes();
@Nullable
Instant getCreationTime();
@Nullable
Instant getLastAccessTime();
@Nullable
Instant getLastModifiedTime();
boolean isEncrypted();
boolean isCommented();
@Nullable
Integer getCrc();
@Nullable
String getMethod();
@Nullable
String getHostOs();
@Nullable
String getOwner();
@Nullable
String getGroup();
@Nullable
String getComment();
int getIndex();
@Nullable
String getLink();
}

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Pair;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.threeten.bp.Instant;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import eu.chainfire.librootjava.RootJava;
import java8.nio.channels.SeekableByteChannel;
import java8.nio.charset.StandardCharsets;
import java8.nio.file.NoSuchFileException;
import java8.nio.file.NotLinkException;
import java8.nio.file.Path;
import me.zhanghai.android.files.BuildConfig;
import me.zhanghai.android.files.R;
import me.zhanghai.android.files.compat.MapCompat;
import me.zhanghai.android.files.provider.common.IsDirectoryException;
import me.zhanghai.android.files.provider.common.MoreFiles;
import me.zhanghai.android.files.provider.common.PosixFileType;
import me.zhanghai.android.files.provider.common.PosixFileTypes;
import me.zhanghai.android.files.provider.root.RootUtils;
import me.zhanghai.android.files.settings.Settings;
import me.zhanghai.android.files.util.IoUtils;
public class ArchiveReader {
private ArchiveReader() {}
@NonNull
public static Pair<Map<Path, ArchiveItem>, Map<Path, List<Path>>> readItems(
@NonNull Path file, @NonNull Path rootPath) throws IOException {
Map<Path, ArchiveItem> items = new HashMap<>();
List<ArchiveItem> rawItems = readItems(file);
for (ArchiveItem item : rawItems) {
Path path = rootPath.resolve(item.getPath());
// Normalize an absolute path to prevent path traversal attack.
if (!path.isAbsolute()) {
throw new AssertionError("Path must be absolute: " + path.toString());
}
if (path.getNameCount() > 0) {
path = path.normalize();
if (path.getNameCount() == 0) {
// Don't allow a path to become the root path only after normalization.
continue;
}
}
MapCompat.putIfAbsent(items, path, item);
}
if (!items.containsKey(rootPath)) {
items.put(rootPath, new DirectoryArchiveItem("/"));
}
Map<Path, List<Path>> tree = new HashMap<>();
tree.put(rootPath, new ArrayList<>());
List<Path> paths = new ArrayList<>(items.keySet());
for (Path path : paths) {
while (true) {
Path parentPath = path.getParent();
if (parentPath == null) {
break;
}
ArchiveItem item = items.get(path);
if (item.isDirectory()) {
MapCompat.computeIfAbsent(tree, path, _1 -> new ArrayList<>());
}
MapCompat.computeIfAbsent(tree, parentPath, _1 -> new ArrayList<>())
.add(path);
if (items.containsKey(parentPath)) {
break;
}
items.put(parentPath, new DirectoryArchiveItem(parentPath.toString()));
path = parentPath;
}
}
return new Pair<>(items, tree);
}
@NonNull
private static List<ArchiveItem> readItems(@NonNull Path file) throws IOException {
try (SeekableByteChannel channel = MoreFiles.newByteChannel(file);
IInArchive archive = SevenZip.openInArchive(null, new ByteChannelInStream(channel))) {
List<ArchiveItem> items = new ArrayList<>();
for (ISimpleInArchiveItem item : archive.getSimpleInterface().getArchiveItems()) {
items.add(new SimpleArchiveItem(archive, item));
}
return items;
}
}
@NonNull
private static String getArchiveFileNameEncoding() {
if (RootUtils.isRunningAsRoot()) {
try {
Context context = RootJava.getPackageContext(BuildConfig.APPLICATION_ID);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
context);
String key = context.getString(R.string.pref_key_archive_file_name_encoding);
String defaultValue = context.getString(
R.string.pref_default_value_archive_file_name_encoding);
return sharedPreferences.getString(key, defaultValue);
} catch (Exception e) {
e.printStackTrace();
return StandardCharsets.UTF_8.name();
}
} else {
return Settings.ARCHIVE_FILE_NAME_ENCODING.getValue();
}
}
@NonNull
public static InputStream newInputStream(@NonNull Path file, @NonNull ArchiveItem item)
throws IOException {
if (item.isDirectory()) {
throw new IsDirectoryException(file.toString());
}
try (SeekableByteChannel channel = MoreFiles.newByteChannel(file);
IInArchive archive = SevenZip.openInArchive(null, new ByteChannelInStream(channel))) {
for (ISimpleInArchiveItem simpleItem : archive.getSimpleInterface().getArchiveItems()) {
if (Objects.equals(simpleItem.getPath(), item.getPath())) {
PipedInputStream inputStream = new PipedInputStream();
// TODO: Might deadlock if on same thread?
simpleItem.extractSlow(new StreamOutStream(new PipedOutputStream(inputStream)));
return inputStream;
}
}
}
throw new NoSuchFileException(file.toString());
}
@NonNull
public static String readSymbolicLink(@NonNull Path file, @NonNull ArchiveItem entry)
throws IOException {
if (!isSymbolicLink(entry)) {
throw new NotLinkException(file.toString());
}
try (InputStream inputStream = newInputStream(file, entry)) {
return IoUtils.inputStreamToString(inputStream, StandardCharsets.UTF_8);
}
}
private static boolean isSymbolicLink(@NonNull ArchiveItem item) {
return PosixFileTypes.fromArchiveItem(item) == PosixFileType.SYMBOLIC_LINK;
}
private static class DirectoryArchiveItem implements ArchiveItem {
@NonNull
private String mPath;
public DirectoryArchiveItem(@NonNull String path) {
mPath = path;
}
@NonNull
@Override
public String getPath() {
return mPath;
}
@Override
public long getSize() {
return 0;
}
@Override
public long getPackedSize() {
return 0;
}
@Override
public boolean isDirectory() {
return true;
}
@Override
public int getAttributes() {
return 0;
}
@Nullable
@Override
public Instant getCreationTime() {
return null;
}
@Nullable
@Override
public Instant getLastAccessTime() {
return null;
}
@Nullable
@Override
public Instant getLastModifiedTime() {
return null;
}
@Override
public boolean isEncrypted() {
return false;
}
@Override
public boolean isCommented() {
return false;
}
@Nullable
@Override
public Integer getCrc() {
return null;
}
@Nullable
@Override
public String getMethod() {
return null;
}
@Nullable
@Override
public String getHostOs() {
return null;
}
@Nullable
@Override
public String getOwner() {
return null;
}
@Nullable
@Override
public String getGroup() {
return null;
}
@Nullable
@Override
public String getComment() {
return null;
}
@Override
public int getIndex() {
return -1;
}
@Nullable
@Override
public String getLink() {
return null;
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding;
import net.sf.sevenzipjbinding.IInStream;
import net.sf.sevenzipjbinding.SevenZipException;
import java.io.IOException;
import java.nio.ByteBuffer;
import androidx.annotation.NonNull;
import java8.nio.channels.SeekableByteChannel;
public class ByteChannelInStream implements IInStream {
@NonNull
private final SeekableByteChannel mChannel;
public ByteChannelInStream(@NonNull SeekableByteChannel channel) {
mChannel = channel;
}
@Override
public synchronized long seek(long offset, int seekOrigin) throws SevenZipException {
try {
switch (seekOrigin) {
case SEEK_SET:
mChannel.position(offset);
break;
case SEEK_CUR:
mChannel.position(mChannel.position() + offset);
break;
case SEEK_END:
mChannel.position(mChannel.size() + offset);
break;
default:
throw new AssertionError(seekOrigin);
}
return mChannel.position();
} catch (IOException e) {
throw new SevenZipException(e);
}
}
@Override
public synchronized int read(@NonNull byte[] data) throws SevenZipException {
try {
return mChannel.read(ByteBuffer.wrap(data));
} catch (IOException e) {
throw new SevenZipException(e);
}
}
@Override
public synchronized void close() throws IOException {
mChannel.close();
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding;
import net.sf.sevenzipjbinding.IOutStream;
import net.sf.sevenzipjbinding.SevenZipException;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import androidx.annotation.NonNull;
import java8.nio.channels.SeekableByteChannel;
public class ByteChannelOutStream implements Closeable, IOutStream {
@NonNull
private final SeekableByteChannel mChannel;
public ByteChannelOutStream(@NonNull SeekableByteChannel channel) {
mChannel = channel;
}
@Override
public synchronized long seek(long offset, int seekOrigin) throws SevenZipException {
try {
switch (seekOrigin) {
case SEEK_SET:
mChannel.position(offset);
break;
case SEEK_CUR:
mChannel.position(mChannel.position() + offset);
break;
case SEEK_END:
mChannel.position(mChannel.size() + offset);
break;
default:
throw new AssertionError(seekOrigin);
}
return mChannel.position();
} catch (IOException e) {
throw new SevenZipException(e);
}
}
@Override
public synchronized void setSize(long newSize) throws SevenZipException {
try {
if (newSize <= mChannel.size()) {
mChannel.truncate(newSize);
} else {
long oldPosition = mChannel.position();
mChannel.position(newSize - 1);
try {
mChannel.write(ByteBuffer.wrap(new byte[] {0}));
} finally {
mChannel.position(oldPosition);
}
}
} catch (IOException e) {
throw new SevenZipException(e);
}
}
@Override
public synchronized int write(@NonNull byte[] data) throws SevenZipException {
try {
return mChannel.write(ByteBuffer.wrap(data));
} catch (IOException e) {
throw new SevenZipException(e);
}
}
@Override
public synchronized void close() throws IOException {
mChannel.close();
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding;
import android.text.TextUtils;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.threeten.bp.Instant;
import java.util.Date;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class SimpleArchiveItem implements ArchiveItem {
@NonNull
private final String mPath;
private final long mSize;
private final long mPackedSize;
private final boolean mIsDirectory;
private final int mAttributes;
@Nullable
private final Instant mCreationTime;
@Nullable
private final Instant mLastAccessTime;
@Nullable
private final Instant mLastModifiedTime;
private final boolean mEncrypted;
private final boolean mCommented;
@Nullable
private final Integer mCrc;
@Nullable
private final String mMethod;
@Nullable
private final String mHostOs;
@Nullable
private final String mOwner;
@Nullable
private final String mGroup;
@Nullable
private final String mComment;
private final int mIndex;
@Nullable
private final String mLink;
public SimpleArchiveItem(@NonNull IInArchive archive, @NonNull ISimpleInArchiveItem item)
throws SevenZipException {
String path = item.getPath();
if (TextUtils.isEmpty(path)) {
throw new SevenZipException("ISimpleInArchiveItem.getPath() returned null or empty");
}
mPath = path;
Long size = item.getSize();
mSize = size != null && size >= 0 ? size : 0;
Long packedSize = item.getPackedSize();
mPackedSize = packedSize != null && packedSize >= 0 ? packedSize : 0;
mIsDirectory = item.isFolder() || path.endsWith("/");
Integer attributes = item.getAttributes();
mAttributes = attributes != null ? attributes : 0;
mCreationTime = toInstant(item.getCreationTime());
mLastAccessTime = toInstant(item.getLastAccessTime());
mLastModifiedTime = toInstant(item.getLastWriteTime());
mEncrypted = item.isEncrypted();
Boolean commented = item.isCommented();
mCommented = commented != null ? commented : false;
mCrc = item.getCRC();
mMethod = item.getMethod();
mHostOs = item.getHostOS();
mOwner = item.getUser();
mGroup = item.getGroup();
mComment = item.getComment();
mIndex = item.getItemIndex();
mLink = archive.getStringProperty(mIndex, PropID.LINK);
}
@Nullable
private static Instant toInstant(@Nullable Date date) {
if (date != null) {
long millis = date.getTime();
if (millis > 0) {
return Instant.ofEpochMilli(millis);
}
}
return null;
}
@Override
@NonNull
public String getPath() {
return mPath;
}
@Override
public long getSize() {
return mSize;
}
@Override
public long getPackedSize() {
return mPackedSize;
}
@Override
public boolean isDirectory() {
return mIsDirectory;
}
@Override
public int getAttributes() {
return mAttributes;
}
@Override
@Nullable
public Instant getCreationTime() {
return mCreationTime;
}
@Override
@Nullable
public Instant getLastAccessTime() {
return mLastAccessTime;
}
@Override
@Nullable
public Instant getLastModifiedTime() {
return mLastModifiedTime;
}
@Override
public boolean isEncrypted() {
return mEncrypted;
}
@Override
public boolean isCommented() {
return mCommented;
}
@Override
@Nullable
public Integer getCrc() {
return mCrc;
}
@Override
@Nullable
public String getMethod() {
return mMethod;
}
@Override
@Nullable
public String getHostOs() {
return mHostOs;
}
@Override
@Nullable
public String getOwner() {
return mOwner;
}
@Override
@Nullable
public String getGroup() {
return mGroup;
}
@Override
@Nullable
public String getComment() {
return mComment;
}
@Override
public int getIndex() {
return mIndex;
}
@Nullable
public String getLink() {
return mLink;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding;
import net.sf.sevenzipjbinding.ISequentialOutStream;
import net.sf.sevenzipjbinding.SevenZipException;
import java.io.IOException;
import java.io.OutputStream;
import androidx.annotation.NonNull;
public class StreamOutStream implements ISequentialOutStream {
@NonNull
private final OutputStream mStream;
public StreamOutStream(@NonNull OutputStream stream) {
mStream = stream;
}
@Override
public synchronized int write(@NonNull byte[] bytes) throws SevenZipException {
try {
mStream.write(bytes);
} catch (IOException e) {
throw new SevenZipException(e);
}
return bytes.length;
}
}

View File

@ -7,6 +7,8 @@ package me.zhanghai.android.files.provider.common;
import android.system.OsConstants;
import net.sf.sevenzipjbinding.PropID;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.dump.DumpArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
@ -14,6 +16,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import androidx.annotation.NonNull;
import java8.nio.file.attribute.BasicFileAttributes;
import me.zhanghai.android.files.provider.archive.archiver_sevenzipjbinding.ArchiveItem;
public class PosixFileTypes {
@ -90,6 +93,27 @@ public class PosixFileTypes {
}
}
@NonNull
public static PosixFileType fromArchiveItem(@NonNull ArchiveItem item) {
if (item.getLink() != null) {
return PosixFileType.SYMBOLIC_LINK;
}
int attributes = item.getAttributes();
if ((attributes & PropID.AttributesBitMask.FILE_ATTRIBUTE_DIRECTORY)
== PropID.AttributesBitMask.FILE_ATTRIBUTE_DIRECTORY) {
return PosixFileType.DIRECTORY;
}
if ((attributes & PropID.AttributesBitMask.FILE_ATTRIBUTE_UNIX_EXTENSION)
== PropID.AttributesBitMask.FILE_ATTRIBUTE_UNIX_EXTENSION) {
int mode = attributes >> 16;
PosixFileType type = fromMode(mode);
if (type != PosixFileType.UNKNOWN) {
return type;
}
}
return PosixFileType.REGULAR_FILE;
}
@NonNull
public static PosixFileType fromMode(int mode) {
return OsConstants.S_ISDIR(mode) ? PosixFileType.DIRECTORY

View File

@ -83,11 +83,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/file_properties_basic_archive_entry"
android:text="@string/file_properties_basic_archive_item"
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
<TextView
android:id="@+id/archive_entry"
android:id="@+id/archive_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"

View File

@ -219,7 +219,7 @@
<string name="file_properties_basic_last_modification_time">最后修改</string>
<string name="file_properties_basic_parent_directory">父文件夹</string>
<string name="file_properties_basic_archive_file">归档文件</string>
<string name="file_properties_basic_archive_entry">归档条目</string>
<string name="file_properties_basic_archive_item">归档条目</string>
<string name="file_properties_basic_free_space">可用空间</string>
<string name="file_properties_permissions">权限</string>
<string name="file_properties_permissions_owner">所有者</string>

View File

@ -219,7 +219,7 @@
<string name="file_properties_basic_last_modification_time">最後修改</string>
<string name="file_properties_basic_parent_directory">父資料夾</string>
<string name="file_properties_basic_archive_file">歸檔檔案</string>
<string name="file_properties_basic_archive_entry">歸檔條目</string>
<string name="file_properties_basic_archive_item">歸檔條目</string>
<string name="file_properties_basic_free_space">可用空間</string>
<string name="file_properties_permissions">權限</string>
<string name="file_properties_permissions_owner">所有者</string>

View File

@ -234,7 +234,7 @@
<string name="file_properties_basic_last_modification_time">Last Modified</string>
<string name="file_properties_basic_parent_directory">Parent Folder</string>
<string name="file_properties_basic_archive_file">Archive File</string>
<string name="file_properties_basic_archive_entry">Archive Entry</string>
<string name="file_properties_basic_archive_item">Archive Item</string>
<string name="file_properties_basic_free_space">Free Space</string>
<string name="file_properties_permissions">Permissions</string>
<string name="file_properties_permissions_owner">Owner</string>