Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Extended Time data #76

Merged
merged 7 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
testImplementation 'org.mockito:mockito-core:4.3.1'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'com.tngtech.archunit:archunit-junit5:0.23.0'
testImplementation 'org.junit-pioneer:junit-pioneer:1.6.1'
}

group = 'com.github.junrar'
Expand Down
240 changes: 223 additions & 17 deletions src/main/java/com/github/junrar/rarfile/FileHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;


/**
Expand All @@ -40,6 +43,8 @@ public class FileHeader extends BlockHeader {

private static final byte NEWLHD_SIZE = 32;

private static final long NANOS_PER_UNIT = 100L; // 100ns units

private final long unpSize;

private final HostSystem hostOS;
Expand All @@ -65,13 +70,13 @@ public class FileHeader extends BlockHeader {

private final byte[] salt = new byte[SALT_SIZE];

private Date mTime;
private FileTime mTime;

private Date cTime;
private FileTime cTime;

private Date aTime;
private FileTime aTime;

private Date arcTime;
private FileTime arcTime;

private long fullPackSize;

Expand Down Expand Up @@ -180,9 +185,71 @@ public FileHeader(BlockHeader bh, byte[] fileHeader) {
position++;
}
}
mTime = getDateDos(fileTime);
// TODO rartime -> extended

mTime = FileTime.fromMillis(getDateDos(fileTime));

if (hasExtTime()) {
short extTimeFlags = Raw.readShortLittleEndian(fileHeader, position);
position += 2;

TimePositionTuple mTimeTuple = parseExtTime(12, extTimeFlags, fileHeader, position, mTime);
mTime = mTimeTuple.time;
position = mTimeTuple.position;

TimePositionTuple cTimeTuple = parseExtTime(8, extTimeFlags, fileHeader, position);
cTime = cTimeTuple.time;
position = cTimeTuple.position;

TimePositionTuple aTimeTuple = parseExtTime(4, extTimeFlags, fileHeader, position);
aTime = aTimeTuple.time;
position = aTimeTuple.position;

TimePositionTuple arcTimeTuple = parseExtTime(0, extTimeFlags, fileHeader, position);
arcTime = arcTimeTuple.time;
position = arcTimeTuple.position;
}
}

private static final class TimePositionTuple {
private final int position;
private final FileTime time;

private TimePositionTuple(int position, FileTime time) {
this.position = position;
this.time = time;
}
}

private static TimePositionTuple parseExtTime(int shift, short flags, byte[] fileHeader, int position) {
return parseExtTime(shift, flags, fileHeader, position, null);
}

private static TimePositionTuple parseExtTime(int shift, short flags, byte[] fileHeader, int position, FileTime baseTime) {
int flag = flags >>> shift;
if ((flag & 0x8) != 0) {
long seconds;
if (baseTime != null) {
seconds = baseTime.to(TimeUnit.SECONDS);
} else {
seconds = TimeUnit.MILLISECONDS.toSeconds(getDateDos(Raw.readIntLittleEndian(fileHeader, position)));
position += 4;
}
int count = flag & 0x3;
long remainder = 0;
for (int i = 0; i < count; i++) {
int b = fileHeader[position] & 0xff;
remainder = (b << 16) | (remainder >>> 8);
position++;
}
long nanos = remainder * NANOS_PER_UNIT;
if ((flag & 0x4) != 0) {
andrebrait marked this conversation as resolved.
Show resolved Hide resolved
nanos += TimeUnit.SECONDS.toNanos(1);
}
FileTime time = FileTime.from(Instant.ofEpochSecond(seconds, nanos));
return new TimePositionTuple(position, time);
} else {
return new TimePositionTuple(position, baseTime);
}
}

@Override
Expand All @@ -192,7 +259,10 @@ public void print() {
StringBuilder str = new StringBuilder();
str.append("unpSize: ").append(getUnpSize());
str.append("\nHostOS: ").append(hostOS.name());
str.append("\nMDate: ").append(mTime);
str.append("\nMTime: ").append(mTime);
str.append("\nCTime: ").append(cTime);
str.append("\nATime: ").append(aTime);
str.append("\nArcTime: ").append(arcTime);
str.append("\nFileName: ").append(fileName);
str.append("\nFileNameW: ").append(fileNameW);
str.append("\nunpMethod: ").append(Integer.toHexString(getUnpMethod()));
Expand All @@ -216,7 +286,7 @@ public void print() {
}
}

private Date getDateDos(int time) {
private static long getDateDos(int time) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, (time >>> 25) + 1980);
cal.set(Calendar.MONTH, ((time >>> 21) & 0x0f) - 1);
Expand All @@ -225,33 +295,134 @@ private Date getDateDos(int time) {
cal.set(Calendar.MINUTE, (time >>> 5) & 0x3f);
cal.set(Calendar.SECOND, (time & 0x1f) * 2);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
return cal.getTimeInMillis();
}

public Date getArcTime() {
private static Date toDate(FileTime time) {
return time != null ? new Date(time.toMillis()) : null;
}

private static FileTime toFileTime(Date time) {
return time != null ? FileTime.fromMillis(time.getTime()) : null;
}

/**
* The time in which the file was archived.
* Corresponds to te {@link FileHeader#arcTime} field.
*
* @return the timestamp, or null if absent.
*/
public FileTime getArchivalTime() {
return arcTime;
}

/**
* Sets the time in which the file was archived.
* Corresponds to te {@link FileHeader#arcTime} field.
*
* @param archivalTime the timestamp, or null to clear it.
*/
public void setArchivalTime(FileTime archivalTime) {
this.arcTime = archivalTime;
}

/**
* Gets {@link FileHeader#getArchivalTime()} as a {@link Date}.
* The maximum granularity is reduced from microseconds to milliseconds.
*
* @return the date, or null if absent.
*/
public Date getArcTime() {
return toDate(getArchivalTime());
}

/**
* Sets {@link FileHeader#setArchivalTime(FileTime)} from a {@link Date}.
*
* @param arcTime the date, or null to clear it.
*/
public void setArcTime(Date arcTime) {
this.arcTime = arcTime;
setArchivalTime(toFileTime(arcTime));
}

public Date getATime() {
/**
* The time in which the file was last accessed.
* Corresponds to te {@link FileHeader#aTime} field.
*
* @return the timestamp, or null if absent.
*/
public FileTime getLastAccessTime() {
return aTime;
}

public void setATime(Date time) {
/**
* Sets the time in which the file was last accessed.
* Corresponds to te {@link FileHeader#aTime} field.
*
* @param time the timestamp, or null to clear it.
*/
public void setLastAccessTime(FileTime time) {
aTime = time;
}

public Date getCTime() {
/**
* Gets {@link FileHeader#getLastAccessTime()} as a {@link Date}.
* The maximum granularity is reduced from microseconds to milliseconds.
*
* @return the date, or null if absent.
*/
public Date getATime() {
return toDate(getLastAccessTime());
}

/**
* Sets {@link FileHeader#setLastAccessTime(FileTime)} from a {@link Date}.
*
* @param time the date, or null to clear it.
*/
public void setATime(Date time) {
setLastAccessTime(toFileTime(time));
}

/**
* The time in which the file was created.
* Corresponds to te {@link FileHeader#cTime} field.
*
* @return the timestamp, or null if absent.
*/
public FileTime getCreationTime() {
return cTime;
}

public void setCTime(Date time) {
/**
* Sets the time in which the file was created.
* Corresponds to te {@link FileHeader#cTime} field.
*
* @param time the timestamp, or null to clear it.
*/
public void setCreationTime(FileTime time) {
cTime = time;
}

/**
* Gets {@link FileHeader#getCreationTime()} as a {@link Date}.
* The maximum granularity is reduced from microseconds to milliseconds.
*
* @return the date, or null if absent.
*/
public Date getCTime() {
return toDate(getCreationTime());
}

/**
* Sets {@link FileHeader#setCreationTime(FileTime)} from a {@link Date}.
*
* @param time the date, or null to clear it.
*/
public void setCTime(Date time) {
setCreationTime(toFileTime(time));
}

public int getFileAttr() {
return fileAttr;
}
Expand Down Expand Up @@ -310,14 +481,45 @@ public HostSystem getHostOS() {
return hostOS;
}

public Date getMTime() {
/**
* The time in which the file was last modified.
* Corresponds to te {@link FileHeader#mTime} field.
*
* @return the timestamp, or null if absent.
*/
public FileTime getLastModifiedTime() {
return mTime;
}

public void setMTime(Date time) {
/**
* Sets the time in which the file was last modified.
* Corresponds to te {@link FileHeader#mTime} field.
*
* @param time the timestamp, or null to clear it.
*/
public void setLastModifiedTime(FileTime time) {
mTime = time;
}

/**
* Gets {@link FileHeader#getLastModifiedTime()} as a {@link Date}.
* The maximum granularity is reduced from microseconds to milliseconds.
*
* @return the date, or null if absent.
*/
public Date getMTime() {
return toDate(getLastModifiedTime());
}

/**
* Sets {@link FileHeader#setLastModifiedTime(FileTime)} from a {@link Date}.
*
* @param time the date, or null to clear it.
*/
public void setMTime(Date time) {
setLastModifiedTime(toFileTime(time));
}

public short getNameSize() {
return nameSize;
}
Expand Down Expand Up @@ -416,6 +618,10 @@ public boolean hasSalt() {
return (flags & LHD_SALT) != 0;
}

public boolean hasExtTime() {
return (flags & LHD_EXTTIME) != 0;
}

public boolean isLargeBlock() {
return (flags & LHD_LARGE) != 0;
}
Expand Down