7 files modified
2 files added
2 files deleted
| | |
| | | * @author Ulrich Hilger, 15. Januar 2024 |
| | | */ |
| | | public class Deflator { |
| | | |
| | | private final String STR_SLASH = "/"; |
| | | |
| | | /* --------------- Ordner packen ----------------- */ |
| | | /** |
| | |
| | | try { |
| | | //String fName = getFileName(e); |
| | | //logger.fine("fName: " + fName); |
| | | if (fName.endsWith(Const.STR_SLASH)) { |
| | | if (fName.endsWith(STR_SLASH)) { |
| | | File dir = new File(base, fName); |
| | | if (dir.isDirectory()) { |
| | | //logger.fine("absPath: " + dir.getAbsolutePath()); |
| | |
| | | * @author Ulrich Hilger, 15. Januar 2024 |
| | | */ |
| | | public class Eraser { |
| | | |
| | | public static final int OP_DELETE = 3; |
| | | |
| | | private final String STR_DOT = "."; |
| | | |
| | | public String deleteFiles(String relPath, List<String> fileNames, String base) { |
| | | String result = null; |
| | | try { |
| | | //logger.fine(fileNames.toString()); |
| | | if (!relPath.startsWith(Const.STR_DOT)) { |
| | | if (!relPath.startsWith(STR_DOT)) { |
| | | File targetDir = new File(base, relPath); // getTargetDir(relPath); |
| | | //logger.fine("targetDir: " + targetDir); |
| | | for (String fileName : fileNames) { |
| | | File targetFile = new File(targetDir, fileName); |
| | | //logger.fine(targetFile.getAbsolutePath()); |
| | | if (targetFile.isDirectory()) { |
| | | CopyMoveVisitor bearbeiter = new CopyMoveVisitor(); |
| | | bearbeiter.setOperation(Const.OP_DELETE); |
| | | FileOpsVisitor bearbeiter = new FileOpsVisitor(); |
| | | bearbeiter.setOperation(OP_DELETE); |
| | | Files.walkFileTree(targetFile.toPath(), bearbeiter); |
| | | } else { |
| | | /* |
| | |
| | | die so heissen, also z.B. alle [Dateiname]*.jpg |
| | | */ |
| | | String fname = targetFile.getName().toLowerCase(); |
| | | if (fname.endsWith(Const.JPEG) |
| | | || fname.endsWith(Const.JPG) |
| | | || fname.endsWith(Const.PNG)) { |
| | | if (fname.endsWith(ImageFileFilter.JPEG) |
| | | || fname.endsWith(ImageFileFilter.JPG) |
| | | || fname.endsWith(ImageFileFilter.PNG)) { |
| | | deleteImgFiles(targetDir, targetFile); |
| | | } else { |
| | | targetFile.delete(); |
| | |
| | | |
| | | private void deleteImgFiles(File targetDir, File targetFile) throws IOException { |
| | | String fnameext = targetFile.getName(); |
| | | int dotpos = fnameext.lastIndexOf(Const.STR_DOT); |
| | | int dotpos = fnameext.lastIndexOf(STR_DOT); |
| | | String fname = fnameext.substring(0, dotpos); |
| | | String ext = fnameext.substring(dotpos); |
| | | //logger.fine("fname: " + fname + ", ext: " + ext); |
New file |
| | |
| | | /* |
| | | fm - File management class library |
| | | Copyright (C) 2024 Ulrich Hilger |
| | | |
| | | This program is free software: you can redistribute it and/or modify |
| | | it under the terms of the GNU Affero General Public License as |
| | | published by the Free Software Foundation, either version 3 of the |
| | | License, or (at your option) any later version. |
| | | |
| | | This program is distributed in the hope that it will be useful, |
| | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| | | GNU Affero General Public License for more details. |
| | | |
| | | You should have received a copy of the GNU Affero General Public License |
| | | along with this program. If not, see <https://www.gnu.org/licenses/>. |
| | | */ |
| | | package de.uhilger.fm; |
| | | |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.nio.file.FileVisitResult; |
| | | import java.nio.file.FileVisitor; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.attribute.BasicFileAttributes; |
| | | |
| | | /** |
| | | * Ein FileVisitor zum Verschieben, Kopieren oder Loeschen ganzer Ordnerstrukturen mit |
| | | * Hilfe der Methode Files.walkFileTree von java.nio. |
| | | * |
| | | * @author Ulrich Hilger |
| | | * @version 1, 14. Mai 2021 |
| | | */ |
| | | public class FileOpsVisitor extends FileHelper implements FileVisitor { |
| | | |
| | | private Path targetDir; |
| | | private int operation; |
| | | |
| | | /** |
| | | * Den Zielordner fuer Kopier- oder Verschiebeoperationen angeben |
| | | * |
| | | * @param targetDir der Zielordner |
| | | */ |
| | | public void setTargetDir(Path targetDir) { |
| | | this.targetDir = targetDir; |
| | | } |
| | | |
| | | /** |
| | | * Die gewuenschte Dateioperation angeben, |
| | | * OP_COPY, OP_MOVE oder OP_DELETE |
| | | * |
| | | * @param op die Dateioperation |
| | | */ |
| | | public void setOperation(int op) { |
| | | this.operation = op; |
| | | } |
| | | |
| | | /** |
| | | * Dafuer sorgen, dass beim Kopieren oder Verschieben kein am Ziel bereits existierender |
| | | * Ordner ueberschrieben wird, indem am Ziel fuer einen bereits existierenden Ordner ein |
| | | * anderer Name mit laufender Nummer erzeugt wird. |
| | | * |
| | | * Invoked for a directory before entries in the directory are visited. If this method |
| | | * returns CONTINUE, then entries in the directory are visited. If this method returns |
| | | * SKIP_SUBTREE or SKIP_SIBLINGS then entries in the directory (and any descendants) |
| | | * will not be visited. |
| | | * |
| | | * @param dir Zielordner |
| | | * @param attrs die gewuenschten Attribute |
| | | * @return gibt stets FileVisitResult.CONTINUE zurueck |
| | | * @throws IOException wenn etwas schief geht |
| | | */ |
| | | @Override |
| | | public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs) |
| | | throws IOException { |
| | | if (operation != Eraser.OP_DELETE) { |
| | | if (dir instanceof Path) { |
| | | Path sourceDir = (Path) dir; |
| | | File destFile = targetDir.resolve(sourceDir.getFileName()).toFile(); |
| | | //logger.fine("sourceDir: " + sourceDir + ", destFile: " + destFile); |
| | | if (destFile.exists()) { |
| | | File newDir = getNewFileName(destFile); |
| | | destFile.renameTo(newDir); |
| | | } |
| | | destFile.mkdir(); |
| | | this.targetDir = destFile.toPath(); |
| | | //logger.fine("targetDir now: " + targetDir.toString()); |
| | | } |
| | | } |
| | | return FileVisitResult.CONTINUE; |
| | | } |
| | | |
| | | /** |
| | | * Fuer jede Datei die gewuenschte Dateioperation ausführen |
| | | * |
| | | * Invoked for a file in a directory. |
| | | * |
| | | * @param file die zu bearbeitende Datei a reference to the file |
| | | * @param attrs the directory's basic attributes |
| | | * @return stets FileVisitResult.CONTINUE |
| | | * @throws IOException wenn etwas schief geht |
| | | */ |
| | | @Override |
| | | public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) throws IOException { |
| | | if(operation != Eraser.OP_DELETE) { |
| | | if (file instanceof Path) { |
| | | Path source = (Path) file; |
| | | File destFile = targetDir.resolve(source.getFileName()).toFile(); |
| | | if (destFile.exists()) { |
| | | destFile = getNewFileName(destFile); |
| | | } |
| | | if (operation == Mover.OP_MOVE) { |
| | | Files.move(source, destFile.toPath()); |
| | | } else if (operation == Mover.OP_COPY) { |
| | | Files.copy(source, destFile.toPath()); |
| | | } |
| | | } |
| | | } else { |
| | | Files.delete((Path) file); |
| | | } |
| | | return FileVisitResult.CONTINUE; |
| | | } |
| | | |
| | | /** |
| | | * Bei diesem Visitor bleibt diese Methode ungenutzt, hier muessten noch Faelle |
| | | * behandelt werden, die zu einem Abbruch fuehren und ggf. ein Rollback realisiert werden. |
| | | * |
| | | * Invoked for a file that could not be visited. This method is invoked if the file's attributes |
| | | * could not be read, the file is a directory that could not be opened, and other reasons. |
| | | * |
| | | * @param file die Datei, bei der es zum Abbruch kam |
| | | * @param exc the I/O exception that prevented the file from being visited |
| | | * @return stets FileVisitResult.CONTINUE |
| | | * @throws IOException wenn etwas schief laeuft |
| | | */ |
| | | @Override |
| | | public FileVisitResult visitFileFailed(Object file, IOException exc) throws IOException { |
| | | return FileVisitResult.CONTINUE; |
| | | } |
| | | |
| | | /** |
| | | * Fuer jede Datei Schritte ausfuehren, wie sie sich nach einer Dateioperation ergeben. |
| | | * Hier wird beim Verschieben von Dateien das Quellverzeichnis geloescht, nachdem es zum |
| | | * Ziel uebertragen wurde. |
| | | * |
| | | * Invoked for a directory after entries in the directory, and all of their descendants, |
| | | * have been visited. This method is also invoked when iteration of the directory completes |
| | | * prematurely (by a visitFile method returning SKIP_SIBLINGS, or an I/O error when |
| | | * iterating over the directory). |
| | | * |
| | | * @param dir der fertig durchlaufene Quellordner |
| | | * @param exc null if the iteration of the directory completes without an error; otherwise |
| | | * the I/O exception that caused the iteration of the directory to complete prematurely |
| | | * @return |
| | | * @throws IOException |
| | | */ |
| | | @Override |
| | | public FileVisitResult postVisitDirectory(Object dir, IOException exc) throws IOException { |
| | | if (operation != Eraser.OP_DELETE) { |
| | | if (dir instanceof Path) { |
| | | Path finishedDir = (Path) dir; |
| | | targetDir = targetDir.getParent(); |
| | | if(operation == Mover.OP_MOVE) { |
| | | //logger.fine("delete " + finishedDir.toString()); |
| | | Files.delete(finishedDir); |
| | | } |
| | | } |
| | | //logger.fine("targetDir now: " + targetDir.toString()); |
| | | } else { |
| | | Files.delete((Path) dir); |
| | | } |
| | | return FileVisitResult.CONTINUE; |
| | | } |
| | | |
| | | } |
| | |
| | | * @version 1, 12. Mai 2021 |
| | | */ |
| | | public class ImageFileFilter implements FileFilter { |
| | | |
| | | public static final String JPG = ".jpg"; |
| | | public static final String JPEG = ".jpeg"; |
| | | public static final String PNG = ".png"; |
| | | |
| | | public static final String B64 = "_b64"; // Base64-Encoded |
| | | |
| | | public static final String TN = "_tn"; // 120 |
| | | public static final String KL = "_kl"; // 240 |
| | | public static final String SM = "_sm"; // 500 |
| | | public static final String MT = "_mt"; // 700 |
| | | public static final String GR = "_gr"; // 1200 |
| | | |
| | | @Override |
| | | public boolean accept(File pathname) { |
| | | boolean pass = true; |
| | | String fname = pathname.getName().toLowerCase(); |
| | | if(fname.endsWith(Const.JPEG) || |
| | | fname.endsWith(Const.JPG) || fname.endsWith(Const.PNG)) { |
| | | if(fname.contains(Const.GR) || fname.contains(Const.KL) || |
| | | fname.contains(Const.MT) || fname.contains(Const.SM) || |
| | | fname.contains(Const.TN) || fname.contains(Const.B64)) { |
| | | if(fname.endsWith(JPEG) || |
| | | fname.endsWith(JPG) || fname.endsWith(PNG)) { |
| | | if(fname.contains(GR) || fname.contains(KL) || |
| | | fname.contains(MT) || fname.contains(SM) || |
| | | fname.contains(TN) || fname.contains(B64)) { |
| | | pass = false; |
| | | } |
| | | } |
| | |
| | | |
| | | public static final String STR_DOT = "."; |
| | | |
| | | /** |
| | | * |
| | | * @param fName Name und relativer Pfad des Ordners, dessen Inhalt aufgelistet werden soll |
| | | * @param ctxPath Kontext Pfad zur Bildung des URL, der auf die Miniaturansicht verweist |
| | | * (koennte evtl. im Client gebildet werden, hier dann nur Mini-Dateiname zurueckgeben) |
| | | * @param base Basisverzeichnis, gegen das der relative Pfad aufgeloest werden soll |
| | | * @return die Dateiliste als JSON String |
| | | * @throws IOException |
| | | */ |
| | | public String liste(String fName, String ctxPath, String base/*, String path*/) throws IOException { |
| | | File[] files = new File(base, fName).listFiles(new ImageFileFilter()); |
| | | if (files != null && files.length > 0) { |
| | |
| | | datei.setTyp(Datei.TYP_DATEI); |
| | | } |
| | | String lowerName = dateiName.toLowerCase(); |
| | | if (lowerName.endsWith(Const.JPEG) |
| | | || lowerName.endsWith(Const.JPG) |
| | | || lowerName.endsWith(Const.PNG)) { |
| | | if (lowerName.endsWith(ImageFileFilter.JPEG) |
| | | || lowerName.endsWith(ImageFileFilter.JPG) |
| | | || lowerName.endsWith(ImageFileFilter.PNG)) { |
| | | datei.setBild(true); |
| | | String ext = dateiName.substring(dateiName.lastIndexOf(STR_DOT)); |
| | | String ohneExt = dateiName.substring(0, dateiName.lastIndexOf(STR_DOT)); |
| | | datei.setMiniurl(ctxPath + /*"/" + */ fName + ohneExt + Const.TN + ext); |
| | | datei.setMiniurl(ctxPath + /*"/" + */ fName + ohneExt + ImageFileFilter.TN + ext); |
| | | //buildImgSrc(file, datei, ohneExt, ext); |
| | | } |
| | | liste.add(datei); |
| | |
| | | import java.nio.file.Path; |
| | | |
| | | /** |
| | | * Eine Klasse mit Methoden zum Kopieren und Verschieben von Dateien |
| | | * Die Klasse Mover verschiebt und kopiert Dateien und Ordner |
| | | * |
| | | * Handhabung von Bilddateien: |
| | | * |
| | | * Fuer jede Datei mit Endung jpg, jpeg und png werden alle Varianten wie zum Beispiel |
| | | * dateiname.jpg, dateiname_kl.jpg, dateiname_gr.jpg, dateiname_gr_b64.jpg usw. |
| | | * beruecksichtigt. |
| | | * |
| | | * @author Ulrich Hilger, 15. Janaur 2024 |
| | | */ |
| | | public class Mover extends FileHelper { |
| | | |
| | | |
| | | public String copyOrMoveFiles(String fromPath, String toPath, String[] fileNames, |
| | | public static final int OP_COPY = 1; |
| | | public static final int OP_MOVE = 2; |
| | | |
| | | /** |
| | | * Dateien und Ordner verschieben oder kopieren |
| | | * |
| | | * @param fromPath der Pfad zur Quelle der Verschiebe- oder Kopieraktion |
| | | * @param toPath der Pfad zum Ziel der Verschiebe- oder Kopieraktion |
| | | * @param fileNames die Liste der Dateien und Ordner, die verschoben oder kopiert werden sollen |
| | | * @param operation die gewuenschte Dateioperation, OP_COPY oder OP_MOVE |
| | | * @param base der Basispfad, gegen den fromPath und toPath aufgeloest werden sollen |
| | | * @throws IOException wenn etwas schief geht |
| | | */ |
| | | public void copyOrMoveFiles(String fromPath, String toPath, String[] fileNames, |
| | | int operation, String base) throws IOException { |
| | | String result = null; |
| | | //String result = null; |
| | | File srcDir = new File(base, fromPath); |
| | | File targetDir = new File(base, toPath); |
| | | for (String fileName : fileNames) { |
| | |
| | | //logger.fine("srcFile: " + srcFile); |
| | | if (srcFile.isDirectory()) { |
| | | //logger.fine("srcFile is directory."); |
| | | CopyMoveVisitor bearbeiter = new CopyMoveVisitor(); |
| | | FileOpsVisitor bearbeiter = new FileOpsVisitor(); |
| | | bearbeiter.setTargetDir(targetDir.toPath()); |
| | | bearbeiter.setOperation(operation); |
| | | Files.walkFileTree(srcFile.toPath(), bearbeiter); |
| | |
| | | if (destFile.exists()) { |
| | | destFile = getNewFileName(destFile); |
| | | } |
| | | if (operation == Const.OP_MOVE) { |
| | | if (operation == OP_MOVE) { |
| | | String fname = srcFile.getName().toLowerCase(); |
| | | if (fname.endsWith(Const.JPEG) |
| | | || fname.endsWith(Const.JPG) |
| | | || fname.endsWith(Const.PNG)) { |
| | | moveImgFilesToDirectory(srcFile, srcDir, targetDir, false); |
| | | if (fname.endsWith(ImageFileFilter.JPEG) |
| | | || fname.endsWith(ImageFileFilter.JPG) |
| | | || fname.endsWith(ImageFileFilter.PNG)) { |
| | | moveImgFilesToDirectory(srcFile, srcDir, targetDir/*, false*/); |
| | | } else { |
| | | Files.move(source, destFile.toPath()); |
| | | } |
| | |
| | | } |
| | | } |
| | | } |
| | | return result; |
| | | //return result; |
| | | } |
| | | |
| | | private void moveImgFilesToDirectory(File srcFile, File srcDir, File targetDir, |
| | | boolean createDestDir) throws IOException { |
| | | /** |
| | | * Eine Bilddatei mit allen Varianten verschieben oder kopieren |
| | | * |
| | | * Fuer jede Datei mit Endung jpg, jpeg und png werden alle Varianten wie zum Beispiel |
| | | * dateiname.jpg, dateiname_kl.jpg, dateiname_gr.jpg, dateiname_gr_b64.jpg usw. |
| | | * beruecksichtigt, also dateiname*.jpg. |
| | | * |
| | | * @param srcFile die Bilddatei, deren Varianten beruecksichtigt werden sollen |
| | | * @param srcDir der Herkunftsort |
| | | * @param targetDir der Zielort |
| | | * @throws IOException wenn etwas schief geht |
| | | */ |
| | | private void moveImgFilesToDirectory(File srcFile, File srcDir, File targetDir/*, |
| | | boolean createDestDir*/) throws IOException { |
| | | String fnameext = srcFile.getName(); |
| | | int dotpos = fnameext.lastIndexOf("."); |
| | | String fname = fnameext.substring(0, dotpos); |
| | |
| | | |
| | | public static final String STR_DOT = "."; |
| | | |
| | | public String umbenennen(HttpExchange exchange, String relPfad, String neuerName, File file) throws IOException { |
| | | public String umbenennen(/*HttpExchange exchange, */String relPfad, String neuerName, File file) |
| | | throws IOException { |
| | | File neueDatei; |
| | | //String relPfad = helper.getFileName(exchange); |
| | | //File file = new File(exchange.getHttpContext().getAttributes().get(FileHandler.ATTR_FILE_BASE).toString(), relPfad); |
| | | String fname = file.getName().toLowerCase(); |
| | | if(fname.endsWith(Const.JPEG) || fname.endsWith(Const.JPG) || fname.endsWith(Const.PNG)) { |
| | | if(fname.endsWith(ImageFileFilter.JPEG) || fname.endsWith(ImageFileFilter.JPG) || |
| | | fname.endsWith(ImageFileFilter.PNG)) { |
| | | neueDatei = renameImgFiles(file.getParentFile(), file, neuerName); |
| | | } else { |
| | | neueDatei = new File(file.getParentFile(), neuerName); |
| | |
| | | String ext = fnameext.substring(dotpos); |
| | | //logger.fine("fname: " + fname + ", ext: " + ext); |
| | | |
| | | DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir.toPath(), fname + "*" + ext); //"*.{txt,doc,pdf,ppt}" |
| | | DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir.toPath(), fname + "*" + ext); |
| | | for (Path path : stream) { |
| | | //logger.fine(path.getFileName().toString()); |
| | | alt = path.getFileName().toString(); |
| | | //logger.fine("alt: " + alt); |
| | | if(alt.contains(Const.TN)) { |
| | | neu = newfname + Const.TN + newext; |
| | | } else if (alt.contains(Const.KL)) { |
| | | neu = newfname + Const.KL + newext; |
| | | } else if(alt.contains(Const.GR)) { |
| | | neu = newfname + Const.GR + newext; |
| | | } else if(alt.contains(Const.MT)) { |
| | | neu = newfname + Const.MT + newext; |
| | | } else if(alt.contains(Const.SM)) { |
| | | neu = newfname + Const.SM + newext; |
| | | if(alt.contains(ImageFileFilter.TN)) { |
| | | neu = newfname + ImageFileFilter.TN + newext; |
| | | } else if (alt.contains(ImageFileFilter.KL)) { |
| | | neu = newfname + ImageFileFilter.KL + newext; |
| | | } else if(alt.contains(ImageFileFilter.GR)) { |
| | | neu = newfname + ImageFileFilter.GR + newext; |
| | | } else if(alt.contains(ImageFileFilter.MT)) { |
| | | neu = newfname + ImageFileFilter.MT + newext; |
| | | } else if(alt.contains(ImageFileFilter.SM)) { |
| | | neu = newfname + ImageFileFilter.SM + newext; |
| | | } else { |
| | | neu = newName; |
| | | } |
New file |
| | |
| | | { |
| | | "pfad": "/h2/cms/www/bilder/", |
| | | "dateien": [{ |
| | | "name": ".DS_Store", |
| | | "typ": "datei", |
| | | "typKlasse": "icon-doc-inv", |
| | | "bild": false |
| | | }, { |
| | | "name": "000022420019-d.jpg", |
| | | "typ": "datei", |
| | | "typKlasse": "icon-doc-inv", |
| | | "bild": true, |
| | | "miniurl": "/h2/cms/www/bilder/000022420019-d_tn.jpg" |
| | | }, { |
| | | "name": "000039350014-1920.jpg", |
| | | "typ": "datei", |
| | | "typKlasse": "icon-doc-inv", |
| | | "bild": true, |
| | | "miniurl": "/h2/cms/www/bilder/000039350014-1920_tn.jpg" |
| | | }, { |
| | | "name": "38036_006-029-1920-r.jpg", |
| | | "typ": "datei", |
| | | "typKlasse": "icon-doc-inv", |
| | | "bild": true, |
| | | "miniurl": "/h2/cms/www/bilder/38036_006-029-1920-r_tn.jpg" |
| | | }] |
| | | } |
| | |
| | | /** |
| | | * Klassen fuer das Dateimanagement. |
| | | * Klassen fuer das Dateimanagement mit java.nio.file. |
| | | * |
| | | * Die folgenden Funktionen sind enthalten: |
| | | * |
| | | * <pre> |
| | | * Ordnerinhalt auflisten: |
| | | * Lister().liste(ordnerName, ctx, basisOrdner) |
| | | * |
| | | * TODO: JSON-Liste und Miniurl sowie Handhabung von Bilddateien ueberpruefen |
| | | * |
| | | * Datei speichern: |
| | | * Writer.speichern(file, content) |
| | | * |
| | | * Dateien und Ordner loeschen: |
| | | * Eraser.deleteFiles(relPfad, dateiname, basis) |
| | | * |
| | | * Kopieren von Dateien und Ordnern: |
| | | * Mover().copyOrMoveFiles(quelle, ziel, dateiNamen, op, base) |
| | | * |
| | | * Verschieben von Dateien und Ordnern: |
| | | * Mover().copyOrMoveFiles(quelle, ziel, dateiNamen, op, base) |
| | | * |
| | | * Duplizieren einer Datei: |
| | | * Duplicator().duplizieren(base, fileName) |
| | | * |
| | | * Umbenennen einer Datei oder eines Ordners: |
| | | * Renamer().umbenennen(exchange, fileName, params[1], file) |
| | | * |
| | | * Packen eines Ordners: |
| | | * Deflator().packFolder(fileName, path, base) |
| | | * |
| | | * Entpacken einer ZIP-Datei: |
| | | * Inflator().extractZipfile(fileName, path, base) |
| | | * </pre> |
| | | */ |
| | | package de.uhilger.fm; |