/*
* Copyright 2014 Yaroslav Mytkalyk
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.docd.purefm.file;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.docd.purefm.commandline.Command;
import com.docd.purefm.commandline.CommandChmod;
import com.docd.purefm.commandline.CommandDu;
import com.docd.purefm.commandline.CommandLine;
import com.docd.purefm.commandline.CommandListContents;
import com.docd.purefm.commandline.CommandListFile;
import com.docd.purefm.commandline.CommandMkdir;
import com.docd.purefm.commandline.CommandMkdirs;
import com.docd.purefm.commandline.CommandMove;
import com.docd.purefm.commandline.CommandReadlink;
import com.docd.purefm.commandline.CommandRemove;
import com.docd.purefm.commandline.CommandTouch;
import com.docd.purefm.commandline.Constants;
import com.docd.purefm.settings.Settings;
import com.docd.purefm.utils.MimeTypes;
import com.docd.purefm.utils.PFMTextUtils;
public final class CommandLineFile implements GenericFile,
Comparable<GenericFile> {
private static final long serialVersionUID = -8173533665283968040L;
private static final int LS_PERMISSIONS = 0;
// private static final int LS_NUMLINKS = 1;
private static final int LS_USER = 2;
private static final int LS_GROUP = 3;
private static final int LS_FILE_SIZE = 4;
// private static final int LS_DAY_OF_WEEK = 5;
private static final int LS_MONTH = 6;
private static final int LS_DAY_OF_MONTH = 7;
private static final int LS_TIME = 8;
private static final int LS_YEAR = 9;
private static final int LS_FILE = 10;
@NonNull
private final File mFile;
@Nullable
private final String mCanonicalPath;
private Permissions mPermissions;
private long mLength;
private long mLastmod;
private boolean mExists;
private int mOwner;
private int mGroup;
private String mMimeType;
private boolean mIsSymlink;
private boolean mIsDirectory;
/**
* Creates a new CommandLineFile using File and canonical path from {@link CommandReadlink}
*
* @param file File
*/
private CommandLineFile(@NonNull final File file) {
this.mFile = file;
this.mCanonicalPath = CommandReadlink.readlink(file.getAbsolutePath());
}
/**
* Creates a new CommandLineFile using file path and canonical path from {@link CommandReadlink}
*
* @param path File path
*/
private CommandLineFile(@NonNull final String path, final boolean noReadlink) {
this.mFile = new File(path);
if (noReadlink) {
String canonicalPath;
try {
canonicalPath = mFile.getCanonicalPath();
} catch (IOException e) {
canonicalPath = null;
}
mCanonicalPath = canonicalPath;
} else {
mCanonicalPath = CommandReadlink.readlink(path);
}
}
/**
* Creates a new CommandLineFile using parent File and file name.
* Canonical path is retrieved from {@link CommandReadlink}
*
* @param parent Parent file
* @param name file name
*/
private CommandLineFile(@NonNull final File parent,
@NonNull final String name) {
this.mFile = new File(parent, name);
this.mCanonicalPath = CommandReadlink.readlink(mFile.getAbsolutePath());
}
/**
* Creates a new CommandLineFile using file path and canonical path from
* {@link java.io.File#getAbsolutePath()}
*
* @param path File path
*/
private CommandLineFile(@NonNull final String path) {
this.mFile = new File(path);
String canonicalPath;
try {
canonicalPath = mFile.getCanonicalPath();
} catch (IOException e) {
canonicalPath = null;
}
this.mCanonicalPath = canonicalPath;
}
/**
* Creates a new CommandLineFile using file path and canonical path
*
* @param path File path
* @param canonicalPath Canonical File path
*/
@SuppressWarnings("NullableProblems")
private CommandLineFile(@NonNull final String path,
@NonNull final String canonicalPath) {
this.mFile = new File(path);
this.mCanonicalPath = canonicalPath;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSymlink() {
return this.mIsSymlink;
}
@NonNull
public static CommandLineFile fromFile(@NonNull final Settings settings,
@NonNull final File file) {
final List<String> res = CommandLine.executeForResult(
new CommandListFile(file, settings));
if (res == null || res.isEmpty()) {
// file not yet exists
return new CommandLineFile(file);
}
return fromLSL(null, res.get(0));
}
@NonNull
public static CommandLineFile fromLSL(@Nullable final File parent,
@NonNull final String line) {
if (line.isEmpty()) {
throw new IllegalArgumentException("Bad ls -lApe output: is empty");
}
final String[] attrs = getAttrs(line);
for (final String attr : attrs) {
if (attr == null) {
throw new IllegalArgumentException("Bad ls -lApe output: attr was null");
}
}
String name = attrs[LS_FILE];
String canonicalPath = null;
// if is symlink then resolve real path
final int index = name.indexOf("->");
if (index != -1) {
canonicalPath = name.substring(index + 3).trim();
name = name.substring(0, index).trim();
}
final CommandLineFile f;
if (parent == null) {
if (canonicalPath != null) {
f = new CommandLineFile(name, canonicalPath);
} else {
f = new CommandLineFile(name);
}
} else {
f = new CommandLineFile(parent, name);
}
init(f, line);
return f;
}
@NonNull
private static String[] getAttrs(String string) {
if (string.length() < 44) {
throw new IllegalArgumentException("Bad ls -lApe output: " + string);
}
final char[] chars = string.toCharArray();
final String[] results = new String[11];
int ind = 0;
final StringBuilder current = new StringBuilder();
Loop: for (int i = 0; i < chars.length; i++) {
switch (chars[i]) {
case ' ':
case '\t':
if (current.length() != 0) {
results[ind] = current.toString();
ind++;
current.setLength(0);
if (ind == 10) {
results[ind] = string.substring(i).trim();
break Loop;
}
}
break;
default:
current.append(chars[i]);
break;
}
}
return results;
}
/**
* Reads parameters from line and applies them to targetFile
*
* @param targetFile CommandLineFile to initialize
* @param line ls -lApe output
*/
private static void init(final CommandLineFile targetFile, final String line) {
if (line.isEmpty()) {
throw new IllegalArgumentException("Bad ls -lApe output: is empty");
}
final String[] attrs = getAttrs(line);
for (final String attr : attrs) {
if (attr == null) {
throw new IllegalArgumentException("Bad ls -lApe output: attr was null");
}
}
init(targetFile, getAttrs(line));
}
/**
* Applies attrs to targetFile
*
* @param targetFile CommandLineFile to initialize
* @param attrs Attributes read from ls -lApe output
*/
private static void init(final CommandLineFile targetFile, String[] attrs) {
final String sourceName = attrs[LS_FILE];
final String perm = attrs[LS_PERMISSIONS];
targetFile.mIsSymlink = perm.charAt(0) == 'l';
targetFile.mIsDirectory = sourceName.endsWith(File.separator);
targetFile.mPermissions = new Permissions(perm);
targetFile.mOwner = Integer.parseInt(attrs[LS_USER]);
targetFile.mGroup = Integer.parseInt(attrs[LS_GROUP]);
final String len = attrs[LS_FILE_SIZE];
if (len != null && !len.isEmpty()) {
targetFile.mLength = Long.parseLong(len);
}
final Calendar c = Calendar.getInstance(Locale.US);
c.set(Calendar.YEAR, Integer.parseInt(attrs[LS_YEAR]));
c.set(Calendar.MONTH, PFMTextUtils.stringMonthToInt(attrs[LS_MONTH]));
c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(attrs[LS_DAY_OF_MONTH]));
final int index1 = attrs[LS_TIME].indexOf(':');
final int index2 = attrs[LS_TIME].lastIndexOf(':');
if (index1 != -1 && index2 != -1) {
c.set(Calendar.HOUR_OF_DAY,
Integer.parseInt(attrs[LS_TIME].substring(0, index1)));
c.set(Calendar.MINUTE,
Integer.parseInt(attrs[LS_TIME].substring(index1 + 1, index2)));
c.set(Calendar.SECOND,
Integer.parseInt(attrs[LS_TIME].substring(index2 + 1)));
}
targetFile.mLastmod = c.getTimeInMillis();
targetFile.mExists = true;
if (!targetFile.mIsDirectory) {
targetFile.mMimeType = MimeTypes.getMimeType(targetFile.mFile);
}
}
private void apply(final CommandLineFile other) {
this.mPermissions = other.mPermissions;
this.mLength = other.mLength;
this.mLastmod = other.mLastmod;
this.mExists = other.mExists;
this.mOwner = other.mOwner;
this.mGroup = other.mGroup;
this.mMimeType = other.mMimeType;
this.mIsSymlink = other.mIsSymlink;
this.mIsDirectory = other.mIsDirectory;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public String getMimeType() {
return this.mMimeType;
}
/**
* {@inheritDoc}
*/
@Override
public boolean exists() {
return this.mExists;
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public File toFile() {
return this.mFile;
}
/**
* {@inheritDoc}
*/
@Override
public boolean delete() {
if (CommandLine.execute(new CommandRemove(this.mFile))) {
this.mExists = false;
this.mIsDirectory = false;
this.mIsSymlink = false;
this.mLastmod = 0;
this.mLength = 0;
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public CommandLineFile[] listFiles() {
final List<String> result = CommandLine.executeForResult(
new CommandListContents(this, Settings.getInstance()));
if (result == null) {
return null;
}
final List<CommandLineFile> res = new ArrayList<>(
result.size());
for (final String f : result) {
try {
res.add(CommandLineFile.fromLSL(mFile, f));
} catch (IllegalArgumentException e) {
//e.printStackTrace();
// not a valid ls -l file line
}
}
final CommandLineFile[] ret = new CommandLineFile[res.size()];
res.toArray(ret);
return ret;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public CommandLineFile[] listFiles(final FileFilter filter) {
if (filter == null) {
return listFiles();
}
final List<String> result = CommandLine.executeForResult(
new CommandListContents(this, Settings.getInstance()));
if (result == null) {
return null;
}
final List<CommandLineFile> res = new ArrayList<>(
result.size());
for (final String f : result) {
try {
final CommandLineFile tmp = CommandLineFile.fromLSL(mFile, f);
if (filter.accept(tmp.toFile())) {
res.add(tmp);
}
} catch (IllegalArgumentException e) {
// not a valid ls -l file line
}
}
final CommandLineFile[] ret = new CommandLineFile[res.size()];
res.toArray(ret);
return ret;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public CommandLineFile[] listFiles(final FilenameFilter filter) {
if (filter == null) {
return listFiles();
}
final List<String> result = CommandLine.executeForResult(
new CommandListContents(this, Settings.getInstance()));
if (result == null) {
return null;
}
final List<CommandLineFile> res = new ArrayList<>(
result.size());
for (String f : result) {
try {
final CommandLineFile tmp = CommandLineFile.fromLSL(mFile, f);
if (filter.accept(mFile, tmp.getName())) {
res.add(tmp);
}
} catch (IllegalArgumentException e) {
// not a valid ls -l file line
}
}
final CommandLineFile[] ret = new CommandLineFile[res.size()];
res.toArray(ret);
return ret;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public CommandLineFile[] listFiles(final GenericFileFilter filter) {
if (filter == null) {
return listFiles();
}
final List<String> result = CommandLine.executeForResult(
new CommandListContents(this, Settings.getInstance()));
if (result == null) {
return null;
}
final List<CommandLineFile> res = new ArrayList<>(
result.size());
for (final String f : result) {
try {
final CommandLineFile tmp = CommandLineFile.fromLSL(mFile, f);
if (filter.accept(tmp)) {
res.add(tmp);
}
} catch (IllegalArgumentException e) {
// not a valid ls -l file line
}
}
final CommandLineFile[] ret = new CommandLineFile[res.size()];
res.toArray(ret);
return ret;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public String[] list() {
final GenericFile[] files = listFiles();
if (files != null) {
final String[] result = new String[files.length];
//noinspection LoopStatementThatDoesntLoop
for (int i = 0; i < result.length; i++) {
result[i] = files[i].getName();
return result;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public long length() {
return this.mLength;
}
/**
* {@inheritDoc}
*/
@Override
public BigInteger lengthTotal() {
if (mIsDirectory) {
return BigInteger.valueOf(CommandDu.du_s(this));
}
return BigInteger.valueOf(mLength);
}
/**
* {@inheritDoc}
*/
@Override
public long lastModified() {
return this.mLastmod;
}
/**
* {@inheritDoc}
*/
@Override
public boolean createNewFile() throws IOException {
if (this.mExists) {
return false;
}
final boolean result = CommandLine.execute(
new CommandTouch(mFile.getAbsolutePath()));
if (result) {
this.apply(CommandLineFile.fromFile(Settings.getInstance(), mFile));
return true;
}
throw new IOException("Could not create file+");
}
/**
* {@inheritDoc}
*/
@Override
public boolean mkdir() {
final boolean result = CommandLine.execute(
new CommandMkdir(mFile.getAbsolutePath()));
if (result) {
this.apply(CommandLineFile.fromFile(Settings.getInstance(), mFile));
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean mkdirs() {
final boolean result = CommandLine.execute(
new CommandMkdirs(mFile.getAbsolutePath()));
if (result) {
this.apply(CommandLineFile.fromFile(Settings.getInstance(), mFile));
}
return result;
}
/**
* Returns true, if this file points to the same location
*
* @param arg0
* File to compare to
* @return true, if this file points to the same location
*/
@Override
public int compareTo(@NonNull final GenericFile arg0) {
return this.mFile.compareTo(arg0.toFile());
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public String getName() {
return this.mFile.getName();
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public String getPath() {
return this.mFile.getPath();
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public String getAbsolutePath() {
return this.mFile.getAbsolutePath();
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public String getCanonicalPath() throws IOException {
if (this.mCanonicalPath != null) {
return this.mCanonicalPath;
}
return this.mFile.getCanonicalPath();
}
@NonNull
@Override
public CommandLineFile getCanonicalFile() throws IOException {
final String canonicalPath = getCanonicalPath();
if (mFile.getAbsolutePath().equals(canonicalPath)) {
return this;
}
return new CommandLineFile(canonicalPath);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("SimplifiableIfStatement")
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof CommandLineFile) {
return ((CommandLineFile) obj).mFile.equals(this.mFile);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return this.mFile.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public long getFreeSpace() {
return this.mFile.getFreeSpace();
}
/**
* {@inheritDoc}
*/
@Override
public long getTotalSpace() {
return this.mFile.getTotalSpace();
}
/**
* {@inheritDoc}
*/
@Override
public String getParent() {
return this.mFile.getParent();
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public CommandLineFile getParentFile() {
final File parentFile = mFile.getParentFile();
if (parentFile == null) {
return null;
}
return CommandLineFile.fromFile(Settings.getInstance(), parentFile);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDirectory() {
return this.mIsDirectory;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isHidden() {
return this.mFile.isHidden() || getName().startsWith(".");
}
/**
* {@inheritDoc}
*/
@Override
public boolean renameTo(@NonNull final GenericFile newName) {
final Command move = new CommandMove(getAbsolutePath(), newName.getAbsolutePath());
final boolean result = CommandLine.execute(move);
if (result) {
this.mExists = false;
this.mIsDirectory = false;
this.mIsSymlink = false;
this.mOwner = 0;
this.mGroup = 0;
}
return result;
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public Permissions getPermissions() {
return this.mPermissions;
}
/**
* {@inheritDoc}
*/
@Override
public boolean applyPermissions(@NonNull final Permissions newPerm) {
if (this.mPermissions.equals(newPerm)) {
return true;
}
final boolean result = CommandLine.execute(new CommandChmod(
mFile.getAbsolutePath(), newPerm));
if (result) {
this.mPermissions = newPerm;
}
return result;
}
/**
* Returns owner id of this file
*
* @return owner id of this file
*/
public int getOwner() {
return this.mOwner;
}
/**
* Returns group id of this file
*
* @return group id of this file
*/
public int getGroup() {
return this.mGroup;
}
/**
* {@inheritDoc}
*/
@Override
public boolean canRead() {
if (!this.mExists) {
return false;
}
if (this.mOwner == Constants.SDCARD_RW ||
this.mOwner == Constants.SDCARD_R ||
this.mOwner == Constants.MEIDA_RW ||
this.mOwner == Constants.MTP) {
return this.mPermissions.ur;
}
if (this.mGroup == Constants.SDCARD_RW ||
this.mGroup == Constants.SDCARD_R ||
this.mGroup == Constants.MEIDA_RW ||
this.mGroup == Constants.MTP) {
return this.mPermissions.gr;
}
return this.mPermissions.or;
}
/**
* {@inheritDoc}
*/
@Override
public boolean canWrite() {
if (!this.mExists) {
return false;
}
if (this.mOwner == Constants.SDCARD_RW ||
this.mOwner == Constants.SDCARD_R || // on some devices it's still writable
this.mOwner == Constants.MEIDA_RW ||
this.mOwner == Constants.MTP) {
return this.mPermissions.uw;
}
if (this.mGroup == Constants.SDCARD_RW ||
this.mGroup == Constants.SDCARD_R || // on some devices it's still writable
this.mGroup == Constants.MEIDA_RW ||
this.mGroup == Constants.MTP) {
return this.mPermissions.gw;
}
return this.mPermissions.ow;
}
/**
* {@inheritDoc}
*/
@Override
public boolean canExecute() {
if (!this.mExists) {
return false;
}
if (this.mOwner == Constants.SDCARD_RW ||
this.mOwner == Constants.SDCARD_R ||
this.mOwner == Constants.MEIDA_RW ||
this.mOwner == Constants.MTP) {
return this.mPermissions.ux;
}
if (this.mGroup == Constants.SDCARD_RW ||
this.mGroup == Constants.SDCARD_R ||
this.mGroup == Constants.MEIDA_RW ||
this.mGroup == Constants.MTP) {
return this.mPermissions.gx;
}
return this.mPermissions.ox;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return getAbsolutePath();
}
}