Android: How to detect a directory in the assets folder? - java

I'm retrieving files like this
String[] files = assetFiles.list("EngagiaDroid");
How can we know whether it is a file or is a directory?
I want to loop through the directories in the assets folder then copy all of its contents.

I think a more general solution (in case you have subfolders etc.) would be something like this (based on the solution you linked to, I've added it there too):
...
copyFileOrDir("myrootdir");
...
private void copyFileOrDir(String path) {
AssetManager assetManager = this.getAssets();
String assets[] = null;
try {
assets = assetManager.list(path);
if (assets.length == 0) {
copyFile(path);
} else {
String fullPath = "/data/data/" + this.getPackageName() + "/" + path;
File dir = new File(fullPath);
if (!dir.exists())
dir.mkdir();
for (int i = 0; i < assets.length; ++i) {
copyFileOrDir(path + "/" + assets[i]);
}
}
} catch (IOException ex) {
Log.e("tag", "I/O Exception", ex);
}
}
private void copyFile(String filename) {
AssetManager assetManager = this.getAssets();
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(filename);
String newFileName = "/data/data/" + this.getPackageName() + "/" + filename;
out = new FileOutputStream(newFileName);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch (Exception e) {
Log.e("tag", e.getMessage());
}
}

You may use list method of AssetManager.
Any directory in asset should have one file at least, empty directory will be ignored when building your application.
So, to determine if some path is directory, use like this:
AssetManager manager = activity.getAssets();
try {
String[] files = manager.list(path);
if (files.length > 0) {
//directory
} else {
//file
}
} catch (Exception e) {
//not exists.
}

I've discovered this variant:
try {
AssetFileDescriptor desc = getAssets().openFd(path); // Always throws exception: for directories and for files
desc.close(); // Never executes
} catch (Exception e) {
exception_message = e.toString();
}
if (exception_message.endsWith(path)) { // Exception for directory and for file has different message
// Directory
} else {
// File
}
It's a more faster as .list()

The appalling truth is that despite being asked nearly 10 years ago, no simple, elegant, roundly applauded method of determining whether an element in the array returned by AssetManager.list() is a file or a directory has been offered by any answer to date.
So, for example, if an asset directory contains a thousand elements, then seemingly a thousand I/O operations are necessary to isolate the directories.
Nor, for any element, does any native method exist for obtaining its parent directory - vital for something complex like an assets Browser / Picker - where you could end up looking at some seriously ugly code.
boolean isAssetDirectory = !elementName.contains(".");
The lateral approach that worked for me was to assume that any element without a dot (.) in its name was a directory. If the assumption is later proved wrong it can be easily rectified.
Asset files generally exist because you put them there. Deploy naming conventions that distinguish between directories and files.

Another way relying on exceptions:
private void checkAssets(String path, AssetManager assetManager) {
String TAG = "CheckAssets";
String[] fileList;
String text = "";
if (assetManager != null) {
try {
fileList = assetManager.list(path);
} catch (IOException e) {
Log.e(TAG, "Invalid directory path " + path);
return;
}
} else {
fileList = new File(path).list();
}
if (fileList != null && fileList.length > 0) {
for (String pathInFolder : fileList) {
File absolutePath = new File(path, pathInFolder);
boolean isDirectory = true;
try {
if (assetManager.open(absolutePath.getPath()) != null) {
isDirectory = false;
}
} catch (IOException ioe) {
isDirectory = true;
}
text = absolutePath.getAbsolutePath() + (isDirectory ? " is Dir" : " is File");
Log.d(TAG, text);
if (isDirectory) {
checkAssets(absolutePath.getPath(), assetManager);
}
}
} else {
Log.e(TAG, "Invalid directory path " + path);
}
}
and then just call checkAssets("someFolder", getAssets()); or checkAssets("", getAssets()); if you want to check the root assets folder. But be aware that the root assets folder contains also other directories/files (Eg. webkit, images, etc.)

In your particular case, since you retrieved the files through list, you already know that these names exist. This simplifies the problem a lot. You can simply use this:
public static boolean isAssetAFolder(AssetManager assetManager, String assetPath) throws IOException {
// Attempt opening as a file,
try {
InputStream inputStream = assetManager.open(assetPath); inputStream.close();
return false; // A file indeed.
} catch (FileNotFoundException e) {
// We already know this name exists. This is a folder.
return true;
}
}
On the other hand, if you need a generic solution to detect if a certain path both exists and is a folder, you can use this:
public static boolean isAssetAFolder(AssetManager assetManager, String assetPath) throws IOException {
// Attempt opening as a file,
try {
InputStream inputStream = assetManager.open(assetPath); inputStream.close();
return false; // A file indeed.
} catch (FileNotFoundException e) {
// This could be a folder, or this path doesn't exist at all. Further checking needed,
return assetPathExists(assetManager, assetPath);
}
}
// If you are checking a file name "icon.png" inside your assets folder, the assetPath should be "icon.png".
public static boolean assetPathExists(AssetManager assetManager, String assetPath) throws IOException {
// Assume that "" exists by default,
if (assetPath.isEmpty()) return true;
// Reject paths that point outside the assets folder,
if (assetPath.startsWith("..") || assetPath.startsWith("/")) return false;
// For other file/folder paths, we'll search the parent folder,
File fileOrFolder = new File(assetPath);
String parent = ((parent=fileOrFolder.getParent()) != null) ? parent : ""; // Handle null parents.
if (!Arrays.asList(assetManager.list(parent)).contains(fileOrFolder.getName())) return false;
// Getting this far means that the specified assetPath indeed exists. However, we didn't handle files
// with trailing "/". For instance, "icon.png/" shouldn't be considered existing although "icon.png"
// does.
// If the path doesn't end with a "/", we are safe,
if (!assetPath.endsWith("/")) return true;
// Remove the trailing slash,
assetPath = assetPath.substring(0, assetPath.length()-1);
// Attempt opening as a file,
try {
InputStream inputStream = assetManager.open(assetPath); inputStream.close();
return false; // It's indeed a file (like "icon.png"). "icon.png/" shouldn't exist.
} catch (FileNotFoundException e) {
return true; // This is a folder that exists.
}
}
I wrote these for a web server, so I couldn't make assumptions about the shape of the input path. But it can be simplified a bit if you have some rules set. This code returns immediately once it becomes certain of the type of the asset, to avoid the extra processing overhead.

You can also try this, it works for me, since you cannot rely solely on .list()
public static boolean isDirectory(Context context, String path) throws IOException {
//If list returns any entries, than the path is a directory
String[] files = context.getAssets().list(path);
if (files != null && files.length > 0) {
return true;
} else {
try {
//If we can open a stream then the path leads to a file
context.getAssets().open(path);
return false;
} catch (Exception ex) {
//.open() throws exception if it's a directory that you're passing as a parameter
return true;
}
}
}

You can start from Android File

You can check if a File represents a directory using http://developer.android.com/reference/java/io/File.html#isDirectory(). Is that what you mean?

Related

How to duplicate a file given a different name in the same directory in windows

I have been trying to duplicate a file but change the name of it in the same windows directory but I got not luck.
I cant just copy the file in the same directory because of the windows rule that two files cannot have the same name in the same directory.
I am not allowed to copy it to another directory then rename it, and then move it back in the same directory.
And I don't see any helpful implementation in the File.class.
Tried something like that but it didnt work:
File file = new File(filePath);
File copiedFile = new File(filePath);
//then rename the copiedFile and then try to copy it
Files.copy(file, copiedFile);
An initial attempt would be using Path as suitable:
Path file = Paths.get(filePath);
String name = file.getFileName().toString();
String copiedName = name.replaceFirst("(\\.[^\\.]*)?$", "-copy$0");
Path copiedFile = file.resolveSibling(copiedName);
try {
Files.copy(file, copiedFile);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
You could create a new file in the same directory and then just copy the contents of the original file to the duplicate
See: Java read from one file and write into another file using methods
For more info
you can also use this snippet from https://www.journaldev.com/861/java-copy-file
private static void copyFileUsingStream(File source, File dest) throws IOException {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(source);
os = new FileOutputStream(dest);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
} finally {
is.close();
os.close();
}
}
#Pierre his code is perfect, however this is what I use so I won't be able to change the extension:
public static void copyWithDifferentName(File sourceFile, String newFileName) {
if (sourceFile == null || newFileName == null || newFileName.isEmpty()) {
return;
}
String extension = "";
if (sourceFile.getName().split("\\.").length > 1) {
extension = sourceFile.getName().split("\\.")[sourceFile.getName().split("\\.").length - 1];
}
String path = sourceFile.getAbsolutePath();
String newPath = path.substring(0, path.length() - sourceFile.getName().length()) + newFileName;
if (!extension.isEmpty()) {
newPath += "." + extension;
}
try (OutputStream out = new FileOutputStream(newPath)) {
Files.copy(sourceFile.toPath(), out);
} catch (IOException e) {
e.printStackTrace();
}
}

Get hidden files in FTP server

I have the local server mockftpserver, and in the server there are couple of files and they are protected with a prefix '._' and the method to protect from getting those files is the following :
protected String getRealPath(Session session, String path) {
String currentDirectory = (String) session.getAttribute(SessionKeys.CURRENT_DIRECTORY);
String result;
if (path == null) {
result = currentDirectory;
}
else if (getFileSystem().isAbsolute(path)) {
result = path;
}
else {
result = getFileSystem().path(currentDirectory, path);
}
return result.replace("._", "");
}
I tried to list the files in the FTP server I got them but the protected ones like '._passwrd' I was not able to see it.
I used the normal method to get the file list:
boolean login = ftpClient.login("user", "password");
if (login) {
System.out.println("Connection established...");
FTPFile[] files = ftpClient.listFiles();
for (FTPFile file : files) {
if (file.getType() == FTPFile.FILE_TYPE) {
System.out.println("File Name: "
+ file.getName()
+ " File Size: " );
}
}
String[] fil = ftpClient.listNames();
if (files != null && fil.length > 0) {
for (String aFile: fil) {
System.out.println(aFile);
}
}
BufferedReader reader = null;
String firstLine = null;
try {
InputStream stream =
ftpClient.retrieveFileStream("._"+"._passwd");
reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
firstLine = reader.readLine();
} finally {
if (reader != null)
try {
reader.close();
} catch (IOException logOrIgnore) {}
}
}
But thinking that the method will only check the name once, so if I added the ._ once again it should work. Although it did not or I could not apply it in the right way.
i don't know about Java but in Python i solved similar task in the following way:
i used: FTP server, Python 2.7.12, library 'ftplib'
so i show just needed part with comments:
#while customer list not empty
while self.customerDirs:
#create connect to root
self.connection.cwd("/")
#choose customer
customer = self.customerDirs.pop()
try:
#go inside to customer's folder
self.connection.cwd(customer)
#for all folders inside
for dir in self.connection.nlst():
#go inside
self.connection.cwd(dir)
#create empty list
hiddenList = []
#create variable which contains path
pathDir = self.connection.pwd()
#write to list hidden files
self.connection.retrlines("LIST -a", hiddenList.append)
for entry in hiddenList:
#split value and take file name
entrySplit = entry.split(' ')[-1]
#cheсk file name
if entrySplit not in ['.', '..'] and entrySplit.startswith('.'):
#all necessary files are sent to method which will delete it (lool like: /customer/folder/.hidden_file.hid)
self.ftp_delete_file('/'.join([pathDir, entrySplit]))
#return to step up
self.connection.cwd("..")
that all, i hope it will be helpful information
You should set your ftpClient to list hidden Files before listing the files, so:
ftpClient.setListHiddenFiles(true);
FTPFile[] files = ftpClient.listFiles();

Read text files from source folder OR JAR depending on execution runtime

My program displays elements read from a text file. The text files will be stored in a folder found in the package folder containing the .java and .class files so they can be embedded in the jar.
I'm trying to get the application to read the text files properly for both situations
Running from the IDE (Netbeans)
Running from the JAR
Currently I can do point one with no problem, but the code reads using File where as the way I am seeing how to do it with Jars is using InputStream.
The functions which work for the IDE runs
public void loadWidgets() {
ArrayList<String> results = new ArrayList<>();
String dir = new File("").getAbsolutePath() + "/src/Creator/textFiles/widgets/;
System.out.println(dir);
getWidgetFiles(dir, results);
results.stream().forEach((s) -> {
readFile(s); // given a string and it opens the file using a Scanner
});
updateWidgetVariables(); // gui updates
}
public void getWidgetFiles(String dirName, ArrayList<String> filePaths) {
File directory = new File(dirName);
File[] files = directory.listFiles();
for (File file : files) {
if (file.isFile()) {
filePaths.add(file.getName() + "," + file.getAbsolutePath());
} else if (file.isDirectory()) {
getWidgetFiles(file.getAbsolutePath(), filePaths);
}
}
}
So I have a bunch of text files organized by the type of widget it is, so I am running through the /widgets/ directory to find all the text files.
The problem I'm having is how I can go through the directories and files of a Jar? Can they be converted to a file, or read into a string?
I got this code from this question and it can read the files, but I dont know how I can open them using a new Scanner(file); code
CodeSource src = WidgetPanel.class.getProtectionDomain().getCodeSource();
try {
System.out.println("Inside try");
List<String> list = new ArrayList<>();
if (src != null) {
URL jar = src.getLocation();
ZipInputStream zip = new ZipInputStream(jar.openStream());
ZipEntry ze = null;
System.out.println(jar.getPath());
System.out.println(zip.getNextEntry());
while ((ze = zip.getNextEntry()) != null) {
String entryName = ze.getName();
System.out.println("Entry name: " + entryName);
if (entryName.startsWith("Creator/textFiles/widgets") && entryName.endsWith(".txt")) {
list.add(entryName);
System.out.println("Added name: " + entryName);
}
}
list.stream().forEach((s) -> {
readFile(s);
});
updateWidgetVariables();
} else {
System.out.println("Src null");
}
}catch (IOException e) {
System.out.println(e.getMessage());
}
Try and obtain a FileSystem associated with your source; the matter then becomes pretty simple. Here is an illustration of how to read, as text, all files from a given FileSystem:
private static final BiPredicate<Path, BasicFileAttributes> FILES
= (path, attrs) -> attrs.isRegularFile();
private static void readAllFilesAsText(final List<Path> paths)
throws IOException
{
for (final Path path: paths)
try (
final Stream<String> stream = Files.lines(path);
) {
stream.forEach(System.out::println);
}
}
private static List<Path> getAllFilesFromFileSystems(final FileSystem fs,
final String pathPrefix)
{
final Path baseDir = fs.getPath(pathPrefix);
try (
final Stream<Path> files = Files.find(baseDir, Integer.MAX_VALUE,
FILES);
) {
return files.collect(Collectors.toList());
}
}
Why the mix of "old style" for loops and "new style" lambdas: it is because lambdas just don't handle checked exceptions... Which means you have to do it. For a workaround, see here.
But the gist of it here is to be able to create a FileSystem out of your sources, and you can do it; yes, you can read jars as such. See here.
I was able to find a solution using the functions I already had.
I first check to see if the text file can be found normally (Run from an IDE). If a file not found exception occurs, it sets ide = false so it tries to read from the jar.
public void loadWidgets() {
boolean ide = true;
String path = "textFiles/widgets/";
ArrayList<String> results = new ArrayList<>();
String dir = new File("").getAbsolutePath() + "/src/Creator/" + path;
try {
getWidgetFiles(dir, results);
results.stream().forEach((s) -> {
readFile(s);
});
updateWidgetVariables();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
ide = false;
}
if (!ide) {
CodeSource src = WidgetPanel.class.getProtectionDomain().getCodeSource();
try {
List<String> list = new ArrayList<>();
if (src != null) {
URL jar = src.getLocation();
ZipInputStream zip = new ZipInputStream(jar.openStream());
ZipEntry ze = null;
while ((ze = zip.getNextEntry()) != null) {
String entryName = ze.getName();
if (entryName.startsWith("Creator/textFiles/widgets") && entryName.endsWith(".txt")) {
// Wouldnt work until i added the "/" before the entryName
list.add("/"+ entryName);
System.out.println("Added name: " + entryName);
}
}
list.stream().forEach((s) -> {
readJarFile(s);
});
updateWidgetVariables();
} else {
System.out.println("Src null");
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
I then created a new readFile function for reading from a Jar
public void readJarFile(String result) {
String name = result.substring(result.lastIndexOf("/") + 1);
String entireWidget = "";
String line;
ArrayList<String> vars = new ArrayList<>();
int begin, end;
InputStream loc = this.getClass().getResourceAsStream(result);
try (Scanner scan = new Scanner(loc)) {
while (scan.hasNextLine()) {
line = scan.nextLine();
entireWidget += line;
while (line.contains("`%")) {
begin = line.indexOf("`%");
end = line.indexOf("%`") + 2;
vars.add(line.substring(begin, end));
//System.out.println("Variable added: " + line.substring(begin, end));
line = line.substring(end);
}
}
}
System.out.println(name + ": " + entireWidget);
Widget widget = new Widget(name, vars, entireWidget, result);
widgetList.put(name, widget);
}
Most of this answer is thanks to this question

How can I count the number of files in a folder within a JAR?

I've spent a bit of time trying to find a way to count the number of files in a folder within a JAR. I put together several examples of code that served different purposes to make this work. It counts just fine when I run the code through Eclipse but after exporting to a JAR it fails and returns 0. In this case, my folder path I use is just "rules/". I would appreciate any recommendations or samples. Thanks.
public static int countFiles(String folderPath) throws IOException { //Counts the number of files in a specified folder
ClassLoader loader = ToolSet.class.getClassLoader();
InputStream is = loader.getResourceAsStream(folderPath);
try {
byte[] c = new byte[1024];
int count = 0;
int readChars = 0;
boolean empty = true;
while ((readChars = is.read(c)) != -1) {
empty = false;
for (int i = 0; i < readChars; ++i) {
if (c[i] == '\n') {
++count;
}
}
}
return (count == 0 && !empty) ? 1 : count;
} finally {
is.close();
}
}
EDIT:
The following doesn't exactly match my original question but thanks to MadProgrammer I was able to reduce my code and eliminate the need to even count the files. The code blow searches every file in my JAR looking for those that end with ".rules", opens the file, searches the file for a string that matches "searchBox.getText()", appends results, and continues on to the next ".rules" file.
StringBuilder results = new StringBuilder();
int count = 0;
JarFile jf = null;
try {
String path = ToolSet.class.getProtectionDomain().getCodeSource().getLocation().getPath();
String decodedPath = URLDecoder.decode(path, "UTF-8");
jf = new JarFile(new File(decodedPath));
Enumeration<JarEntry> entries = jf.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".rules")) {
String name = entry.getName();
InputStream in = ToolSet.class.getResourceAsStream(name);
InputStreamReader isr = new InputStreamReader(in);
BufferedReader bf = new BufferedReader(isr);
String line;
while ((line = bf.readLine()) != null) {
String lowerText = line.toLowerCase();
if(lowerText.indexOf(searchBox.getText().toLowerCase()) > 0) {
results.append(line + "\n");
count++;
}
}
bf.close();
}
}
} catch (IOException ex) {
try {
jf.close();
} catch (Exception e2) {
}
}
if(count>0) {
logBox.setText(results.toString());
} else {
logBox.setText("No matches could be found");
}
A Jar file is essentially a Zip file with a manifest.
Jar/Zip files don't actually have a concept of directories like disks do. They simply have a list of entries that have names. These names may contain some kind path separator and some entries may actually be marked as directories (and tend not to have any bytes associated with them, merely acting as markers)
If you want to find all the resources within a given path, you're going to have to open the Jar file and inspect it's entries yourself, for example...
JarFile jf = null;
try {
String path = "resources";
jf = new JarFile(new File("dist/ResourceFolderCounter.jar"));
Enumeration<JarEntry> entries = jf.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.isDirectory()) {
String name = entry.getName();
name = name.replace(path + "/", "");
if (!name.contains("/")) {
System.out.println(name);
}
}
}
} catch (IOException ex) {
try {
jf.close();
} catch (Exception e) {
}
}
Now, this requires you to know the name of the Jar file you want to use, this may be problematic, as you may wish to list resources from a number of different Jars...
A better solution would be to generate some kind of "resource lookup" file at build time, which contained all the names of the resources that you might need, maybe even keyed to particular names...
This way you could simple use...
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsInputStream("/resources/MasterResourceList.txt")));
String name = null;
while ((name = br.readLine()) != null) {
URL url = getClass().getResource(name);
}
} finally {
try {
br.close();
} catch (Exception exp) {
}
}
For example...
You could even seed the file with the number of resources ;)
this is a simple solution :
InputStream is = loader.getResourceAsStream(folderPath);
//open zip
ZipInputStream zip = new ZipInputStream(is);
//count number of files
while ((zip.getNextEntry()) != null ) {
UnzipCounter++;
}

Converting a jar-file URI to a File

I need to access a configuration file from within my Jar, so I use:
URL configUrl = Object.class.getResource("/config.xml");
Now I need to convert URL into a File object, because that's what the ConfigurationFile object downstream needs for initialization. When I try this:
new File(configUrl.toURI())
I get:
java.lang.IllegalArgumentException: URI is not hierarchical
When I try this:
new File(Thread.currentThread().getContextClassLoader().getResource("config.xml").getFile())
I get:
File does not exist: 'file:\E:\Apps\jarfile.jar!\config.xml'
NOTE: Unfortunately I must have a File object, on an InputStream.
If file is inside of JAR... you can use getResourceAsStream() and read directly or using URL...
URL urlConfig = Object.class.getResource(CONFIG_FILE);
if (urlConfig == null) {
// throw <error>
}
URLConnection connConfig = urlConfig.openConnection();
InputStream isConfig = connConfig.getInputStream(); // do things
Save content to a temporal File... (wait 1 second... mmm)
public static File doThing(InputStream is) throws IOException {
File tmp = null;
FileOutputStream tmpOs = null;
try {
tmp = File.createTempFile("xml", "tmp");
tmpOs = new FileOutputStream(tmp);
int len = -1;
byte[] b = new byte[4096];
while ((len = is.read(b)) != -1) {
tmpOs.write(b, 0, len);
}
} finally {
try { is.close(); } catch (Exception e) {}
try { tmpOs.close(); } catch (Exception e) {}
}
return tmp;
}
Your question doesn't make sense. A resource may be inside a JAR file, and an item in a JAR file simply is not a file, or a File.
Period.
If you need a File object you are going to have to distribute the item separately from the JAR file.
Have you tried this:
File f;
try {
f = new File(url.toURI());
} catch(URISyntaxException e) {
f = new File(url.getPath());
}

Categories