mirror of
https://github.com/topjohnwu/jtar.git
synced 2024-11-23 11:29:47 +00:00
first v2.0 commit
This commit is contained in:
commit
5a18d206f7
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
target/
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
|
201
LICENSE.txt
Executable file
201
LICENSE.txt
Executable file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
0
README.textile
Normal file
0
README.textile
Normal file
107
pom.xml
Executable file
107
pom.xml
Executable file
@ -0,0 +1,107 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.kamranzafar</groupId>
|
||||||
|
<artifactId>jtar</artifactId>
|
||||||
|
<name>JTar</name>
|
||||||
|
<version>2.0</version>
|
||||||
|
<description>Java Tar API</description>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.sonatype.oss</groupId>
|
||||||
|
<artifactId>oss-parent</artifactId>
|
||||||
|
<version>5</version>
|
||||||
|
</parent>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>xeus.man</id>
|
||||||
|
<name>Kamran Zafar</name>
|
||||||
|
<email>xeus.man@gmail.com
|
||||||
|
</email>
|
||||||
|
<roles>
|
||||||
|
<role>developer</role>
|
||||||
|
</roles>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache Software License</name>
|
||||||
|
<url>${basedir}/LICENSE.txt
|
||||||
|
</url>
|
||||||
|
<distribution>repo</distribution>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<organization>
|
||||||
|
<name>Kamran Zafar</name>
|
||||||
|
<url>http://kamranzafar.org</url>
|
||||||
|
</organization>
|
||||||
|
<scm>
|
||||||
|
<connection>
|
||||||
|
scm:svn:https://jtar.googlecode.com/svn/trunk/jtar
|
||||||
|
</connection>
|
||||||
|
<developerConnection>
|
||||||
|
scm:svn:https://jtar.googlecode.com/svn/trunk/jtar
|
||||||
|
</developerConnection>
|
||||||
|
<url>https://jtar.googlecode.com/svn/trunk/jtar
|
||||||
|
</url>
|
||||||
|
</scm>
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>${basedir}/src/main/java
|
||||||
|
</sourceDirectory>
|
||||||
|
<testSourceDirectory>${basedir}/src/test/java
|
||||||
|
</testSourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.5</source>
|
||||||
|
<target>1.5</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sign-artifacts</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
141
src/main/java/org/kamranzafar/jtar/Octal.java
Executable file
141
src/main/java/org/kamranzafar/jtar/Octal.java
Executable file
@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Octal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an octal string from a header buffer. This is used for the file
|
||||||
|
* permission mode value.
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
*
|
||||||
|
* @return The long value of the octal string.
|
||||||
|
*/
|
||||||
|
public static long parseOctal(byte[] header, int offset, int length) {
|
||||||
|
long result = 0;
|
||||||
|
boolean stillPadding = true;
|
||||||
|
|
||||||
|
int end = offset + length;
|
||||||
|
for (int i = offset; i < end; ++i) {
|
||||||
|
if (header[i] == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (header[i] == (byte) ' ' || header[i] == '0') {
|
||||||
|
if (stillPadding)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (header[i] == (byte) ' ')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stillPadding = false;
|
||||||
|
|
||||||
|
result = ( result << 3 ) + ( header[i] - '0' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an octal integer from a header buffer.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param buf
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
*
|
||||||
|
* @return The integer value of the octal bytes.
|
||||||
|
*/
|
||||||
|
public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
|
||||||
|
int idx = length - 1;
|
||||||
|
|
||||||
|
buf[offset + idx] = 0;
|
||||||
|
--idx;
|
||||||
|
buf[offset + idx] = (byte) ' ';
|
||||||
|
--idx;
|
||||||
|
|
||||||
|
if (value == 0) {
|
||||||
|
buf[offset + idx] = (byte) '0';
|
||||||
|
--idx;
|
||||||
|
} else {
|
||||||
|
for (long val = value; idx >= 0 && val > 0; --idx) {
|
||||||
|
buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
|
||||||
|
val = val >> 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; idx >= 0; --idx) {
|
||||||
|
buf[offset + idx] = (byte) ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the checksum octal integer from a header buffer.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param buf
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
* @return The integer value of the entry's checksum.
|
||||||
|
*/
|
||||||
|
public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
|
||||||
|
getOctalBytes( value, buf, offset, length );
|
||||||
|
buf[offset + length - 1] = (byte) ' ';
|
||||||
|
buf[offset + length - 2] = 0;
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an octal long integer from a header buffer.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param buf
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
*
|
||||||
|
* @return The long value of the octal bytes.
|
||||||
|
*/
|
||||||
|
public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
|
||||||
|
byte[] temp = new byte[length + 1];
|
||||||
|
getOctalBytes( value, temp, 0, length + 1 );
|
||||||
|
System.arraycopy( temp, 0, buf, offset, length );
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/main/java/org/kamranzafar/jtar/TarConstants.java
Executable file
28
src/main/java/org/kamranzafar/jtar/TarConstants.java
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarConstants {
|
||||||
|
public static final int EOF_BLOCK = 1024;
|
||||||
|
public static final int DATA_BLOCK = 512;
|
||||||
|
public static final int HEADER_BLOCK = 512;
|
||||||
|
}
|
303
src/main/java/org/kamranzafar/jtar/TarEntry.java
Executable file
303
src/main/java/org/kamranzafar/jtar/TarEntry.java
Executable file
@ -0,0 +1,303 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarEntry {
|
||||||
|
protected File file;
|
||||||
|
protected TarHeader header;
|
||||||
|
|
||||||
|
private TarEntry() {
|
||||||
|
this.file = null;
|
||||||
|
this.header = new TarHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarEntry(File file, String entryName) {
|
||||||
|
this();
|
||||||
|
this.file = file;
|
||||||
|
this.extractTarHeader(entryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarEntry(byte[] headerBuf) {
|
||||||
|
this();
|
||||||
|
this.parseTarHeader(headerBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(TarEntry it) {
|
||||||
|
return this.header.name.toString().equals(it.header.name.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDescendent(TarEntry desc) {
|
||||||
|
return desc.header.name.toString().startsWith(
|
||||||
|
this.header.name.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarHeader getHeader() {
|
||||||
|
return this.header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.header.name.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.header.name = new StringBuffer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUserId() {
|
||||||
|
return this.header.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(int userId) {
|
||||||
|
this.header.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGroupId() {
|
||||||
|
return this.header.groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(int groupId) {
|
||||||
|
this.header.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return this.header.userName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.header.userName = new StringBuffer(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupName() {
|
||||||
|
return this.header.groupName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupName(String groupName) {
|
||||||
|
this.header.groupName = new StringBuffer(groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIds(int userId, int groupId) {
|
||||||
|
this.setUserId(userId);
|
||||||
|
this.setGroupId(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModTime(long time) {
|
||||||
|
this.header.modTime = time / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModTime(Date time) {
|
||||||
|
this.header.modTime = time.getTime() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModTime() {
|
||||||
|
return new Date(this.header.modTime * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return this.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return this.header.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(long size) {
|
||||||
|
this.header.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the org.xeustechnologies.jtar entry is a directory
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isDirectory() {
|
||||||
|
if (this.file != null)
|
||||||
|
return this.file.isDirectory();
|
||||||
|
|
||||||
|
if (this.header != null) {
|
||||||
|
if (this.header.linkFlag == TarHeader.LF_DIR)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (this.header.name.toString().endsWith("/"))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract header from File
|
||||||
|
*
|
||||||
|
* @param entryName
|
||||||
|
*/
|
||||||
|
public void extractTarHeader(String entryName) {
|
||||||
|
String name = entryName;
|
||||||
|
|
||||||
|
name = name.replace(File.separatorChar, '/');
|
||||||
|
|
||||||
|
if (name.startsWith("/"))
|
||||||
|
name = name.substring(1);
|
||||||
|
|
||||||
|
header.linkName = new StringBuffer("");
|
||||||
|
|
||||||
|
header.name = new StringBuffer(name);
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
header.mode = 040755;
|
||||||
|
header.linkFlag = TarHeader.LF_DIR;
|
||||||
|
if (header.name.charAt(header.name.length() - 1) != '/') {
|
||||||
|
header.name.append("/");
|
||||||
|
}
|
||||||
|
header.size = 0;
|
||||||
|
} else {
|
||||||
|
header.mode = 0100644;
|
||||||
|
header.linkFlag = TarHeader.LF_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.size = file.length();
|
||||||
|
header.modTime = file.lastModified() / 1000;
|
||||||
|
header.checkSum = 0;
|
||||||
|
header.devMajor = 0;
|
||||||
|
header.devMinor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate checksum
|
||||||
|
*
|
||||||
|
* @param buf
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long computeCheckSum(byte[] buf) {
|
||||||
|
long sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < buf.length; ++i) {
|
||||||
|
sum += 255 & buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the header to the byte buffer
|
||||||
|
*
|
||||||
|
* @param outbuf
|
||||||
|
*/
|
||||||
|
public void writeEntryHeader(byte[] outbuf) {
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
offset = TarHeader.getNameBytes(this.header.name, outbuf, offset,
|
||||||
|
TarHeader.NAMELEN);
|
||||||
|
offset = Octal.getOctalBytes(this.header.mode, outbuf, offset,
|
||||||
|
TarHeader.MODELEN);
|
||||||
|
offset = Octal.getOctalBytes(this.header.userId, outbuf, offset,
|
||||||
|
TarHeader.UIDLEN);
|
||||||
|
offset = Octal.getOctalBytes(this.header.groupId, outbuf, offset,
|
||||||
|
TarHeader.GIDLEN);
|
||||||
|
|
||||||
|
long size = this.header.size;
|
||||||
|
|
||||||
|
offset = Octal.getLongOctalBytes(size, outbuf, offset,
|
||||||
|
TarHeader.SIZELEN);
|
||||||
|
offset = Octal.getLongOctalBytes(this.header.modTime, outbuf, offset,
|
||||||
|
TarHeader.MODTIMELEN);
|
||||||
|
|
||||||
|
int csOffset = offset;
|
||||||
|
for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
|
||||||
|
outbuf[offset++] = (byte) ' ';
|
||||||
|
|
||||||
|
outbuf[offset++] = this.header.linkFlag;
|
||||||
|
|
||||||
|
offset = TarHeader.getNameBytes(this.header.linkName, outbuf, offset,
|
||||||
|
TarHeader.NAMELEN);
|
||||||
|
offset = TarHeader.getNameBytes(this.header.magic, outbuf, offset,
|
||||||
|
TarHeader.MAGICLEN);
|
||||||
|
offset = TarHeader.getNameBytes(this.header.userName, outbuf, offset,
|
||||||
|
TarHeader.UNAMELEN);
|
||||||
|
offset = TarHeader.getNameBytes(this.header.groupName, outbuf, offset,
|
||||||
|
TarHeader.GNAMELEN);
|
||||||
|
offset = Octal.getOctalBytes(this.header.devMajor, outbuf, offset,
|
||||||
|
TarHeader.DEVLEN);
|
||||||
|
offset = Octal.getOctalBytes(this.header.devMinor, outbuf, offset,
|
||||||
|
TarHeader.DEVLEN);
|
||||||
|
|
||||||
|
for (; offset < outbuf.length;)
|
||||||
|
outbuf[offset++] = 0;
|
||||||
|
|
||||||
|
long checkSum = this.computeCheckSum(outbuf);
|
||||||
|
|
||||||
|
Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset,
|
||||||
|
TarHeader.CHKSUMLEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the tar header to the byte buffer
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
* @param bh
|
||||||
|
*/
|
||||||
|
public void parseTarHeader(byte[] bh) {
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
|
||||||
|
offset += TarHeader.NAMELEN;
|
||||||
|
|
||||||
|
header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
|
||||||
|
offset += TarHeader.MODELEN;
|
||||||
|
|
||||||
|
header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
|
||||||
|
offset += TarHeader.UIDLEN;
|
||||||
|
|
||||||
|
header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
|
||||||
|
offset += TarHeader.GIDLEN;
|
||||||
|
|
||||||
|
header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
|
||||||
|
offset += TarHeader.SIZELEN;
|
||||||
|
|
||||||
|
header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
|
||||||
|
offset += TarHeader.MODTIMELEN;
|
||||||
|
|
||||||
|
header.checkSum = (int) Octal.parseOctal(bh, offset,
|
||||||
|
TarHeader.CHKSUMLEN);
|
||||||
|
offset += TarHeader.CHKSUMLEN;
|
||||||
|
|
||||||
|
header.linkFlag = bh[offset++];
|
||||||
|
|
||||||
|
header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
|
||||||
|
offset += TarHeader.NAMELEN;
|
||||||
|
|
||||||
|
header.magic = TarHeader.parseName(bh, offset, TarHeader.MAGICLEN);
|
||||||
|
offset += TarHeader.MAGICLEN;
|
||||||
|
|
||||||
|
header.userName = TarHeader.parseName(bh, offset, TarHeader.UNAMELEN);
|
||||||
|
offset += TarHeader.UNAMELEN;
|
||||||
|
|
||||||
|
header.groupName = TarHeader.parseName(bh, offset, TarHeader.GNAMELEN);
|
||||||
|
offset += TarHeader.GNAMELEN;
|
||||||
|
|
||||||
|
header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.DEVLEN);
|
||||||
|
offset += TarHeader.DEVLEN;
|
||||||
|
|
||||||
|
header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.DEVLEN);
|
||||||
|
}
|
||||||
|
}
|
197
src/main/java/org/kamranzafar/jtar/TarHeader.java
Executable file
197
src/main/java/org/kamranzafar/jtar/TarHeader.java
Executable file
@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Offset Size Field
|
||||||
|
* 0 100 File name
|
||||||
|
* 100 8 File mode
|
||||||
|
* 108 8 Owner's numeric user ID
|
||||||
|
* 116 8 Group's numeric user ID
|
||||||
|
* 124 12 File size in bytes
|
||||||
|
* 136 12 Last modification time in numeric Unix time format
|
||||||
|
* 148 8 Checksum for header block
|
||||||
|
* 156 1 Link indicator (file type)
|
||||||
|
* 157 100 Name of linked file
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* File Types
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Value Meaning
|
||||||
|
* '0' Normal file
|
||||||
|
* (ASCII NUL) Normal file (now obsolete)
|
||||||
|
* '1' Hard link
|
||||||
|
* '2' Symbolic link
|
||||||
|
* '3' Character special
|
||||||
|
* '4' Block special
|
||||||
|
* '5' Directory
|
||||||
|
* '6' FIFO
|
||||||
|
* '7' Contigous
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Ustar header
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Offset Size Field
|
||||||
|
* 257 6 UStar indicator "ustar"
|
||||||
|
* 263 2 UStar version "00"
|
||||||
|
* 265 32 Owner user name
|
||||||
|
* 297 32 Owner group name
|
||||||
|
* 329 8 Device major number
|
||||||
|
* 337 8 Device minor number
|
||||||
|
* 345 155 Filename prefix
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TarHeader {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Header
|
||||||
|
*/
|
||||||
|
public static final int NAMELEN = 100;
|
||||||
|
public static final int MODELEN = 8;
|
||||||
|
public static final int UIDLEN = 8;
|
||||||
|
public static final int GIDLEN = 8;
|
||||||
|
public static final int SIZELEN = 12;
|
||||||
|
public static final int MODTIMELEN = 12;
|
||||||
|
public static final int CHKSUMLEN = 8;
|
||||||
|
public static final byte LF_OLDNORM = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File Types
|
||||||
|
*/
|
||||||
|
public static final byte LF_NORMAL = (byte) '0';
|
||||||
|
public static final byte LF_LINK = (byte) '1';
|
||||||
|
public static final byte LF_SYMLINK = (byte) '2';
|
||||||
|
public static final byte LF_CHR = (byte) '3';
|
||||||
|
public static final byte LF_BLK = (byte) '4';
|
||||||
|
public static final byte LF_DIR = (byte) '5';
|
||||||
|
public static final byte LF_FIFO = (byte) '6';
|
||||||
|
public static final byte LF_CONTIG = (byte) '7';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ustar header
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static final int MAGICLEN = 8;
|
||||||
|
/**
|
||||||
|
* The magic tag representing a POSIX tar archive.
|
||||||
|
*/
|
||||||
|
public static final String TMAGIC = "ustar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The magic tag representing a GNU tar archive.
|
||||||
|
*/
|
||||||
|
public static final String GNU_TMAGIC = "ustar ";
|
||||||
|
|
||||||
|
public static final int UNAMELEN = 32;
|
||||||
|
public static final int GNAMELEN = 32;
|
||||||
|
public static final int DEVLEN = 8;
|
||||||
|
|
||||||
|
// Header values
|
||||||
|
public StringBuffer name;
|
||||||
|
public int mode;
|
||||||
|
public int userId;
|
||||||
|
public int groupId;
|
||||||
|
public long size;
|
||||||
|
public long modTime;
|
||||||
|
public int checkSum;
|
||||||
|
public byte linkFlag;
|
||||||
|
public StringBuffer linkName;
|
||||||
|
public StringBuffer magic;
|
||||||
|
public StringBuffer userName;
|
||||||
|
public StringBuffer groupName;
|
||||||
|
public int devMajor;
|
||||||
|
public int devMinor;
|
||||||
|
|
||||||
|
public TarHeader() {
|
||||||
|
this.magic = new StringBuffer( TarHeader.TMAGIC );
|
||||||
|
|
||||||
|
this.name = new StringBuffer();
|
||||||
|
this.linkName = new StringBuffer();
|
||||||
|
|
||||||
|
String user = System.getProperty( "user.name", "" );
|
||||||
|
|
||||||
|
if (user.length() > 31)
|
||||||
|
user = user.substring( 0, 31 );
|
||||||
|
|
||||||
|
this.userId = 0;
|
||||||
|
this.groupId = 0;
|
||||||
|
this.userName = new StringBuffer( user );
|
||||||
|
this.groupName = new StringBuffer( "" );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an entry name from a header buffer.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param header
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
* @return The header's entry name.
|
||||||
|
*/
|
||||||
|
public static StringBuffer parseName(byte[] header, int offset, int length) {
|
||||||
|
StringBuffer result = new StringBuffer( length );
|
||||||
|
|
||||||
|
int end = offset + length;
|
||||||
|
for (int i = offset; i < end; ++i) {
|
||||||
|
if (header[i] == 0)
|
||||||
|
break;
|
||||||
|
result.append( (char) header[i] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the number of bytes in an entry name.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param header
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
* @return The number of bytes in a header's entry name.
|
||||||
|
*/
|
||||||
|
public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < length && i < name.length(); ++i) {
|
||||||
|
buf[offset + i] = (byte) name.charAt( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < length; ++i) {
|
||||||
|
buf[offset + i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
242
src/main/java/org/kamranzafar/jtar/TarInputStream.java
Executable file
242
src/main/java/org/kamranzafar/jtar/TarInputStream.java
Executable file
@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
private static final int SKIP_BUFFER_SIZE = 2048;
|
||||||
|
private TarEntry currentEntry;
|
||||||
|
private long currentFileSize;
|
||||||
|
private long bytesRead;
|
||||||
|
private boolean defaultSkip = false;
|
||||||
|
|
||||||
|
public TarInputStream(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
currentFileSize = 0;
|
||||||
|
bytesRead = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not supported
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(int readlimit) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not supported
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
throw new IOException("mark/reset not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a byte
|
||||||
|
*
|
||||||
|
* @see java.io.FilterInputStream#read()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
byte[] buf = new byte[1];
|
||||||
|
|
||||||
|
int res = this.read(buf, 0, 1);
|
||||||
|
|
||||||
|
if (res != -1) {
|
||||||
|
return buf[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the bytes being read exceed the entry size and adjusts the byte
|
||||||
|
* array length. Updates the byte counters
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see java.io.FilterInputStream#read(byte[], int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
if (currentFileSize == currentEntry.getSize()) {
|
||||||
|
return -1;
|
||||||
|
} else if ((currentEntry.getSize() - currentFileSize) < len) {
|
||||||
|
len = (int) (currentEntry.getSize() - currentFileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int br = super.read(b, off, len);
|
||||||
|
|
||||||
|
if (br != -1) {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
currentFileSize += br;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead += br;
|
||||||
|
}
|
||||||
|
|
||||||
|
return br;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next entry in the tar file
|
||||||
|
*
|
||||||
|
* @return TarEntry
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public TarEntry getNextEntry() throws IOException {
|
||||||
|
closeCurrentEntry();
|
||||||
|
|
||||||
|
byte[] header = new byte[TarConstants.HEADER_BLOCK];
|
||||||
|
byte[] theader = new byte[TarConstants.HEADER_BLOCK];
|
||||||
|
int tr = 0;
|
||||||
|
|
||||||
|
// Read full header
|
||||||
|
while (tr < TarConstants.HEADER_BLOCK) {
|
||||||
|
int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(theader, 0, header, tr, res);
|
||||||
|
tr += res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if record is null
|
||||||
|
boolean eof = true;
|
||||||
|
for (byte b : header) {
|
||||||
|
if (b != 0) {
|
||||||
|
eof = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eof) {
|
||||||
|
bytesRead += header.length;
|
||||||
|
currentEntry = new TarEntry(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current tar entry
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void closeCurrentEntry() throws IOException {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
if (currentEntry.getSize() > currentFileSize) {
|
||||||
|
// Not fully read, skip rest of the bytes
|
||||||
|
long bs = 0;
|
||||||
|
while (bs < currentEntry.getSize() - currentFileSize) {
|
||||||
|
long res = skip(currentEntry.getSize() - currentFileSize
|
||||||
|
- bs);
|
||||||
|
|
||||||
|
if (res == 0
|
||||||
|
&& currentEntry.getSize() - currentFileSize > 0) {
|
||||||
|
// I suspect file corruption
|
||||||
|
throw new IOException("Possible tar file corruption");
|
||||||
|
}
|
||||||
|
|
||||||
|
bs += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEntry = null;
|
||||||
|
currentFileSize = 0L;
|
||||||
|
skipPad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips the pad at the end of each tar entry file content
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void skipPad() throws IOException {
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
long bs = 0;
|
||||||
|
while (bs < TarConstants.DATA_BLOCK - extra) {
|
||||||
|
long res = skip(TarConstants.DATA_BLOCK - extra - bs);
|
||||||
|
bs += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips 'n' bytes on the InputStream<br>
|
||||||
|
* Overrides default implementation of skip
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
if (defaultSkip) {
|
||||||
|
// use skip method of parent stream
|
||||||
|
// may not work if skip not implemented by parent
|
||||||
|
return super.skip(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long left = n;
|
||||||
|
byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left
|
||||||
|
: SKIP_BUFFER_SIZE));
|
||||||
|
if (res < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
left -= res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n - left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultSkip() {
|
||||||
|
return defaultSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultSkip(boolean defaultSkip) {
|
||||||
|
this.defaultSkip = defaultSkip;
|
||||||
|
}
|
||||||
|
}
|
134
src/main/java/org/kamranzafar/jtar/TarOutputStream.java
Executable file
134
src/main/java/org/kamranzafar/jtar/TarOutputStream.java
Executable file
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarOutputStream extends FilterOutputStream {
|
||||||
|
private long bytesWritten;
|
||||||
|
private long currentFileSize;
|
||||||
|
private TarEntry currentEntry;
|
||||||
|
|
||||||
|
public TarOutputStream(OutputStream out) {
|
||||||
|
super( out );
|
||||||
|
bytesWritten = 0;
|
||||||
|
currentFileSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the EOF record and closes the stream
|
||||||
|
*
|
||||||
|
* @see java.io.FilterOutputStream#close()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
closeCurrentEntry();
|
||||||
|
write( new byte[TarConstants.EOF_BLOCK] );
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a byte to the stream and updates byte counters
|
||||||
|
*
|
||||||
|
* @see java.io.FilterOutputStream#write(int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
super.write( b );
|
||||||
|
bytesWritten += 1;
|
||||||
|
|
||||||
|
if (currentEntry != null) {
|
||||||
|
currentFileSize += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the bytes being written exceed the current entry size.
|
||||||
|
*
|
||||||
|
* @see java.io.FilterOutputStream#write(byte[], int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (currentEntry != null && !currentEntry.isDirectory()) {
|
||||||
|
if (currentEntry.getSize() < currentFileSize + len) {
|
||||||
|
throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
|
||||||
|
+ currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
|
||||||
|
+ "] being written." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.write( b, off, len );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the next tar entry header on the stream
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void putNextEntry(TarEntry entry) throws IOException {
|
||||||
|
closeCurrentEntry();
|
||||||
|
|
||||||
|
byte[] header = new byte[TarConstants.HEADER_BLOCK];
|
||||||
|
entry.writeEntryHeader( header );
|
||||||
|
|
||||||
|
write( header );
|
||||||
|
|
||||||
|
currentEntry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current tar entry
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void closeCurrentEntry() throws IOException {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
if (currentEntry.getSize() > currentFileSize) {
|
||||||
|
throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
|
||||||
|
+ currentEntry.getSize() + "] has not been fully written." );
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEntry = null;
|
||||||
|
currentFileSize = 0;
|
||||||
|
|
||||||
|
pad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pads the last content block
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void pad() throws IOException {
|
||||||
|
if (bytesWritten > 0) {
|
||||||
|
int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
write( new byte[TarConstants.DATA_BLOCK - extra] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/main/java/org/kamranzafar/jtar/TarUtils.java
Executable file
75
src/main/java/org/kamranzafar/jtar/TarUtils.java
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarUtils {
|
||||||
|
/**
|
||||||
|
* Determines the tar file size of the given folder/file path
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static long calculateTarSize(File path) {
|
||||||
|
return tarSize( path ) + TarConstants.EOF_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long tarSize(File dir) {
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
if (dir.isFile()) {
|
||||||
|
return entrySize( dir.length() );
|
||||||
|
} else {
|
||||||
|
File[] subFiles = dir.listFiles();
|
||||||
|
|
||||||
|
if (subFiles != null && subFiles.length > 0) {
|
||||||
|
for (File file : subFiles) {
|
||||||
|
if (file.isFile()) {
|
||||||
|
size += entrySize( file.length() );
|
||||||
|
} else {
|
||||||
|
size += tarSize( file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Empty folder header
|
||||||
|
return TarConstants.HEADER_BLOCK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long entrySize(long fileSize) {
|
||||||
|
long size = 0;
|
||||||
|
size += TarConstants.HEADER_BLOCK; // Header
|
||||||
|
size += fileSize; // File size
|
||||||
|
|
||||||
|
long extra = size % TarConstants.DATA_BLOCK;
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
size += ( TarConstants.DATA_BLOCK - extra ); // pad
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
185
src/test/java/org/kamranzafar/jtar/JTarTest.java
Executable file
185
src/test/java/org/kamranzafar/jtar/JTarTest.java
Executable file
@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.kamranzafar.jtar.TarEntry;
|
||||||
|
import org.kamranzafar.jtar.TarInputStream;
|
||||||
|
import org.kamranzafar.jtar.TarOutputStream;
|
||||||
|
import org.kamranzafar.jtar.TarUtils;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class JTarTest {
|
||||||
|
static final int BUFFER = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tar the given folder
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void tar() throws IOException {
|
||||||
|
FileOutputStream dest = new FileOutputStream("c:/tmp/tartest/test.tar");
|
||||||
|
TarOutputStream out = new TarOutputStream(
|
||||||
|
new BufferedOutputStream(dest));
|
||||||
|
|
||||||
|
tarFolder(null, "c:/tmp/untartest", out);
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
System.out.println("Calculated tar size: "
|
||||||
|
+ TarUtils.calculateTarSize(new File("c:/tmp/untartest")));
|
||||||
|
System.out.println("Actual tar size: "
|
||||||
|
+ new File("c:/tmp/tartest/test.tar").length());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Untar the tar file
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void untarTarFile() throws IOException {
|
||||||
|
String destFolder = "c:/tmp/untartest";
|
||||||
|
File zf = new File("c:/tmp/tartest/test.tar");
|
||||||
|
|
||||||
|
TarInputStream tis = new TarInputStream(new BufferedInputStream(
|
||||||
|
new FileInputStream(zf)));
|
||||||
|
|
||||||
|
untar(tis, destFolder);
|
||||||
|
|
||||||
|
tis.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Untar the gzipped-tar file
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void untarTGzFile() throws IOException {
|
||||||
|
String destFolder = "c:/tmp/untartest";
|
||||||
|
File zf = new File("c:/tmp/test.tar.gz");
|
||||||
|
|
||||||
|
TarInputStream tis = new TarInputStream(new BufferedInputStream(
|
||||||
|
new GZIPInputStream(new FileInputStream(zf))));
|
||||||
|
|
||||||
|
untar(tis, destFolder);
|
||||||
|
|
||||||
|
tis.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void untar(TarInputStream tis, String destFolder)
|
||||||
|
throws IOException {
|
||||||
|
BufferedOutputStream dest = null;
|
||||||
|
|
||||||
|
TarEntry entry;
|
||||||
|
while ((entry = tis.getNextEntry()) != null) {
|
||||||
|
System.out.println("Extracting: " + entry.getName());
|
||||||
|
int count;
|
||||||
|
byte data[] = new byte[BUFFER];
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
new File(destFolder + "/" + entry.getName()).mkdirs();
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
int di = entry.getName().lastIndexOf('/');
|
||||||
|
if (di != -1) {
|
||||||
|
new File(destFolder + "/"
|
||||||
|
+ entry.getName().substring(0, di)).mkdirs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream fos = new FileOutputStream(destFolder + "/"
|
||||||
|
+ entry.getName());
|
||||||
|
dest = new BufferedOutputStream(fos);
|
||||||
|
|
||||||
|
while ((count = tis.read(data)) != -1) {
|
||||||
|
dest.write(data, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.flush();
|
||||||
|
dest.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tarFolder(String parent, String path, TarOutputStream out)
|
||||||
|
throws IOException {
|
||||||
|
BufferedInputStream origin = null;
|
||||||
|
File f = new File(path);
|
||||||
|
String files[] = f.list();
|
||||||
|
|
||||||
|
// is file
|
||||||
|
if (files == null) {
|
||||||
|
files = new String[1];
|
||||||
|
files[0] = f.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = ((parent == null) ? (f.isFile()) ? "" : f.getName() + "/"
|
||||||
|
: parent + f.getName() + "/");
|
||||||
|
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
System.out.println("Adding: " + files[i]);
|
||||||
|
File fe = f;
|
||||||
|
byte data[] = new byte[BUFFER];
|
||||||
|
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
fe = new File(f, files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fe.isDirectory()) {
|
||||||
|
String[] fl = fe.list();
|
||||||
|
if (fl != null && fl.length != 0) {
|
||||||
|
tarFolder(parent, fe.getPath(), out);
|
||||||
|
} else {
|
||||||
|
TarEntry entry = new TarEntry(fe, parent + files[i] + "/");
|
||||||
|
out.putNextEntry(entry);
|
||||||
|
out.write(new byte[(int) entry.getSize()]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream fi = new FileInputStream(fe);
|
||||||
|
origin = new BufferedInputStream(fi);
|
||||||
|
|
||||||
|
TarEntry entry = new TarEntry(fe, parent + files[i]);
|
||||||
|
out.putNextEntry(entry);
|
||||||
|
|
||||||
|
int count;
|
||||||
|
int bc = 0;
|
||||||
|
while ((count = origin.read(data)) != -1) {
|
||||||
|
out.write(data, 0, count);
|
||||||
|
bc += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
origin.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user