/* Copyright 2009 Albrecht Weinert, Bochum, Germany (a-weinert.de)
* All rights reserved.
*
* This file is part of Frame4J
* ( frame4j.de https://weinert-automation.de/software/frame4j/ )
*
* Frame4J is made available under the terms of the
* Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/ or as text in
https://weinert-automation.de/java/docs/frame4j/de/frame4j/doc-files/epl.txt
* within the source distribution
*/
package de.frame4j;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import de.frame4j.io.FileCriteria;
import de.frame4j.io.FileHelper;
import de.frame4j.io.FileService;
import de.frame4j.io.FileVisitor;
import de.frame4j.util.App;
import de.frame4j.util.AppBase;
import de.frame4j.util.MinDoc;
import de.frame4j.util.Prop;
import de.frame4j.text.TextHelper;
/** Delete files.
*
* The application Era deletes files and directories.
*
* Multiple files can be specified in the command line or by a list (text)
* file preferably generated by {@link FS} and edited afterwards. The file
* specifications allow so called wild cards as well as type lists:
* ..\tmp\+.j+
* ..\tmp\class;bak;tmp;obj;map
*
* Options stand for
* - unconditional delete all files specified without prior asking (-y)
* - if asking do it by a dialogue window instead of by console interaction
* (-g -graf),
* - Recursively descend to sub-directories (-r, -recursion)
* or not (-nr),
* - specify an age range for files to be deleted
* (-since, -til, -days -daysOld),
* - specify a text file list of the files to be deleted (@listFile)
* containing one file denomination per line,
* - delete the sub-directories involved, if (now) empty
* (-d, -delEmpty / -nd),
* - Allow only relative and only descending file specifications related
* to a work directory explicitly specified (-workDir wdir). This may
* also act as a safety option. That would mean
* uhu\\otto\\x.txt is allowed, but ..\\wdir\\dir\\uhu\\otto\\x.txt,
* D:\\dir\\uhu\\otto\\x.txt \\dir\\uhu\\otto\\x.txt and
* uhu\\..\\uhu\\otto\\x.txt are not.
* - Output some usage help (-? -help)
*
* The ample possibilities for file and directory criteria may (best) be set
* by {@link de.frame4j.util.Prop Prop}erties. See
* {@link FileService FileService} .
* {@link FileService#set(de.frame4j.util.PropMap) set(PropMap)}.
*
* File denominations are (single threaded) processed in the sequence given
* as parameters or list files specified. Option parameters on the other hand
* set this {@link App}lication's modi beforehand independent of their
* position. (As the most simple example -h might appear anywhere, and the
* application does nothing but output the online help.)
* The only exception are the options -r, -recursion switching on the
* recursive descend for all the follows, respectively the options -nr or the
* denomination of a list file (@listFilename) switching off the recursive
* descend for all the follows. Example:
* java Era -v -r build/+.class -nr A+.bat -y
* would delete all .class files in directory build including all of it's
* subdirectories as well as all files A*.bat only in the actual directory.
* For all applies no safety question (-y) and verbose report (-v).
*
* On the list / response file's syntax
*
* One line per file denomination is expected. The first "word" in
* a line is it. The rest is ignored and surrounding white space is stripped.
* If file names contain spaces that congeries has to be put in quotes.
* Lines starting by // (no regarding white space) will be ignored as
* comment lines.
* Recursively descending to subdirectories will be switched of when starting
* to process a list / response file for all that follows (up to the next
* option -r or -recursion). Any file denotation in one line just specifies
* one file either absolutely or relative to the working directory
* specified.
* This seems (or is) a restriction wit regard to hand crafted list files but
* it is the only way to process tool generated list files safely without
* user interaction. (Otherwise by -r -y @toolGeneratedList one would
* kill files, the list generating tool would not have meant to.)
*
* A file denomination in a line may contain wildcards or type lists
* (uhu\html;bak or A*.cl*).
*
* File lists generated by the tool {@link FS} may be directly used as file
* lists for this application {@link Era}.
*
* Hints:
*
* To this application Era
* (source) belongs (as an
* integral part) a .properties file named
* Era.properties.
* Those (base) .properties files normally reside in the same directory as
* the applications's class file, within the deployment .jar.
*
* For denominating files in the command line so called wildcards may be
* used:
* ..\tmp\+.j+
* ..\tmp\class;bak;tmp;obj;map
* Like in the example + may be substituted for *. If you use * or ? the
* parameter has to be put in quotes. Otherwise the expansion mechanism
* of Linux shells will spoil all. No clear signal for Windows here: this
* bug has been meticulously ported ton Windows JREs/JDKs.
*
* ©
* Copyright 1998 - 2004 Albrecht Weinert
*
* @see Prop
* @see App
* @see FileHelper
* @see FileCriteria
* @see FileService
*/
// So far V00.00 (20.06.1998) : new as a FS spin off
// V01.00 (06.12.2001) : Prop, graf, wildc.
// V01.10 (06.04.2002) : relative to working directory
// V01.11 (06.04.2002) : Prop's parse partial changed
// V02.03 (20.05.2003) : Delete non empty directories
// V02.21 (17.12.2004) : internat. AppLangMap etc.
// V.129+ (06.01.2016) : FileHelper
// V.144+ (05.08.2016) : refactored to Frame4J'89 slimline
@MinDoc(
copyright = "Copyright 1998 - 2004, 2009 A. Weinert",
author = "Albrecht Weinert",
version = "V.$Revision: 44 $",
lastModified = "$Date: 2021-05-06 19:43:45 +0200 (Do, 06 Mai 2021) $",
usage = "start as Java application (-? for help)",
purpose = "delete files and / or directories specified also by complex criteria"
) public class Era extends App implements FileVisitor {
/** Era wants only partial parameter parsing by Prop.
*/
@Override public final boolean parsePartial(){ return true; }
//----- Properties, automatic setting by Prop's parameter parsing ----
/** (Sub-) directories not to consider.
*
* If given according to the syntax rules of
* {@link FileCriteria#getExcludeNames() FileCriteria.excludeNames}) the
* specified (sub) directories are excluded from visiting.
*
* default: null
*/
public String omitDirs;
/** Do ask before a delete.
*
* default: true
*/
public boolean askBefore = true;
/** Specification of a (one) base directory for all deletes.
*
* If given all file specifications have to be relative to the directory
* specified (and descending). All absolute filenames (beginning with \ or
* by a drive or computer name) as well as parent directories
* (beginning by ..\) will be rejected as illegal.
*
* default: null = no anchor / base directory.
*/
public String workDir;
boolean relOnly; // workDir != null
// -------------------------------------------------------------------------
/** Delete files.
*
* default: true
*/
public boolean fDel = true;
/** Delete directories (also).
*
* Default: false
*/
public boolean tDel;
/** The file visitor (only) for asking prior to delete.
*/
FileVisitor.Ask askDelete;
boolean dirLdel; // delete visited empty directories
// Book keeping
int noOfFiles; // Number of visited file (and -t directories)
int noOfDelFil; // Number of deleted files
int noOfDelDir; // Number of deleted directories
int noOfRejects; // total number of failed deletes
int noOfErrors; // Number of failed deletes due to exceptions
int noOfNoOpPar; // Number of non option parameters
/** File visit.
*/
@Override public int visit(File aktDatei){
if (isFinito) return 0;
boolean nex = ! aktDatei.exists();
boolean isDir = !nex && aktDatei.isDirectory();
String ifL = FileHelper.infoLine(aktDatei);
boolean ifLaus = false;
if (nex || isTest()) { // non existent or do nothing anyway in test mode
if (verbose )
log.println (ifL);
if (!nex) ++noOfFiles;
return 0;
}
++noOfFiles;
int ans = 1;
if (askBefore) {
ans = askDelete.visit(aktDatei);
ifLaus |= !appBase.askGraf;
}
if (!ifLaus && (ans != 1 || verbose)) {
log.println(ifL);
ifLaus = true;
log.flush();
}
if (ans != 1) {
++noOfRejects;
log.println(prop.valueLang("delrejcd"));
// " ... was not deleted: reject.");
if (ans == -2) finito();
return 0;
}
try {
if (aktDatei.delete()) {
if (isDir)
++noOfDelDir;
else
++noOfDelFil;
if (verbose)
log.println(prop.valueLang("wasdeld"));
///" ... was deleted.");
return 1;
} // if file deleted
if (!ifLaus) log.println(ifL);
++noOfRejects;
log.println(prop.valueLang("cnbdeld"));
// " ... could not be deleted.");
} catch (Exception se) {
if (!ifLaus) log.println(ifL);
++noOfErrors;
log.println(formMessage("wndeldc", se));
//" ... was not deleted:\n" + se + ". .\n");
}
return 0;
} // visit
/** Era's start method.
*
* Exit codes: 0, in case of success and > 0 if not
* @param args command line parameter
*/
public static void main(String[] args){
try {
new Era().go(args);
} catch (Exception e) {
AppBase.exit(e, 14);
}
} // main(String[])
//---------------------------------------------------------------------------
/** Era's working method.
*/
@Override public int doIt(){
if (verbose) {
log.println(twoLineStartMsg());
File logFile = appIO.getLogFile();
if (logFile != null)
log.println(" /// Logfile "
+ FileHelper.pathName(logFile, null, false, false));
if (omitDirs != null)
log.println(" /// excl.Dir " + omitDirs);
} // verbose
workDir = TextHelper.makeFName(workDir, null);
if (workDir != null) { // workDir given
relOnly = true;
if (workDir.charAt(workDir.length() -1 ) != FS)
workDir += FS;
File workDat = FileHelper.getInstance(workDir);
if (!workDat.exists() || !workDat.isDirectory())
return errMeld(12, formMessage("bezknvrz", workDir));
//"base directory " + workDir + " is no directory.");
if (verbose) {
log.println(formMessage("bezvrzmldz",
FileHelper.pathName(workDat, null, false, false)));
}
// " /// Base directory "
// + workDat.pathName(false, false));
} // workDir given
askDelete = askBefore ? appBase.askGraf
? (FileVisitor.Ask) new FileVisitor.AskGrafImpl(
true, prop, this, true)
: new FileVisitor.AskConsImpl(true, prop, log) : null;
FileService dS = new FileService();
dS.set(prop);
dirLdel = dS.isDelEmpty();
if (omitDirs != null) {
omitDirs = omitDirs.trim();
if (omitDirs.length() != 0) {
dS.dirCrit.setExcludeNames(omitDirs);
}
}
FileVisitor dirBes = dirLdel ? isTest() ? (FileVisitor)this
: new FileVisitor() {
@Override public int visit(File aktDatei){
if (isFinito) return 0;
String ifL = verbose ? FileHelper.infoLine(aktDatei) : null;
try {
if (!aktDatei.delete()) return 0;
if (verbose) {
log.println(ifL);
log.println(prop.valueLang("deldemty"));
}
++noOfDelDir;
return 1;
} catch (Exception e) {}
return 0;
} // visit
} : null; // Directory visitor for deleting (if empty)
int anzPar = args.length;
parLoop: for (int i = 0; i < anzPar; ++i) {
String parV = args[i];
// out.println("TEST par[" + i + "]= " + parV);
if (parV == null) continue parLoop;
parV = parV.trim();
int parVl = parV.length();
if (parVl == 0) continue parLoop;
optionCheck: while (parVl >= 2
&& parV.charAt(0) == '-') { // start with -
parV = parV.substring(1);
--parVl;
if (parV.charAt(0) == '-') break optionCheck; // 2 Minus
parV = TextHelper.simpLowerC(parV);
if ("r".equals(parV) || "recursion".equals(parV)) {
dS.setRecursion(true);
dS.filCrit.setAllowDir(false);
continue parLoop;
}
if ("nr".equals(parV)) {
dS.setRecursion(false);
dS.filCrit.setAllowDir(true);
continue parLoop;
}
} // start with -
// to here only by file specification (plain or @...)
if (isTest()) {
log.println (" /// Order : " + parV + " (test only)");
} else if (verbose) {
log.println (formMessage("delord", parV));
/// delord = " /// Order : " + parV);
}
boolean inResponse = false;
String fileName = parV;
BufferedReader bur = null;
++noOfNoOpPar;
if (fileName.charAt(0) == '@') { // @-Parameter
if (parVl < 2)
return errMeld(9, formMessage("wrngaff", ""));
parV = parV.substring(1).trim();
if (parV.length() == 0)
return errMeld(9, formMessage("wrngaff", " "));
if (parV.charAt(0) != '@') try {
bur = new BufferedReader(
new FileReader(parV));
inResponse = true;
dS.setRecursion(false);
} catch (Exception e) {
return errMeld(10, formMessage("resperr", e));
}
} // @-Parameter
fileLoop: do {
if (inResponse) {
if (bur == null) break fileLoop; // just to suppress null warn
try {
fileName = bur.readLine();
if (fileName == null) bur.close();
} catch (Exception e) {
return errMeld(11,
formMessage("resperr", new Object[]{parV, e}));
///"Response-Datei @: " + e);
}
if (fileName == null)
break fileLoop;
fileName = fileName.trim();
int combeg = -1;
int quoted = 0;
if (fileName.length() == 0)
continue fileLoop;
if (fileName.charAt(0) == '\"') { // quoted
if (fileName.length() < 3)
continue fileLoop;
combeg = fileName.indexOf('\"',1);
if (combeg < 2)
continue fileLoop;
quoted = 1;
} // quoted
if (combeg < 0)
combeg = fileName.indexOf("//");
if (combeg < 0)
combeg = fileName.indexOf(' ');
if (combeg == 0)
continue fileLoop;
if (combeg > 0)
fileName = fileName.substring(quoted, combeg).trim();
if (fileName.length() == 0)
continue fileLoop;
} // inResponse
// fileName is a file's name
if (relOnly) { // check if relative and descending
if (fileName.indexOf("\\\\") >= 0
|| fileName.charAt(0) == FS
|| fileName.charAt(0) == '.'
|| fileName.indexOf(':') >= 0
|| fileName.indexOf("..") >= 0 )
return errMeld(13, formMessage("norelfil", fileName));
// "Ne legal relative denomination " + fileName);
fileName = workDir + fileName;
} // check if relative and descending
String path = dS.filCrit.parse(fileName);
dS.filCrit.setAllowDir(tDel);
dS.filCrit.setAllowFile(fDel);
if (isTest()) log.println("\n /// dS : " + dS);
dS.dirVisit(path, null,
this, // file
dirBes, // empty dir
null, // dir
dirBes); // dir after
} while (inResponse); // fileLoop
} // parLoop
if (!isFinito) finito();
return 0;
} // doIt()
private boolean isFinito;
/** End / cancel the application.
*/
void finito(){
isFinito = true;
if (askDelete != null) askDelete.dispose();
if (noOfNoOpPar == 0) {
String tmp = getHelpText();
if (tmp != null) err.println(tmp);
}
if (noOfDelFil > 0 || noOfDelDir > 0 || verbose)
log.println(formMessage("delsums",
new int[]{noOfDelFil, noOfDelDir, noOfFiles}));
if (noOfRejects > 0 || noOfErrors > 0) {
log.println(formMessage("rjerrsum",
new int[]{noOfErrors , noOfRejects}));
//noOfErrors + " errors + "
// + noOfRejects + " rejections occured");
normalExit(noOfErrors > 0 ? 2 : 1);
} // if dirDel
normalExit(0);
} // finito
} // Era (26.04.2003, 17.12.2004, 20.12.2004, 06.01.2016)