Why ZipEntry fails to work in Jelly Bean - java

I have a ZIP file of 140 MB containing about 40 thousand MP3 files. I use the following code to play a certain file inside the ZIP file without decompressing it:
String fullPath = Environment.getExternalStorageDirectory().getPath() + "_audio_.mp3";
String path = Environment.getExternalStorageDirectory().getPath() + "mySoundFolder";
try {
ZipFile zip = new ZipFile(path + "myFile.zip");
Enumeration zipEntries = zip.entries();
ZipEntry entry = zip.getEntry("myFile" + "/" + currentWord + ".mp3");
if (entry != null) {
Log.i(MAIN_TAG, "entry found: " + entry.getName());
InputStream in = zip.getInputStream(entry);
File f = new File(fullPath);
FileOutputStream out = new FileOutputStream(f);
IOUtils.copy(in, out);
byte buffer[] = new byte[4096];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
if (f.exists())
{
Log.i(MAIN_TAG,"Audio file found!");
final MediaPlayer mp = new MediaPlayer();
mp.setDataSource(fullPath);
mp.prepare();
mp.setOnBufferingUpdateListener(null);
mp.setLooping(false);
mp.setOnPreparedListener(new OnPreparedListener()
{ public void onPrepared(MediaPlayer arg0)
{
mp.start();
Log.i(MAIN_TAG,"Pronunciation finished!");
}});
}
else
{
Log.i(MAIN_TAG,"File doesn't exist!!");
}
}
else {
// no such entry in the zip
Log.i(MAIN_TAG, "no such entry in the zip");
}
}
catch (IOException e) {
e.printStackTrace();
Log.i(MAIN_TAG,"IOException reading zip file");
}
}
There are two strange things with this code:
It works flawlessly in Android 2.2 but fails in Android 4.0.3. In 2.2, it finds and plays the MP3 file as I expect, but in 4.0.3, it keeps saying it cannot find the entry in the ZIP file ("no such entry in the zip").
If I reduce the number of MP3 files down to about 100 files, then in Android 4.0.3, it finds and plays the selected MP3 files as it should do.
Can you guys please help me to figure out what the problem is?
Thanks a lot in advance.

In the end, I have a workaround for this problem. I split my zip file into two files, with each containing about 20k entries. Voila, it works like a charm again.
I've heard of Java's problem with reading entries in zip files of more than 64k entries. What I have no idea why is my file has only about 40k entries but it faces the problem as well.

Related

Zip entry stream not writing anything [duplicate]

I am currently extracting the contents of a war file and then adding some new files to the directory structure and then creating a new war file.
This is all done programatically from Java - but I am wondering if it wouldn't be more efficient to copy the war file and then just append the files - then I wouldn't have to wait so long as the war expands and then has to be compressed again.
I can't seem to find a way to do this in the documentation though or any online examples.
Anyone can give some tips or pointers?
UPDATE:
TrueZip as mentioned in one of the answers seems to be a very good java library to append to a zip file (despite other answers that say it is not possible to do this).
Anyone have experience or feedback on TrueZip or can recommend other similar libaries?
In Java 7 we got Zip File System that allows adding and changing files in zip (jar, war) without manual repackaging.
We can directly write to files inside zip files as in the following example.
Map<String, String> env = new HashMap<>();
env.put("create", "true");
Path path = Paths.get("test.zip");
URI uri = URI.create("jar:" + path.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, env))
{
Path nf = fs.getPath("new.txt");
try (Writer writer = Files.newBufferedWriter(nf, StandardCharsets.UTF_8, StandardOpenOption.CREATE)) {
writer.write("hello");
}
}
As others mentioned, it's not possible to append content to an existing zip (or war). However, it's possible to create a new zip on the fly without temporarily writing extracted content to disk. It's hard to guess how much faster this will be, but it's the fastest you can get (at least as far as I know) with standard Java. As mentioned by Carlos Tasada, SevenZipJBindings might squeeze out you some extra seconds, but porting this approach to SevenZipJBindings will still be faster than using temporary files with the same library.
Here's some code that writes the contents of an existing zip (war.zip) and appends an extra file (answer.txt) to a new zip (append.zip). All it takes is Java 5 or later, no extra libraries needed.
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Main {
// 4MB buffer
private static final byte[] BUFFER = new byte[4096 * 1024];
/**
* copy input to output stream - available in several StreamUtils or Streams classes
*/
public static void copy(InputStream input, OutputStream output) throws IOException {
int bytesRead;
while ((bytesRead = input.read(BUFFER))!= -1) {
output.write(BUFFER, 0, bytesRead);
}
}
public static void main(String[] args) throws Exception {
// read war.zip and write to append.zip
ZipFile war = new ZipFile("war.zip");
ZipOutputStream append = new ZipOutputStream(new FileOutputStream("append.zip"));
// first, copy contents from existing war
Enumeration<? extends ZipEntry> entries = war.entries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
System.out.println("copy: " + e.getName());
append.putNextEntry(e);
if (!e.isDirectory()) {
copy(war.getInputStream(e), append);
}
append.closeEntry();
}
// now append some extra content
ZipEntry e = new ZipEntry("answer.txt");
System.out.println("append: " + e.getName());
append.putNextEntry(e);
append.write("42\n".getBytes());
append.closeEntry();
// close
war.close();
append.close();
}
}
I had a similar requirement sometime back - but it was for reading and writing zip archives (.war format should be similar). I tried doing it with the existing Java Zip streams but found the writing part cumbersome - especially when directories where involved.
I'll recommend you to try out the TrueZIP (open source - apache style licensed) library that exposes any archive as a virtual file system into which you can read and write like a normal filesystem. It worked like a charm for me and greatly simplified my development.
You could use this bit of code I wrote
public static void addFilesToZip(File source, File[] files)
{
try
{
File tmpZip = File.createTempFile(source.getName(), null);
tmpZip.delete();
if(!source.renameTo(tmpZip))
{
throw new Exception("Could not make temp file (" + source.getName() + ")");
}
byte[] buffer = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tmpZip));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(source));
for(int i = 0; i < files.length; i++)
{
InputStream in = new FileInputStream(files[i]);
out.putNextEntry(new ZipEntry(files[i].getName()));
for(int read = in.read(buffer); read > -1; read = in.read(buffer))
{
out.write(buffer, 0, read);
}
out.closeEntry();
in.close();
}
for(ZipEntry ze = zin.getNextEntry(); ze != null; ze = zin.getNextEntry())
{
out.putNextEntry(ze);
for(int read = zin.read(buffer); read > -1; read = zin.read(buffer))
{
out.write(buffer, 0, read);
}
out.closeEntry();
}
out.close();
tmpZip.delete();
}
catch(Exception e)
{
e.printStackTrace();
}
}
I don't know of a Java library that does what you describe. But what you described is practical. You can do it in .NET, using DotNetZip.
Michael Krauklis is correct that you cannot simply "append" data to a war file or zip file, but it is not because there is an "end of file" indication, strictly speaking, in a war file. It is because the war (zip) format includes a directory, which is normally present at the end of the file, that contains metadata for the various entries in the war file. Naively appending to a war file results in no update to the directory, and so you just have a war file with junk appended to it.
What's necessary is an intelligent class that understands the format, and can read+update a war file or zip file, including the directory as appropriate. DotNetZip does this, without uncompressing/recompressing the unchanged entries, just as you described or desired.
As Cheeso says, there's no way of doing it. AFAIK the zip front-ends are doing exactly the same as you internally.
Anyway if you're worried about the speed of extracting/compressing everything, you may want to try the SevenZipJBindings library.
I covered this library in my blog some months ago (sorry for the auto-promotion). Just as an example, extracting a 104MB zip file using the java.util.zip took me 12 seconds, while using this library took 4 seconds.
In both links you can find examples about how to use it.
Hope it helps.
See this bug report.
Using append mode on any kind of
structured data like zip files or tar
files is not something you can really
expect to work. These file formats
have an intrinsic "end of file"
indication built into the data format.
If you really want to skip the intermediate step of un-waring/re-waring, you could read the war file file, get all the zip entries, then write to a new war file "appending" the new entries you wanted to add. Not perfect, but at least a more automated solution.
Yet Another Solution: You may find code below useful in other situations as well. I have used ant this way to compile Java directories, generating jar files, updating zip files,...
public static void antUpdateZip(String zipFilePath, String libsToAddDir) {
Project p = new Project();
p.init();
Target target = new Target();
target.setName("zip");
Zip task = new Zip();
task.init();
task.setDestFile(new File(zipFilePath));
ZipFileSet zipFileSet = new ZipFileSet();
zipFileSet.setPrefix("WEB-INF/lib");
zipFileSet.setDir(new File(libsToAddDir));
task.addFileset(zipFileSet);
task.setUpdate(true);
task.setProject(p);
task.init();
target.addTask(task);
target.setProject(p);
p.addTarget(target);
DefaultLogger consoleLogger = new DefaultLogger();
consoleLogger.setErrorPrintStream(System.err);
consoleLogger.setOutputPrintStream(System.out);
consoleLogger.setMessageOutputLevel(Project.MSG_DEBUG);
p.addBuildListener(consoleLogger);
try {
// p.fireBuildStarted();
// ProjectHelper helper = ProjectHelper.getProjectHelper();
// p.addReference("ant.projectHelper", helper);
// helper.parse(p, buildFile);
p.executeTarget(target.getName());
// p.fireBuildFinished(null);
} catch (BuildException e) {
p.fireBuildFinished(e);
throw new AssertionError(e);
}
}
this a simple code to get a response with using servlet and send a response
myZipPath = bla bla...
byte[] buf = new byte[8192];
String zipName = "myZip.zip";
String zipPath = myzippath+ File.separator+"pdf" + File.separator+ zipName;
File pdfFile = new File("myPdf.pdf");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipPath));
ZipEntry zipEntry = new ZipEntry(pdfFile.getName());
out.putNextEntry(zipEntry);
InputStream in = new FileInputStream(pdfFile);
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
in.close();
out.close();
FileInputStream fis = new FileInputStream(zipPath);
response.setContentType("application/zip");
response.addHeader("content-disposition", "attachment;filename=" + zipName);
OutputStream os = response.getOutputStream();
int length = is.read(buffer);
while (length != -1)
{
os.write(buffer, 0, length);
length = is.read(buffer);
}
Here are examples how easily files can be appended to existing zip using TrueVFS:
// append a file to archive under different name
TFile.cp(new File("existingFile.txt"), new TFile("archive.zip", "entry.txt"));
// recusively append a dir to the root of archive
TFile src = new TFile("dirPath", "dirName");
src.cp_r(new TFile("archive.zip", src.getName()));
TrueVFS, the successor of TrueZIP, uses Java 7 NIO 2 features under the hood when appropriate but offers much more features like thread-safe async parallel compression.
Beware also that Java 7 ZipFileSystem by default is vulnerable to OutOfMemoryError on huge inputs.
Here is Java 1.7 version of Liam answer which uses try with resources and Apache Commons IO.
The output is written to a new zip file but it can be easily modified to write to the original file.
/**
* Modifies, adds or deletes file(s) from a existing zip file.
*
* #param zipFile the original zip file
* #param newZipFile the destination zip file
* #param filesToAddOrOverwrite the names of the files to add or modify from the original file
* #param filesToAddOrOverwriteInputStreams the input streams containing the content of the files
* to add or modify from the original file
* #param filesToDelete the names of the files to delete from the original file
* #throws IOException if the new file could not be written
*/
public static void modifyZipFile(File zipFile,
File newZipFile,
String[] filesToAddOrOverwrite,
InputStream[] filesToAddOrOverwriteInputStreams,
String[] filesToDelete) throws IOException {
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(newZipFile))) {
// add existing ZIP entry to output stream
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry entry = null;
while ((entry = zin.getNextEntry()) != null) {
String name = entry.getName();
// check if the file should be deleted
if (filesToDelete != null) {
boolean ignoreFile = false;
for (String fileToDelete : filesToDelete) {
if (name.equalsIgnoreCase(fileToDelete)) {
ignoreFile = true;
break;
}
}
if (ignoreFile) {
continue;
}
}
// check if the file should be kept as it is
boolean keepFileUnchanged = true;
if (filesToAddOrOverwrite != null) {
for (String fileToAddOrOverwrite : filesToAddOrOverwrite) {
if (name.equalsIgnoreCase(fileToAddOrOverwrite)) {
keepFileUnchanged = false;
}
}
}
if (keepFileUnchanged) {
// copy the file as it is
out.putNextEntry(new ZipEntry(name));
IOUtils.copy(zin, out);
}
}
}
// add the modified or added files to the zip file
if (filesToAddOrOverwrite != null) {
for (int i = 0; i < filesToAddOrOverwrite.length; i++) {
String fileToAddOrOverwrite = filesToAddOrOverwrite[i];
try (InputStream in = filesToAddOrOverwriteInputStreams[i]) {
out.putNextEntry(new ZipEntry(fileToAddOrOverwrite));
IOUtils.copy(in, out);
out.closeEntry();
}
}
}
}
}
this works 100% , if you dont want to use extra libs ..
1) first, the class that append files to the zip ..
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class AddZip {
public void AddZip() {
}
public void addToZipFile(ZipOutputStream zos, String nombreFileAnadir, String nombreDentroZip) {
FileInputStream fis = null;
try {
if (!new File(nombreFileAnadir).exists()) {//NO EXISTE
System.out.println(" No existe el archivo : " + nombreFileAnadir);return;
}
File file = new File(nombreFileAnadir);
System.out.println(" Generando el archivo '" + nombreFileAnadir + "' al ZIP ");
fis = new FileInputStream(file);
ZipEntry zipEntry = new ZipEntry(nombreDentroZip);
zos.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while ((length = fis.read(bytes)) >= 0) {zos.write(bytes, 0, length);}
zos.closeEntry();
fis.close();
} catch (FileNotFoundException ex ) {
Logger.getLogger(AddZip.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(AddZip.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
2) you can call it in your controller ..
//in the top
try {
fos = new FileOutputStream(rutaZip);
zos = new ZipOutputStream(fos);
} catch (FileNotFoundException ex) {
Logger.getLogger(UtilZip.class.getName()).log(Level.SEVERE, null, ex);
}
...
//inside your method
addZip.addToZipFile(zos, pathFolderFileSystemHD() + itemFoto.getNombre(), "foto/" + itemFoto.getNombre());
Based on the answer given by #sfussenegger above, following code is used to append to a jar file and download it:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Resource resourceFile = resourceLoader.getResource("WEB-INF/lib/custom.jar");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos, StandardCharsets.ISO_8859_1);) {
try (ZipFile zin = new ZipFile(resourceFile.getFile(), StandardCharsets.ISO_8859_1);) {
zin.stream().forEach((entry) -> {
try {
zos.putNextEntry(entry);
if (!entry.isDirectory()) {
zin.getInputStream(entry).transferTo(zos);
}
zos.closeEntry();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
/* build file records to be appended */
....
for (FileContents record : records) {
zos.putNextEntry(new ZipEntry(record.getFileName()));
zos.write(record.getBytes());
zos.closeEntry();
}
zos.flush();
}
response.setContentType("application/java-archive");
response.setContentLength(baos.size());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"custom.jar\"");
try (BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
baos.writeTo(out);
}
}

how to replacing some files within a certain directory inside a jar file?

I'm having the problem of replacing or updating some files within a certain directory inside a jar file.
I've read a few post already. The code (the JarUpdater Class) given at this link Updating .JAR's contents from code
is being very helpful for me to understand the role and the use of ZipInputStream, ZipOutputStream and ZipEntry, etc..
However, when I run it,
I have an EOF Exception
[EDITED by mk7: I found out the jar file was corrupted after I went through it 20 times or so. So after I replaced the jar file with a new one, the EOF Exception went away. The other two problems below still remains unsolved]
these two new xml files only get copied to the "root directory" of the jar file.
these two new xml files NEVER replaced the two original files inside a directory called /conf.
Which lines of code should I change in order to replace the xml files with the new ones?
With the System.out.println, I did see that the while loop steps through every directory and compare at every file as expected. A new temp jar was also created as expected...
I thought the statement "notInFiles = false" would take care of my need but it's NOT.
How do I step into the /conf and only replace those two files and NOT leave a copy at the root of the jar file?
What am I missing? Thanks for any insight!
Below are the code from that link.
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class JarUpdater {
public static void main(String[] args) {
File[] contents = {new File("abc.xml"),
new File("def.xml")};
File jarFile = new File("xyz.jar");
try {
updateZipFile(jarFile, contents);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void updateZipFile(File jarFile,
File[] contents) throws IOException {
// get a temp file
File tempFile = File.createTempFile(jarFile.getName(), null);
// delete it, otherwise you cannot rename your existing zip to it.
tempFile.delete();
System.out.println("tempFile is " + tempFile);
boolean renameOk=jarFile.renameTo(tempFile);
if (!renameOk)
{
throw new RuntimeException("could not rename the file "+jarFile.getAbsolutePath()+" to "+tempFile.getAbsolutePath());
}
byte[] buf = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(jarFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
String name = entry.getName();
boolean notInFiles = true;
for (File f : contents) {
System.out.println("f is " + f);
if (f.getName().equals(name)) {
// that file is already inside the jar file
notInFiles = false;
System.out.println("file already inside the jar file");
break;
}
}
if (notInFiles) {
System.out.println("name is " + name);
System.out.println("entry is " + entry);
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(name));
// Transfer bytes from the ZIP file to the output file
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
entry = zin.getNextEntry();
}
// Close the streams
zin.close();
// Compress the contents
for (int i = 0; i < contents.length; i++) {
InputStream in = new FileInputStream(contents[i]);
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(contents[i].getName()));
// Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
// Complete the entry
out.closeEntry();
in.close();
}
// Complete the ZIP file
out.close();
tempFile.delete();
}
}
In your first cycle (while loop) where you copy the entries which you don't want to replace you don't close the entries in the output zip file. Add out.closeEntry(); like this:
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(name));
// Transfer bytes from the ZIP file to the output file
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
// ADD THIS LINE:
out.closeEntry();
Also when you check if an entry is to be replaced, you should compare it to a full path, not just to the name of the file. For example if you want to replace abc.xml which is in the /conf folder, you should compare the entry name to "/conf/abc.xml" and not to "abc.xml".
To properly check if an entry is to be replaced:
String name = entry.getName();
boolean notInFiles = true;
for (File f : contents) {
System.out.println("f is " + f);
if (name.equals("/conf/" + f.getName()) {
// that file is already inside the jar file
notInFiles = false;
System.out.println("file already inside the jar file");
break;
}
}
And when you add the entries to the output which are the replaced files, you also have to specify the entry name having full path, e.g. "/conf/abc.xml" and not just "abc.xml" because it would put "abc.xml" in the root of the output zip.
To do this, start the entry name with "/conf/" like this:
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry("/conf/" + contents[i].getName()));
For URIs with the protocol jar:file: (usable for all zip files), you can use a zip file system.
URI jarUri = new URI("jar:" + jarFile.toURI().toString()); // "jar:file:/C:/../xyz.jar"
Map<String, String> zipProperties = new HashMap<>();
zipProperties.put("encoding", "UTF-8");
try (FileSystem zipFS = FileSystems.newFileSystem(jarUri, zipProperties)) {
for (File file : contents) {
Path updatePath = zipFS.getPath("/" + file.getName());
Files.delete(updatePath);
Files.copy(file.toPath(), updatePath, StandardCopyOption.REPLACE_EXISTING);
}
} // closes.
One way to derive the URI is prefixing "jar:" to a File.toURI().
This is a bit more elegant and abstract, and also allows Files.copy in and out the zip. Something to keep in ones tool chest.

java.util.zip.ZipException: invalid compression method

I am having some issues in dealing with zip files on Mac OS X 10.7.3.
I am receiving a zip file from a third party, which I have to process. My code is using ZipInputStream to do this. This code has been used several times before, without any issue but it fails for this particular zip file. The error which I get is as follows:
java.util.zip.ZipException: invalid compression method
at java.util.zip.ZipInputStream.read(ZipInputStream.java:185)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:105)
at org.apache.xerces.impl.XMLEntityManager$RewindableInputStream.read(Unknown Source)
at org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)
at org.apache.xerces.impl.XMLVersionDetector.determineDocVersion(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
I googled about it and I can see that there are some issues with ZipInputStream, e.g. this one.
I have also found some related questions on Stackoverflow, e.g. this one. But there is no proper, accepted/acceptable answer.
I have a couple questions:
Has anyone found any concrete solution for this? Like any lates
update or an all together different JAR which has same functionality
but no issues
On this link, the user phobuz1 mentions that "if
non-standard compression method (method 6)" is used, then this
problem occurs. Is there a way to find out which compression method
is used? So that I can be sure of the reason of failure?
Note that as with some users, if I unzip the file on my local machine and re-zip it, it works perfectly fine.
EDIT 1:
The file which I am getting is in .zip format, I don't know which OS/utility program they are using to compress it. On my local machine I am using the built-in zip utility which comes with Mac OS X.
API JAVADOC :
THIS is what actually compresses your file ( Updated Feb 2021 note, the oracle provided link expired, Duke University (no affiliation) however has the antiquated 1.4 javadoc available) : https://www2.cs.duke.edu/csed/java/jdk1.4.2/docs/api/java/util/zip/ZipEntry.html
per that interface, you are able to get and set compression methods ( getCompression() and setCompression(int), respectively).
good luck!
I'm using the following code in Java on my Windows XP OS to zip folders. It may at least be useful to you as a side note.
//add folder to the zip file
private void addFolderToZip(String path, String srcFolder, ZipOutputStream zip) throws Exception
{
File folder = new File(srcFolder);
//check the empty folder
if (folder.list().length == 0)
{
System.out.println(folder.getName());
addFileToZip(path , srcFolder, zip,true);
}
else
{
//list the files in the folder
for (String fileName : folder.list())
{
if (path.equals(""))
{
addFileToZip(folder.getName(), srcFolder + "/" + fileName, zip,false);
}
else
{
addFileToZip(path + "/" + folder.getName(), srcFolder + "/" + fileName, zip,false);
}
}
}
}
//recursively add files to the zip files
private void addFileToZip(String path, String srcFile, ZipOutputStream zip,boolean flag)throws Exception
{
//create the file object for inputs
File folder = new File(srcFile);
//if the folder is empty add empty folder to the Zip file
if (flag==true)
{
zip.putNextEntry(new ZipEntry(path + "/" +folder.getName() + "/"));
}
else
{
//if the current name is directory, recursively traverse it to get the files
if (folder.isDirectory())
{
addFolderToZip(path, srcFile, zip); //if folder is not empty
}
else
{
//write the file to the output
byte[] buf = new byte[1024];
int len;
FileInputStream in = new FileInputStream(srcFile);
zip.putNextEntry(new ZipEntry(path + "/" + folder.getName()));
while ((len = in.read(buf)) > 0)
{
zip.write(buf, 0, len); //Write the Result
}
}
}
}
//zip the folders
private void zipFolder(String srcFolder, String destZipFile) throws Exception
{
//create the output stream to zip file result
FileOutputStream fileWriter = new FileOutputStream(destZipFile);
ZipOutputStream zip = new ZipOutputStream(fileWriter);
//add the folder to the zip
addFolderToZip("", srcFolder, zip);
//close the zip objects
zip.flush();
zip.close();
}
private boolean zipFiles(String srcFolder, String destZipFile) throws Exception
{
boolean result=false;
System.out.println("Program Start zipping the given files");
//send to the zip procedure
zipFolder(srcFolder,destZipFile);
result=true;
System.out.println("Given files are successfully zipped");
return result;
}
In this code, you need to invoke the preceding method zipFiles(String srcFolder, String destZipFile) by passing two parameters. The first parameter indicates your folder to be zipped and the second parameter destZipFile indicates your destination zip folder.
The following code is to unzip a zipped folder.
private void unzipFolder(String file) throws FileNotFoundException, IOException
{
File zipFile=new File("YourZipFolder.zip");
File extractDir=new File("YourDestinationFolder");
extractDir.mkdirs();
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(zipFile));
try
{
ZipEntry entry;
while ((entry = inputStream.getNextEntry()) != null)
{
StringBuilder sb = new StringBuilder();
sb.append("Extracting ");
sb.append(entry.isDirectory() ? "directory " : "file ");
sb.append(entry.getName());
sb.append(" ...");
System.out.println(sb.toString());
File unzippedFile = new File(extractDir, entry.getName());
if (!entry.isDirectory())
{
if (unzippedFile.getParentFile() != null)
{
unzippedFile.getParentFile().mkdirs();
}
FileOutputStream outputStream = new FileOutputStream(unzippedFile);
try
{
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1)
{
outputStream.write(buffer, 0, len);
}
}
finally
{
if (outputStream != null)
{
outputStream.close();
}
}
}
else
{
unzippedFile.mkdirs();
}
}
}
finally
{
if (inputStream != null)
{
inputStream.close();
}
}
}

Unzipping Files with a large amount of small Files inside

I have a lot of zips with a large amount of Files which i have to unzip to the SD-Card.
Currently I am using the util.zip Package with Zipentry for each File inside the zips. This works fine but is very slow.
So I wonder if there is a lib which can handle those Files faster than the normal zip of Java/Android.
Edit: The zips are archives which are about 5 to 10MB large and contain about 50 to 100 jpg-Files whiche are each part of a picture. I need to extract all zips to a specific folder.
Slow means that the same files are extracted on an IPhone in fraction of the time.
Edit 2 the code:
ZipInputStream zin = new ZipInputStream(fd);
ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null) {
System.out.println("Unzipping " + ze.getName());
if (ze.isDirectory()) {
File f = new File(targetFilePath + ze.getName());
if (!f.isDirectory()) {
f.mkdirs();
}
} else {
int size;
byte[] buffer = new byte[2048];
FileOutputStream outStream = new FileOutputStream(targetFilePath + ze.getName() + ".tile");
BufferedOutputStream bufferOut = new BufferedOutputStream(outStream, buffer.length);
while ((size = zin.read(buffer, 0, buffer.length)) != -1) {
bufferOut.write(buffer, 0, size);
}
bufferOut.flush();
bufferOut.close();
}
}
zin.close();
The best case you could do is go NDK way. AFAIK, Google even provides supported headers/API in NDK on zlib.

Can't play a reversed .wav file with MediaPlayer

I've created an app that records audio, saves the sample to the sd card then plays it back, using the record and play buttons. I need to reverse this sample. I can do all this and the the reversed sample is saved on the SD card under a different name. The original sample is test.wav and the same sample reversed is save as revFile.wav. when i try play revFile.wav android says it can't play this format.
I've litterally put the sample in an array then reversed the contents, something is telling me that there could be header info at the start of the sample that needs striping first, any ideas. thanks.
Here's what i have so far.
public class recorder extends Activity {
MediaRecorder myRecorder = null;
DataInputStream dis = null;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onClickPlay(View v){
Log.v("onClickplay", "play clicked");
try{
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(Environment.getExternalStorageDirectory().getPath() + "/test.wav");
mp.prepare();
mp.start();
} catch(Exception e3) {
e3.printStackTrace();
}
TextView text = (TextView)findViewById(R.id.TextView01);
text.setText("playing");
}
public void onClickRecord(View v){
Log.v("onClickRecord", "record clicked");
File path = Environment.getExternalStorageDirectory();
Log.v("file path", ""+path.getAbsolutePath());
File file = new File(path, "test.wav");
if(file.exists()){
file.delete();
}
path.mkdirs();
Log.v("file path", ""+file.getAbsolutePath());
myRecorder = new MediaRecorder();
myRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
myRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
myRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
myRecorder.setOutputFile(file.getAbsolutePath());
Log.i("myrecorder", "about to prepare recording");
try{
myRecorder.prepare();
} catch(Exception e) {
e.printStackTrace();
}
Log.i("myrecorder", "prepared");
myRecorder.start(); // Recording is now started
Log.i("myrecorder", "recording");
TextView text = (TextView)findViewById(R.id.TextView01);
text.setText("recording");
}
public void onClickStop(View v){
Log.v("onClickStop", "stop clicked");
try{
myRecorder.stop();
myRecorder.reset(); // You can reuse the object by going back to setAudioSource() step
myRecorder.release(); // Now the object cannot be reused
}catch(Exception e){}
TextView text = (TextView)findViewById(R.id.TextView01);
text.setText("recording stopped");
}
public void onClickReverse(View v){
Log.v("onClickReverse", "reverse clicked");
File f = Environment.getExternalStorageDirectory();
String path = f.getAbsolutePath();
path = path + "/test.wav";
Log.v("path = ", ""+path);
Log.v("dir = ", ""+f.getAbsolutePath());
Log.v("test file exists? = ", ""+f.getAbsolutePath()+"/test.wav");
File f2 = new File(path);
Log.v("f2 = ", ""+f2.getAbsolutePath());
try {
InputStream is = new FileInputStream(f2);
BufferedInputStream bis = new BufferedInputStream(is);
dis = new DataInputStream(bis);
} catch (Exception e) {
e.printStackTrace();
}
int fileLength = (int)f2.length();
byte[] buffer = new byte[fileLength];
/*File reversedFile = Environment.getExternalStorageDirectory();
File revFile = new File(reversedFile, "reversedFile.wav");
Log.v("reversedfile path", ""+ revFile.getAbsolutePath());
if(revFile.exists()){
revFile.delete();
}
reversedFile.mkdirs();
*/
byte[] byteArray = new byte[fileLength +1];
Log.v("bytearray size = ", ""+byteArray.length);
try {
while(dis.read(buffer) != -1 ) {
dis.read(buffer);
Log.v("about to read buffer", "buffer");
byteArray = buffer;
}
Log.v(" buffer size = ", ""+ buffer.length);
} catch (IOException e) {
e.printStackTrace();
}
byte[] tempArray = new byte[fileLength];
int j=0;
for (int i=byteArray.length-1; i >=0; i--) {
tempArray[ j++ ] = byteArray[i];
}
File revPath = Environment.getExternalStorageDirectory();
Log.v("revpath path", ""+revPath.getAbsolutePath());
File revFile = new File(revPath, "revFile.wav");
Log.v("revfile path ", ""+revFile.getAbsolutePath());
if(revFile.exists()){
revFile.delete();
}
revPath.mkdirs();
try {
OutputStream os = new FileOutputStream(revFile);
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream dos = new DataOutputStream(bos);
Log.v("temparray size = ", ""+ tempArray.length);
dos.write(tempArray);
dos.flush();
dos.close();
} catch (Exception e) {
e.printStackTrace();
}
try{
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(Environment.getExternalStorageDirectory().getPath()
+"/revFile.wav");
mp.prepare();
mp.start();
} catch(Exception e3) {
e3.printStackTrace();
}
TextView text = (TextView)findViewById(R.id.TextView01);
text.setText("playing reversed file");
}
}// end of onclickrev
The WAV file format includes a 44-byte header chunk. Most WAV files consist of this 44 byte header, followed by the actual sample data. So, to reverse a WAV file, you should first copy the 44 byte header from the original file, and then copy the inverted sample data from the original after the header. If you just reverse the byte order of the original entire file, it definitely won't work. It also won't work if you copy the header and then reverse the byte order of the remainder of the file (actually it will sort of work, except what you get will be just noise). You actually need to reverse the frames, where the frame size is dependent on the bytes-per-sample and whether the file is stereo or mono (for example, if the file is stereo and 2 bytes per sample, then each frame is 4 bytes).
Note that not all WAV files are "canonical" like this. WAV files are actually a variant of RIFF files, so technically you need much more complicated code to find the various parts of the header and sample data within the original file. However, most WAV files are just the header followed by the samples (and this will certainly be true if you're recording the audio yourself), in which case you can save yourself a lot of work.
Joe Cullity's link is a good description of the WAV file format.
There is a good description of the .WAV header at link text
Also note that all data in the file is stored as Little Endian order (low order byte of 1 multi byte number is stored at the lowest address....) so you can't just reverse the bytes. you need to see how many bytes wide each sample is, (usually 16, but check the header) and reverse them in chunks of that size
I'm pretty sure that you're right and that the problem is that you can't just reverse the bytes in the file to reverse the waveform because you're destroying header information. You should try to see if there's a good library out there to do this, since I have little experience working with .wav files.

Categories

Resources