Skip to content

Instantly share code, notes, and snippets.

@kirumbik
Created April 30, 2023 14:51
Show Gist options
  • Save kirumbik/35c29ada125ef51472bedcf1fd75b5ad to your computer and use it in GitHub Desktop.
Save kirumbik/35c29ada125ef51472bedcf1fd75b5ad to your computer and use it in GitHub Desktop.
DynamicFile
public class DynamicFile {
private static final String DOWNLOAD_SUFFIX = ".download";
private static final String ACCESS_MODE_RW = "rw";
private static final String ACCESS_MODE_R = "r";
private static final int READ_LOCK_TIME_MS = 50;
private static final int WAIT_FOR_BYTES_MS = 350;
private File mStableFile;
private File mTempFile;
private final Lock mDynamicFileWritingLock = new ReentrantLock();
private DynamicAccessFile mDynamicAccessFile;
private DynamicFileStream mDynamicFileStream;
private volatile long mTotalSize = 0;
private volatile boolean mLoaded = false;
public DynamicFile (File aFile, String accessMode) throws FileNotFoundException {
// link to stable file
mStableFile = aFile;
// total size
mTotalSize = ConfigUtils.getLong(mBFService, mStableFile.getAbsolutePath() + FILE_SIZE_SUFFIX);
if( mStableFile.exists() && mStableFile.length() == mTotalSize && mTotalSize != 0) {
// file already loaded => local
mDynamicAccessFile = new DynamicAccessFile(mStableFile, accessMode);
mDynamicAccessFile.setLoaded();
mLoaded = true;
}
else {
// link to temporary file
mTempFile = new File( mStableFile.getAbsolutePath() + DOWNLOAD_SUFFIX );
// --
mDynamicAccessFile = new DynamicAccessFile(mTempFile, accessMode);
}
}
public void close () {
if( mDynamicAccessFile != null ) {
try {
mDynamicAccessFile.close();
} catch (IOException e) {
/* ... */
}
}
}
public DynamicAccessFile getDynamicAccessFile () {
return mDynamicAccessFile;
}
public boolean tryLockForWriting () {
if( mDynamicFileWritingLock.tryLock() ) {
// reset downloaded counter
mDynamicAccessFile.mDownloaded = 0;
return true;
}
return false;
}
public void unlockForWriting () {
mDynamicFileWritingLock.unlock();
}
public void setTotalSize ( long size ) {
mTotalSize = size;
ConfigUtils.setLong(mBFService, mStableFile.getAbsolutePath() + FILE_SIZE_SUFFIX, mTotalSize);
}
public long getTotalSize () {
return mTotalSize;
}
public boolean isLoaded () {
return mLoaded;
}
public void onLoaded () {
mDynamicAccessFile.lock();
try {
mDynamicAccessFile.renameTo(mStableFile);
mDynamicAccessFile.setLoaded();
mLoaded = true;
// TODO: add IOException handling
} finally {
mDynamicAccessFile.unlock();
}
}
public InputStream getInputStream () {
if( mDynamicFileStream != null ) {
try {
mDynamicFileStream.close();
} catch (IOException e) {
// ...
}
}
mDynamicFileStream = new DynamicFileStream();
try {
mDynamicAccessFile.seek(0);
} catch (IOException e) {
// ...
}
return mDynamicFileStream;
}
public class DynamicFileStream extends InputStream {
boolean closed = false;
@Override
public void close() throws IOException {
closed = true;
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
if( closed )
throw new IOException();
return mDynamicAccessFile.read(buffer, offset, length);
}
@Override
public int read() throws IOException {
if( closed )
throw new IOException();
return mDynamicAccessFile.read();
}
@Override
public int available() throws IOException {
return (int) (mTotalSize - mDynamicAccessFile.getFilePointer());
}
}
public class DynamicAccessFile extends RandomAccessFile {
private DynamicFileProgressInterface mDynamicFileProgressInterface;
private volatile long mDownloaded = 0;
private AtomicLong mLoadedSize = new AtomicLong(0);
private final Lock mLock = new ReentrantLock();
private File mDAFile;
public DynamicAccessFile(File file, String accessMode) throws FileNotFoundException {
super(file, accessMode);
mDAFile = file;
mLoadedSize.set(mDAFile.length());
try {
super.seek(0);
} catch (IOException e) {
// ...
}
}
public void setDynamicFileProgressInterface ( DynamicFileProgressInterface aDynamicFileProgressInterface ) {
mDynamicFileProgressInterface = aDynamicFileProgressInterface;
}
private void setLoaded () {
mLoadedSize.set(DynamicFile.this.mTotalSize);
}
public File getFile () {
return mDAFile;
}
public void lock () {
mLock.lock();
}
public void unlock () {
mLock.unlock();
}
public void renameTo ( File name ) {
mDAFile.renameTo(name);
}
public String getAbsolutePath () {
return mDAFile.getAbsolutePath();
}
public long lastModified () {
return mDAFile.lastModified();
}
public long length () {
return mDAFile.length();
}
public long getTotalSize () {
return mTotalSize;
}
public void appendBytes (byte[] buffer, int offset, int count) throws IOException {
mLock.lock();
try {
long read_pos = this.getFilePointer();
super.seek(mLoadedSize.get());
super.write(buffer, offset, count);
super.seek(read_pos); // return pointer back to read position
mDownloaded += count;
mLoadedSize.set(count - offset + mLoadedSize.get());
} finally {
mLock.unlock();
if( mDynamicFileProgressInterface != null )
mDynamicFileProgressInterface.onProgressChanged(mDownloaded);
}
}
@Override
public void seek(long offset) throws IOException {
mLock.lock();
try {
super.seek(offset);
} finally {
mLock.unlock();
}
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
if(mLoadedSize.get() == mTotalSize) {
return super.read(buffer, byteOffset, byteCount);
}
else {
while(true) {
try {
if( mLock.tryLock(READ_LOCK_TIME_MS, TimeUnit.MILLISECONDS) ) {
// got bytes
long got_bytes = mLoadedSize.get() - this.getFilePointer();
if( got_bytes > 0 ) {
// we have bytes for reading
int i = -1;
try {
// try to read
if( got_bytes > byteCount ) {
i = super.read(buffer, byteOffset, byteCount);
}
else {
i = super.read(buffer, byteOffset, (int) got_bytes);
}
}
finally {
mLock.unlock();
}
return i;
}
else {
mLock.unlock();
// wait for bytes
Thread.sleep(WAIT_FOR_BYTES_MS);
}
if( !mIsLoading.get() ) {
return -1;
}
if( getFilePointer() >= mTotalSize - 1 )
return -1;
}
} catch (InterruptedException e) {
mLock.unlock();
return -1;
}
}
}
}
/**
* @deprecated
*/
@Override
public int read() throws IOException {
throw new IOException("Non-bocking method is deprecated");
}
/**
* @deprecated
*/
@Override
public int read(byte[] buffer) throws IOException {
throw new IOException("Non-bocking method is deprecated");
}
/**
* @deprecated
*/
@Override
public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
throw new IOException("Non-bocking method is deprecated");
}
/**
* @deprecated
*/
@Override
public void write(byte[] buffer) throws IOException {
throw new IOException("Non-bocking method is deprecated");
}
/**
* @deprecated
*/
@Override
public void write(int oneByte) throws IOException {
throw new IOException("Non-bocking method is deprecated");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment