decompress RAR file in java

Discussion in 'Scripts, 3rd Party Apps, and Programming' started by galaxyAbstractor, Nov 4, 2009.

  1. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    Is there any way of doing this?

    I found http://sourceforge.net/projects/java-unrar/, but there is no documentation :/

    Edit: ok I think I got some of the lib now, how correct is this:

    Code:
    public static void unrar () throws RarException, IOException {
         
         String output = "C:\\Users\\vigge_sWe\\Desktop\\test\\";
         Archive arch = new Archive(new File("C:\\test.rar"));
         List<FileHeader> headers = arch.getFileHeaders();
         Iterator<FileHeader> iterator = headers.iterator();
         FileOutputStream os = new FileOutputStream(new File(output));
    	  while ( iterator.hasNext() ){
    	      arch.extractFile(iterator.next(), os);
    
    	  }
    
    
       }
    It gives me:
     
    Last edited: Nov 4, 2009
  2. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    I see you have specified output as a folder, but FileOutputStream writes to a single file, i.e. it tries to write a file called test, but that is seemingly a folder, which might be causing the problem.


    Update:
    I assum you want a separate file written for each file that was in the rar, in this case the code would be:
    Code:
    	  while ( iterator.hasNext() ){
                  // Get the header
                  FileHeader fh = iterator.next();
                  // Extract that file, and write it into output/FileNameInRar
    	      arch.extractFile(fh, new FileOutputStream(new File(output + fh.getFileNameString())));
    
    	  }
    
    (I haven't tested it / proofread, so there could be some typos and incorrect brackets etc.)
    This gets the header for each file, then writes it out, with the original name being used, and the file being placed in the output folder.

    BTW, you can generate the JUnrar Documentation from the source using javadoc: it is pretty poor, but the main stuff is documented.
     
    Last edited: Nov 4, 2009
  3. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    now I get:

    (a file that is in the RAR)

    code now:
    Code:
     public static void unrar () throws RarException, IOException {
         
         String output = "C:\\Users\\vigge_sWe\\Desktop\\test\\";
         Archive arch = new Archive(new File("C:\\test.rar"));
         List<FileHeader> headers = arch.getFileHeaders();
         
         Iterator<FileHeader> iterator = headers.iterator();
         FileHeader fh;
         while ( iterator.hasNext() ){
               fh = iterator.next();
    	  arch.extractFile(fh, new FileOutputStream(new File(output + fh.getFileNameString())));
         }
    
    
       }
     
    Last edited: Nov 4, 2009
  4. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    I'm not too sure here, but I think what also needs to be done, is to check whether the header is a directory, since at the moment it writes a file, even if it is a directory, and then later the code treats it as a directory since that's what it is in the archive. Here's the updated code:
    Code:
         while ( iterator.hasNext() ){
               fh = iterator.next();
               if (fh.isDirectory()) {
                   // Create the dir
                   File f = new File(new File(output + fh.getFileNameString()));
                   f.mkdirs();
              } else {
                 // Extract as a file
    	      arch.extractFile(fh, new FileOutputStream(new File(output + fh.getFileNameString())));
              }
         }
    
    (It really is horrible coding with undocumented apis.)
    BTW it is probably better not to use Rar since it is a proprietary format. Use something like zip, gzip, bzip2 or 7zip. (I think bzip2 has the best compression.)
     
    Last edited: Nov 4, 2009
  5. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    nope same error.

    This is for my fileHider app, and I wanted to be able to uncompress both RAR and ZIP files from within the images (I already fixed it with ZIP).

    the isDir if never runs
     
    Last edited: Nov 4, 2009
  6. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    I noticed an error in my code:
    Code:
    File f = new File(new File(output + fh.getFileNameString()));
                   f.mkdirs();
    
    This should be:
    Code:
    File f = new File(output + fh.getFileNameString());
                   f.mkdirs();
    
    (On my system this didn't compile, so you will have been using the older, unmodified version without knowing it.)
    Hopefully this works. I'm just trying to find a rar file to test it on.

    //Edit: works well for me. Just make sure the folder in output is already made before trying to write to it.
     
    Last edited: Nov 4, 2009
  7. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    I noticed that error and fixed it before I ran it.

    I found this code in a class that followed the lib (changed it to match my info)

    Code:
    String filename="C:/test.rar";
            File f = new File(filename);
            Archive a=null;
            try {
                a = new Archive(f);
            } catch (RarException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if(a!=null){
                a.getMainHeader().print();
                FileHeader fh = a.nextFileHeader();
                while(fh!=null){
                    try {
                        File out = new File("C:\\Users\\vigge_sWe\\Desktop\\test\\"+fh.getFileNameString().trim());
                        System.out.println(out.getAbsolutePath());
                        FileOutputStream os = new FileOutputStream(out);
                        a.extractFile(fh, os);
                        os.close();
                    } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (RarException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    fh=a.nextFileHeader();
                }
            }
    I get this output:

    A 0-byte extensionless file is created tho (the last line in the output)
     
  8. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    That is the problem: a file is created instead of a folder, and you therefore can't write a file into a "folder" location which is a file. My code works fine, making a folder and not a file when required, at least on my system: the code you have pasted wouldn't work on a rar file with any subfolders because of that flaw. Try running my code again, but first of all delete all the contents of the output folder, since if that file which should have been a folder is still present, it will still be causing problems. I.e. make sure the output folder is present, but that there is nothing in it.
     
    Last edited: Nov 5, 2009
  9. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    Nope. I got a hybrid of the test code and your code to create the directory but not the file:

    Code:
     String filename="C:/test.rar";
    		File f = new File(filename);
    		Archive a=null;
    		try {
    			a = new Archive(f);
    		} catch (RarException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
                    String output = "C:/Users/vigge_sWe/Desktop/test/";
    		if(a!=null){
    			a.getMainHeader().print();
    			FileHeader fh = a.nextFileHeader();
    			while(fh!=null){
    				try {
                                         if (fh.isDirectory()) {
                   // Create the dir
    
                                             File fol = new File(output + fh.getFileNameString());
                                             fol.mkdirs();
    
                                        } else {
    					File out = new File("C:/Users/vigge_sWe/Desktop/test/"+fh.getFileNameString().trim());
    					System.out.println(out.getAbsolutePath());
    					FileOutputStream os = new FileOutputStream(out);
    					a.extractFile(fh, os);
    					os.close();
                                    }
    				} catch (FileNotFoundException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				} catch (RarException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				fh=a.nextFileHeader();
    			}
    		}
    
    EDIT

    I got it! The file needs to be created before used in an output stream:
    Code:
    String filename="C:/test.rar";
    		File f = new File(filename);
    		Archive a=null;
    		try {
    			a = new Archive(f);
    		} catch (RarException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
                    String output = "C:/Users/vigge_sWe/Desktop/test/";
    		if(a!=null){
    			a.getMainHeader().print();
    			FileHeader fh = a.nextFileHeader();
    			while(fh!=null){
    				try {
                                         if (fh.isDirectory()) {
                   // Create the dir
    
                                             File fol = new File(output + fh.getFileNameString());
                                             fol.mkdirs();
    
                                        } else {
    					File out = new File("C:/Users/vigge_sWe/Desktop/test/"+fh.getFileNameString().trim());
    					System.out.println(out.getAbsolutePath());
                                            if(!out.exists()) {
                                                out.createNewFile();
                                            }
    					FileOutputStream os = new FileOutputStream(out);
    					a.extractFile(fh, os);
    					os.close();
                                    }
    				} catch (FileNotFoundException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				} catch (RarException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				fh=a.nextFileHeader();
    			}
    		}
    Code:
     if(!out.exists()) {
                                                out.createNewFile();
                                            }
    
    fixed it
     
    Last edited: Nov 5, 2009
  10. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    Last edited: Nov 5, 2009
  11. Gouri

    Gouri Community Paragon Community Support

    Messages:
    4,563
    Likes Received:
    245
    Trophy Points:
    63
    Yes Please. I want the codes and libs you used. So I can check.
     
  12. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    I contacted the java channel, they said I can't use ZipInputStream for this, and need to use ZipFile instead, and remove the byte signatures marshian and ah-blabla took their time to make.

    Is it worth removing that part and guess archive type instead, or make some weird workaround?
     
  13. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    I really don't know what's happening now. I just realised my code also had a slight problem where directories were being made, but files named incorrectly (rar seems to use windows directory separators, causing chaos when I use / instead since I'm on *nix, but that seems to have worked fine on Windows.) The spaces in the filename could be causing the problems, but I doubt that.


    //Edit: ignore that. The problem I think is that iterator.next() on the list of headers returns the headers in random order. If you happen to get a file in a directory, before that directory is returned, you will have problems...
    New code to extract file:
    Code:
    String loc = changeSeparators(fh.getFileNameString());
                  File outFile = new File(output +loc);
                  outFile.mkdirs();
                  outFile.delete();
                  outFile.createNewFile();
              arch.extractFile(fh, new FileOutputStream(outFile));
    
    What this does is: create a File object, then make all parent directories + create a folder with this name (this is because you can't create a file while automatically creating parent directories, but you can a folder), then deletes the folder made (but any parent folders are kept), makes a file of that name, and lastly extracts the file. This now works well on my system, and hopefully also on yours...

    Oh, just noticed your latest post: one way to do this, while keeping the signature, is to write the archive to a temporary file, then use ZipFile on this.
     
    Last edited: Nov 5, 2009
  14. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    I did...

    Creating temp file and calling functions: http://pastebin.com/m4b34eaf3
    unZip.class: http://pastebin.com/m30c37759

    I am getting:
    Because there is the 8 extra bytes in the end of the file that we added when we created the files
     
  15. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    You need to cut off those last eight bytes, i.e. stop eight bytes from the end:
    Change this code to stop at the right point:
    Code:
    #
                            out = new FileOutputStream(temp);
    #
     
    #
                            while ((nextChar = in.read()) != -1) {
    #
                                out.write((char) nextChar);
    #
                            }
    #
                            out.close();
    You can get the file size with File.length(), so make a file with "findDir + zipFileLabel.getText()", get the size, subtract the number of bytes you skip at first, subtract another 8 bytes, and now use a for loop to go through that number of bytes. (I'm a bit too tired to write code now. Sorry...)
     
    Last edited: Nov 5, 2009
  16. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    you mean something like

    Code:
    out = new FileOutputStream(temp);
                            long totalSize = fi.imageSize(findDir + zipFileLabel.getText()) +
                                    fi.archiveSize(findDir); // total size of archive
                            int a = 0;
                            while ((nextChar = in.read()) != -1 && a <= totalSize-8) {
                                out.write((char) nextChar);
                                a++;
                            }
                            out.close();
    ?

    Well, if it is, it is still not working
     
  17. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    You need to skip the image size first, then read the archive bytes only:
    Code:
    out = new FileOutputStream(temp);
    File archive = new File(findDir + zipFileLabel.getText());
                            // The size of the image portion
                            long skipSize = fi.imageSize(findDir + zipFileLabel.getText());
                            // The arhive portion
                            long archSize =archive.length() - skipSize -8;
                            // Just make sure the image part is skipped, if already done remove this line.
                            in.skip(skipSize);
                            for (int i = 0; i < archSize; i++) {
                                nextChar = in.read();
                                out.write((char) nextChar);
                            }
                            out.close();
    //Assuming you don't need to read close it as well.
                            in.close();
    
    I'm not sure what fi.imageSize() and fi.archiveSize() do, but skipSize basically needs to be set as the size of image you have in the file (this is what is stored as part of the signature isn't it), and archSize the size of the archive in the file, i.e. totalSize-8-imageSize - I would just use this calculation if possible. I noticed that you are running archiveSize() and imageSize() on two different files, which could be causing problems, so I have edited the code, assuming "findDir + zipFileLabel.getText()" to be the archived file. (Make sure directory separators are present in findDir.) I have also done skipping of the input file here, but I think you already did that earlier, so remove that if that is the case.
     
    Last edited: Nov 6, 2009
  18. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    imageSize() gets the image size from the bytes we added in the signature, archiveSize() get's the size of the archive without the 8 bytes.

    I'll test your code to see if it works. Edit: yes it worked xD

    Now over to the RAR issue


    RAR error: http://pastebin.com/m779c0a33
    unRar is called: http://pastebin.com/m224e4fa1
    unrar method: http://pastebin.com/m4d85e6ae
     
    Last edited: Nov 6, 2009
  19. ah-blabla

    ah-blabla New Member

    Messages:
    375
    Likes Received:
    7
    Trophy Points:
    0
    The only thing I can think of now, might be the spaces in the filename, read this for a thread about someone having problems with spaces. (I do not have problems with spaces on my sytem, but that might be since I'm using openJDK and *nix, where files are handled differently.)

    Try testing a rar file with files and folder names without any spaces just to test whether the method usually works fine.
     
    Last edited: Nov 6, 2009
  20. galaxyAbstractor

    galaxyAbstractor Community Advocate Community Support

    Messages:
    5,508
    Likes Received:
    35
    Trophy Points:
    48
    nope, I tried removing all spaces in the filenames but I'm still getting that :/.

    Noticed tho that if the folders are already created, it works. So if you run it once, let it make the folders and give that error, run it once more and it will work
     
    Last edited: Nov 7, 2009

Share This Page