Created
April 30, 2023 14:51
-
-
Save kirumbik/35c29ada125ef51472bedcf1fd75b5ad to your computer and use it in GitHub Desktop.
DynamicFile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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