/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.aesh.extensions.ls;
import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.PosixFileAttributes;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import org.jboss.aesh.cl.Arguments;
import org.jboss.aesh.cl.CommandDefinition;
import org.jboss.aesh.cl.Option;
import org.jboss.aesh.comparators.PosixFileNameComparator;
import org.jboss.aesh.console.Config;
import org.jboss.aesh.console.command.Command;
import org.jboss.aesh.console.command.CommandResult;
import org.jboss.aesh.console.command.invocation.CommandInvocation;
import org.jboss.aesh.extensions.grep.AeshPosixFilePermissions;
import org.jboss.aesh.filters.file.FileAndDirectoryNoDotNamesFilter;
import org.jboss.aesh.parser.Parser;
import org.jboss.aesh.terminal.Color;
import org.jboss.aesh.terminal.Shell;
import org.jboss.aesh.terminal.TerminalColor;
import org.jboss.aesh.terminal.TerminalString;
import org.jboss.aesh.util.PathResolver;
/**
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
@CommandDefinition(name = "ls", description = "[OPTION]... [FILE]...\n" +
"List information about the FILEs (the current directory by default).\n" +
"Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.\n")
public class Ls implements Command<CommandInvocation> {
@Option(shortName = 'H', name = "help", hasValue = false,
description = "display this help and exit")
private boolean help;
@Option(shortName = 'a', name = "all", hasValue = false,
description = "do not ignore entries starting with .")
private boolean all;
@Option(shortName = 'd', name = "directory", hasValue = false,
description = "list directory entries instead of contents, and do not dereference symbolic links")
private boolean directory;
@Option(shortName = 'l', name = "longlisting", hasValue = false,
description = "use a long listing format")
private boolean longListing;
@Option(shortName = 's', name = "size", hasValue = false,
description = "print the allocated size of each file, in blocks")
private boolean size;
@Option(shortName = 'h', name ="human-readable", hasValue = false,
description = "with -l, print sizes in human readable format (e.g., 1K 234M 2G)")
private boolean humanReadable;
@Arguments
private List<File> arguments;
private static final char SPACE = ' ';
private static final Class<? extends BasicFileAttributes> fileAttributes =
Config.isOSPOSIXCompatible() ? PosixFileAttributes.class : DosFileAttributes.class;
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM dd HH:mm");
private static final DateFormat OLD_FILE_DATE_FORMAT = new SimpleDateFormat("MMM dd yyyy");
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(".#");
private static final TerminalColor DIRECTORY_COLOR = new TerminalColor(Color.BLUE, Color.DEFAULT, Color.Intensity.BRIGHT);
private static final TerminalColor SYMBOLIC_LINK_COLOR = new TerminalColor(Color.CYAN, Color.DEFAULT, Color.Intensity.BRIGHT);
public Ls() {
DECIMAL_FORMAT.setRoundingMode(RoundingMode.UP);
}
@Override
public CommandResult execute(CommandInvocation commandInvocation) throws IOException {
//just display help and return
if(help) {
commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("ls"));
return CommandResult.SUCCESS;
}
if(arguments == null) {
arguments = new ArrayList<>(1);
arguments.add(commandInvocation.getAeshContext().getCurrentWorkingDirectory());
}
int counter = 0;
for(File file : arguments) {
if(counter > 0) {
commandInvocation.getShell().out().println(Config.getLineSeparator()+file.getName()+":");
}
for(File f : PathResolver.resolvePath(file, commandInvocation.getAeshContext().getCurrentWorkingDirectory())) {
if(f.isDirectory())
displayDirectory(f, commandInvocation.getShell());
else if(f.isFile())
displayFile(f,commandInvocation.getShell());
else if(!f.exists()) {
commandInvocation.getShell().out().println("ls: cannot access "+
f.toString()+": No such file or directory");
}
}
counter++;
}
return CommandResult.SUCCESS;
}
private void displayDirectory(File input, Shell shell) {
File[] files;
if (all) {
// add "." and ".." to list of files
File[] fileArray = input.listFiles();
files = Arrays.copyOf(fileArray, fileArray.length + 2);
files[fileArray.length] = new File(input, ".");
files[fileArray.length + 1] = new File(input, "..");
} else {
files = input.listFiles(new FileAndDirectoryNoDotNamesFilter());
}
Arrays.sort(files, new PosixFileComparator());
if (longListing) {
shell.out().print(displayLongListing(files));
} else {
shell.out().print(Parser.formatDisplayCompactListTerminalString(
formatFileList(files),
shell.getSize().getWidth()));
}
}
private List<TerminalString> formatFileList(File[] fileList) {
ArrayList<TerminalString> list = new ArrayList<TerminalString>(fileList.length);
for(File file : fileList) {
if (Files.isSymbolicLink(file.toPath())) {
list.add(new TerminalString(file.getName(), SYMBOLIC_LINK_COLOR));
} else if (file.isDirectory()) {
list.add(new TerminalString(file.getName(), DIRECTORY_COLOR));
} else {
list.add(new TerminalString(file.getName()));
}
}
return list;
}
private void displayFile(File input, Shell shell) {
if(longListing) {
shell.out().print(displayLongListing(new File[]{input}));
}
else {
shell.out().print(Parser.formatDisplayListTerminalString(
formatFileList(new File[]{input}), shell.getSize().getHeight(), shell.getSize().getWidth()));
}
}
private String displayLongListing(File[] files) {
StringGroup access = new StringGroup(files.length);
StringGroup size = new StringGroup(files.length);
StringGroup owner = new StringGroup(files.length);
StringGroup group = new StringGroup(files.length);
StringGroup modified = new StringGroup(files.length);
try {
int counter = 0;
for(File file : files) {
BasicFileAttributes attr = Files.readAttributes(file.toPath(), fileAttributes, LinkOption.NOFOLLOW_LINKS);
if (Config.isOSPOSIXCompatible()) {
access.addString(AeshPosixFilePermissions.toString(((PosixFileAttributes) attr)), counter);
} else {
access.addString("", counter);
}
size.addString(makeSizeReadable(attr.size()), counter);
if (Config.isOSPOSIXCompatible()) {
owner.addString(((PosixFileAttributes) attr).owner().getName(), counter);
group.addString(((PosixFileAttributes) attr).group().getName(), counter);
} else {
owner.addString("", counter);
group.addString("", counter);
}
// show year instead of time when file wasn't changed in actual year
Date lastModifiedTime = new Date(attr.lastModifiedTime().toMillis());
Calendar lastModifiedCalendar = Calendar.getInstance();
lastModifiedCalendar.setTime(lastModifiedTime);
Calendar nowCalendar = Calendar.getInstance();
if (lastModifiedCalendar.get(Calendar.YEAR) == nowCalendar.get(Calendar.YEAR)) {
modified.addString(DATE_FORMAT.format(lastModifiedTime), counter);
} else {
modified.addString(OLD_FILE_DATE_FORMAT.format(lastModifiedTime), counter);
}
counter++;
}
}
catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < files.length; i++) {
builder.append(access.getString(i))
.append(owner.getFormattedStringPadRight(i))
.append(group.getFormattedStringPadRight(i))
.append(size.getFormattedString(i))
.append(SPACE)
.append(modified.getString(i))
.append(SPACE);
if (Files.isSymbolicLink(files[i].toPath())) {
builder.append(new TerminalString(files[i].getName(), SYMBOLIC_LINK_COLOR));
builder.append(" -> ");
try {
builder.append(Files.readSymbolicLink(files[i].toPath()));
} catch (IOException ex) {
ex.printStackTrace(); // this should not happen
}
} else if (files[i].isDirectory()) {
builder.append(new TerminalString(files[i].getName(), DIRECTORY_COLOR));
} else {
builder.append(files[i].getName());
}
builder.append(Config.getLineSeparator());
}
return builder.toString();
}
private String makeSizeReadable(long size) {
if(!humanReadable)
return String.valueOf(size);
else {
if(size < 10000)
return String.valueOf(size);
else if(size < 10000000) //K
return DECIMAL_FORMAT.format((double) size/1024)+"K";
else if(size < 1000000000) //M
return DECIMAL_FORMAT.format((double) size/(1048576))+"M";
else
return DECIMAL_FORMAT.format((double) size/(1048576*1014))+"G";
}
}
class PosixFileComparator implements Comparator<File> {
private PosixFileNameComparator posixFileNameComparator = new PosixFileNameComparator();
@Override
public int compare(File o1, File o2) {
return posixFileNameComparator.compare(o1.getName(), o2.getName());
}
}
}