Wait for completion of file attachment download - java

I tried downloading a file attachment using a variant of the example code given by the official JDA documentation. Afterwards, the downloaded file should be moved to another place.
List<Message.Attachment> attachments = null;
try {
attachments = event.getMessage().getAttachments();
} catch (UnsupportedOperationException ignore) {}
File downloadFile;
if (attachments != null && !attachments.isEmpty()) {
Message.Attachment attachment = attachments.get(0);
downloadFile = new File("./tmp/testfile");
downloadFile.getParentFile().mkdirs();
attachment.downloadToFile(downloadFile)
.thenAccept(file -> System.out.println("Saved attachment"))
.exceptionally(t -> {
t.printStackTrace();
return null;
});
}
...
File renamedFile = new File("./files/movedfiled");
renamedFile.getParentFile().mkdirs();
try {
Files.move(downloadFile.toPath(), renamedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
I already tried to add .complete(Void) after .exceptionally(...) and .complete(File) after .downloadToFile(File). None of these worked.
Most of the time, the moved file has a size of 0 bytes or does not exist at all and the original is still present in the old directory (sometimes the size of the downloaded one is also 0 bytes).
Is there a way to wait for completion of the download and closure after writing to prevent file corruption while moving or is the problem caused by my file system (I'm using an aarch64 GNU/Linux system)?

Message.Attachment#downloadToFile() returns a CompletableFuture. You can use CompletableFuture#join() to wait until it finishes but IIRC this is a blocking action.
Better use CompletableFuture#thenAccept() or CompletableFuture#thenCompose().
attachment.downloadToFile(downloadFile)
.thenAccept(file -> {
// Here goes the code which decides what to do after downloading the file
})
.exceptionally(e -> {
e.printStackTrace();
return null;
});

Related

Wildfly is keeping file using when I do Files.write

I have two method - one to write, second to rename file:
public void writeToFile(File file, String content, boolean isLastLine) {
Optional<File> optionalFile = Optional.ofNullable(file);
if (!isLastLine)
content += System.lineSeparator();
try {
Files.write(
optionalFile.orElseThrow(() -> new RuntimeException("File couldn't be find")).toPath(),
content.getBytes(),
StandardOpenOption.APPEND, StandardOpenOption.SYNC);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void renameFile(File fileToRename, String newFileName) {
Optional<File> optionalFile = Optional.ofNullable(fileToRename);
File finalBikFileName = new File(newFileName);
if (!optionalFile.orElseThrow(() -> new RuntimeException("File couldn't be find or doesn't exist")).renameTo(finalBikFileName)) {
throw new RuntimeException("File couldn't be saved - already exists or some other issues");
}
}
public void renameFile(File fileToRename, String newFileName) {
Optional<File> optionalFile = Optional.ofNullable(fileToRename);
File finalBikFileName = new File(newFileName);
if (!optionalFile.orElseThrow(() -> new RuntimeException("File couldn't be find or doesn't exist")).renameTo(finalBikFileName)) {
throw new RuntimeException("File couldn't be saved - already exists or some other issues");
}
}
This is normal class, in application deployed on wildfly. I tested it in many ways. If I comment the write function then rename function is working proper. But if I first write something to file and then I want to rename then i got "action cannot be completed because the file is open in another program" Also i cant touch this file in windows explorer - i can't rename or delete. What can be a reason? How can I unlock it?
1) Is it different threads (or server requests) that call the writeToFile and the renameFile methods? Or both methods are calling one after other under same thread/request?
2) How much data (content.length I mean) are you writing? Just want to make sure SYNC is done before the RENAME.

After File compression I lost the Filename extension

I am trying to compress my file using the ZipOutPutStream. I tried below code and got the compressed folder with the file. But when I am extracting the folder using 7Zip, the fileName extension is missing.
Also I am unable to extract the folder by normal Extract option provided in Windows.
Below is the code I tried Using Java -8
public void compress(String compressed , String raw) {
Path pCompressed = null;
try {
pCompressed = Files.createFile(Paths.get(compressed));
try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(pCompressed))) {
Path pRaw = Paths.get(raw);
Files.walk(pRaw).filter(path -> !Files.isDirectory(path)).forEach(path -> {
ZipEntry zipEntry = new ZipEntry(pRaw.relativize(path).toString());
try {
zos.putNextEntry(zipEntry);
Files.copy(path, zos);
zos.closeEntry();
} catch (IOException e) {
logger.error("Exception while copying file to compressed Directory: "+e);
}
});
}
catch(IOException ioe) {
logger.error("Exception while compressing the output file: "+ioe);
}
}
catch (IOException e1) {
logger.error("Exception While Path initialization for compressing the file");
}
}
Expecting : After Extraction someFolder/MyFile.csv
I've just tried this code and when I provided it with compressed = "out.zip" and raw = "testdir", it worked fine for me. It produced a zip file containing the contents of testdir. I was then able to extract this with 7Zip and the built in Windows extraction and the files were all present and correct.
My guess is that you have not specified the .zip extension for the output file or that Windows is hiding the extension in the folder view. (I think it does this by default.)

Files.walk identify if file is from subfolder or mainfolder

i finally got my Files.walk working now my question is if there is any way to identify if the file collected to the list comes from a subfolder or mainfolder because there is a delete function for these files but the files from the sub folder the user should not be able to delete.
private static List<FileInfo> listBackupFilesInLocalDir(String localPath, Predicate<String> fileNamePredicate) {
try (Stream<Path> files = Files.walk(Paths.get(localPath))) {
return files.filter(p -> fileNamePredicate.test(p.getFileName().toString()))
.map(p -> new FileInfo(p.getFileName().toString(), p.toFile().length()))
.sorted()
.collect(toList());
} catch (IOException e) {
log.error("Error listing directories", e);
throw new RuntimeException(e);
}
}
This is the function wich find and collects all the files. Is it some sort of filter i need or is it even possible to do what i want?
deleteLocalFile.addClickListener(event -> {
try {
Files.delete(Paths.get(this.localStorage, String.valueOf(localFilesComboBox.getValue())));
} catch (IOException e) {
UI.getCurrent().access(() -> Notification.show(e.getMessage(), Notification.Type.ERROR_MESSAGE));
}
UI.getCurrent().access(() -> {
localFilesComboBox.removeAllItems();
localFilesComboBox.addItems(listBackupFiles());
});
});
The above is the delete method and what i want to is simply like a if(from folder a) {
deny delete
}
or something similar
Okay, so you want to be able to only delete files, and only files, in the main folder, and not files in the sub folders. Thus you need a list of files in the main folder. You can do this by checking the URL of the FileInfo objects from the result of your listBackupFilesInLocalDir method. This can be done in the following manner:
public ArrayList<FileInfo> filesInMainFolder(string mainPath,
ArrayList<FileInfo> files) {
ArrayList<FileInfo> res = new ArrayList<FileInfo>();
for (FileInfo info : files) {
String url = info.getUrl().toString();
// Get the path of the File for which we have file information
url = url.substring(0, url.lastIndexOf('/'));
// Is file in the main folder
if (url.compareTo(mainPath) == 0 && info.isDirectory() == false) {
res.add(info);
}
}
return res;
}
The method should be fairly easy to follow. And option I have not include here is the getUrl() method on URLs because I am not 100% certain how it works. If it gets you the directory path, use that instead and drop the conversion to string of the url and simply use info.getUrl().getPath()
To know at which depth you are relative to localPath, you could count the number of elements in the path. Something like this:
int mainFolderDepth = Paths.get(localPath).toRealPath().getNameCount();
//in your stream
int folderDepth = p.toRealPath().getNameCount();
if (! Files.isDirectory(p)) folderDepth--; //don't count the file name
if (folderDepth != mainFolderDepth) { /* not in main folder */ }
Alternatively, in your file walk, make sure you don't enter subfolders if you want to ignore them by setting the maxDepth argument to 1.

java.io.FileNotFoundException: /tmp/ (Too many open files)

I have a rest service in Java EE and for some weird backward compatibility reasons I have to return an .mdb file from a URL request after adding some rows inside it.
At first I simply opened an mdb file, cleared all rows in it, wrote my rows and returned it to the caller, however I realized the .mdb kept growing this way because Access doesn't purge the rows on deletion but only erases it and the library I am using (Jackcess) doesn't support purging completely the rows.
So I switched to creating a copy of an empty .mdb file with java.io.File.createTempFile() and returning it however a dangling pointer to the file in the /tmp/ folder is left and after several days I get a
java.io.FileNotFoundException: /tmp/tmpdb.mdb04949499 (Too many open files)
The only solutions I found so far are:
Set MAX_FILE_HANDLES_FOR_READ_ENDS_MAP to a very high number (which only postpones the problem)
deleting the temp file, which however is not viable because I return it from a function and once returned I lose control of the pointer.
below what I currently have:
GET
#Path("/get/database/{filename}")
#Produces("application/jet")
public StreamingOutput getDatabase(#PathParam("filename") String fileName)
{
//access files increase indefinitely in size because deleted rows are simply marked "deleted" and not removed
//so we create a temporary file equal to the template .mdb file and use it instead
java.io.File myDBFile = null;
try
{
java.io.File templateDBFile = new java.io.File(url + "resources/clean_tmpdb.mdb");
myDBFile = java.io.File.createTempFile("tmpdb", ".mdb");
myDBFile.deleteOnExit(); //useless hint
FileChannel src = new FileInputStream(templateDBFile).getChannel();
FileChannel dest = new FileOutputStream(myDBFile).getChannel();
dest.transferFrom(src, 0, src.size());
}
catch (IOException ex)
{
Logger.getLogger(FileResource.class.getName()).log(Level.SEVERE, null, ex);
}
finally
{
if (src != null)
{
try
{
src.close();
}
catch (IOException ex)
{
Logger.getLogger(FileResource.class.getName()).log(Level.SEVERE, null, ex);
}
}
if (dest != null)
{
try
{
dest.close();
}
catch (IOException ex)
{
Logger.getLogger(FileResource.class.getName()).log(Level.SEVERE, null, ex);
}
}
/* work on the file inserting rows */
return new FileStreamingOutput(myDBFile);
}
EDIT: found a similar question, with a vague accepted answer: How to delete file after REST response, the accepted answer is "just write directly to the output stream contained in the Response."
You aren't closing either src or dest. So as to ensure they are closed, close them in finally blocks, or use the try-with-resources syntax.
return new FileStreamingOutput(myDBFile);
/* here I should call myDBFile.close(); and myDBFile.delete(); */
Here you can't call myDBFile.close(), as there is no such method. You can't call myDBFile.delete() either, as otherwise the caller will receive a File that doesn't exist. If the File needs to be deleted, the caller will have to do it. Your stated requirement doesn't make sense.

Java, cannot delete file on Windows

I have a simple updater for my application. In code i am downloading a new version, deleting old version and renaming new version to old.
It works fine on Linux. But doesn't work on Windows. There are no excepions or something else.
p.s. RemotePlayer.jar it is currently runned application.
UPDATED:
Doesn't work - it means that after file.delete() and file.renameTo(...) file still alive.
I use sun java 7. (because I use JavaFX).
p.s. Sorry for my English.
public void checkUpdate(){
new Thread(new Runnable() {
#Override
public void run() {
System.err.println("Start of checking for update.");
StringBuilder url = new StringBuilder();
url.append(NetworkManager.SERVER_URL).append("/torock/getlastversionsize");
File curJarFile = null;
File newJarFile = null;
try {
curJarFile = new File(new File(".").getCanonicalPath() + "/Player/RemotePlayer.jar");
newJarFile = new File(new File(".").getCanonicalPath() + "/Player/RemotePlayerTemp.jar");
if (newJarFile.exists()){
newJarFile.delete();
}
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
System.err.println("Cannot find curr Jar file");
return;
}
if (curJarFile.exists()){
setAccesToFile(curJarFile);
try {
String resp = NetworkManager.makeGetRequest(url.toString());
JSONObject jsresp = new JSONObject(resp);
if (jsresp.getString("st").equals("ok")){
if (jsresp.getInt("size") != curJarFile.length()){
System.out.println("New version available, downloading started.");
StringBuilder downloadURL = new StringBuilder();
downloadURL.append(NetworkManager.SERVER_URL).append("/torock/getlatestversion");
if (NetworkManager.downLoadFile(downloadURL.toString(), newJarFile)){
if (jsresp.getString("md5").equals(Tools.md5File(newJarFile))){
setAccesToFile(newJarFile);
System.err.println("Deleting old version. File = " + curJarFile.getCanonicalPath());
boolean b = false;
if (curJarFile.canWrite() && curJarFile.canRead()){
curJarFile.delete();
}else System.err.println("Cannot delete cur file, doesn't have permission");
System.err.println("Installing new version. new File = " + newJarFile.getCanonicalPath());
if (curJarFile.canWrite() && curJarFile.canRead()){
newJarFile.renameTo(curJarFile);
b = true;
}else System.err.println("Cannot rename new file, doesn't have permission");
System.err.println("last version has been installed. new File = " + newJarFile.getCanonicalPath());
if (b){
Platform.runLater(new Runnable() {
#Override
public void run() {
JOptionPane.showMessageDialog(null, String.format("Внимание, %s", "Установлена новая версия, перезапустите приложение" + "", "Внимание", JOptionPane.ERROR_MESSAGE));
}
});
}
}else System.err.println("Downloading file failed, md5 doesn't match.");
}
} else System.err.println("You use latest version of application");
}
}catch (Exception e){
e.printStackTrace();
System.err.println("Cannot check new version.");
}
}else {
System.err.println("Current jar file not found");
}
}
}).start();
}
private void setAccesToFile(File f){
f.setReadable(true, false);
f.setExecutable(true, false);
f.setWritable(true, false);
}
I found the solution to this problem. The problem of deletion occurred in my case because-:
File f1=new File("temp.txt");
RandomAccessFile raf=new RandomAccessFile(f1,"rw");
f1.delete();//The file will not get deleted because raf is open on the file to be deleted
But if I close RandomAccessFile before calling delete then I am able to delete the file.
File f1=new File("temp.txt");
RandomAccessFile raf=new RandomAccessFile(f1,"rw");
raf.close();
f1.delete();//Now the file will get deleted
So we must check before calling delete weather any object such as FileInputStream, RandomAccessFile is open on that file or not. If yes then we must close that object before calling delete on that file.
windows locks files that are currently in use. you cannot delete them. on windows, you cannot delete a jar file which your application is currently using.
Since you are using Java 7, try java.nio.file.Files.delete(file.toPath()), it'll throw exception if deletion fails.
There are several reasons:
Whether you have permissions to edit the file in windows.
The file is in use or not.
The path is right or not.
I don't know wich version of Java you are using.
I know when Java was sun property they publish that the Object File can't delete files correctly on windows plateform (sorry I don't find the reference no more).
The tricks you can do is to test the plateform directly. When you are on linux just use the classic File object.
On windows launch a command system to ask windows to delete the file you want.
Runtime.getRuntime().exec(String command);
I just want to make one comment. I learned that you can delete files in Java from eclipse if you run eclipse program as Administrator. I.e. when you right click on the IDE Icon (Eclipse or any other IDE) and select Run as Administrator, Windows lets you delete the file.
I hope this helps. It helped me.
Cordially,
Fernando

Categories

Resources