New DiskLoader with gzip and zip support.

Flow of disk image selection now goes through DiskLoader
with onImageReady/onImageCancelled callbacks.  DiskLoader
handles downloading, ungzipping, and extracting from zipfile.
This commit is contained in:
James Sanford 2012-11-04 21:22:27 -08:00
parent ee103891c5
commit 7bde7861c2
15 changed files with 604 additions and 254 deletions

View File

@ -20,41 +20,51 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:priority="999">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" android:pathPattern=".*\\.2mg" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" android:pathPattern=".*\\.dsk" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" android:pathPattern=".*\\.nib" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" android:pathPattern=".*\\.hdv" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" android:pathPattern=".*\\.po" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" android:pathPattern=".*\\.do" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.zip" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.ZIP" />
<!-- Yeah, this is pretty much awful. Blame PatternMatcher -->
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.2mg" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.dsk" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.nib" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.hdv" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.po" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.do" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.2MG" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.DSK" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.NIB" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.HDV" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.PO" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.DO" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.2mg.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.dsk.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.nib.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.hdv.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.po.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.do.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.2MG.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.DSK.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.NIB.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.HDV.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.PO.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.DO.gz" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.2MG.GZ" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.DSK.GZ" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.NIB.GZ" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.HDV.GZ" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.PO.GZ" />
<data android:scheme="file" android:host="*" android:mimeType="*/*" android:pathPattern=".*\\.DO.GZ" />
</intent-filter>
</activity>

View File

@ -263,7 +263,7 @@ void android_config_init(char *output, int maxlen) {
}
JNIEXPORT void JNICALL
Java_com_froop_app_kegs_AssetImages_nativeSync( JNIEnv* env, jobject thiz) {
Java_com_froop_app_kegs_DiskLoader_nativeSync( JNIEnv* env, jobject thiz) {
sync();
sync();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -6,6 +6,9 @@
<string name="rom_title">Checking for ROM</string>
<string name="rom_check">Checking for ROM...</string>
<string name="rom_error">Unable to obtain ROM. Find ROM.01 or ROM.03 and put it in /Android/data/com.froop.app.kegs/files/ on your SD Card.</string>
<string name="image_error">Unable to load requested disk image.</string>
<string name="error_title">Error</string>
<string name="dialog_ok">OK</string>
<string-array name="rom_choices">
<item>I have a ROM 01</item>
@ -21,7 +24,8 @@
</string-array>
<string name="diskimage_title">Choose a disk</string>
<string name="asset_loading">Preparing disk image...</string>
<string name="progress_message">Preparing disk image...</string>
<string name="zipfile_nomatches">No disk images found</string>
<string-array name="swapdisk_choices">
<item>Ignore this disk</item>

View File

@ -46,7 +46,6 @@ class AssetImages extends AsyncTask<Void, Void, Boolean> {
mConfigFile.ensureAssetCopied(mConfigFile.getImagePath(),
"System 6 Shareware.zip", "System 6.hdv");
// TODO: could check to make sure they actually exist now.
nativeSync();
return true;
}
@ -57,7 +56,4 @@ class AssetImages extends AsyncTask<Void, Void, Boolean> {
protected void onPostExecute(final Boolean result) {
mNotify.onAssetsReady(result);
}
// See jni/android_driver.c:nativeSync()
private native void nativeSync();
}

View File

@ -35,6 +35,10 @@ class ConfigFile {
return mConfigPath + "/images";
}
public String getCachePath() {
return mContext.getExternalCacheDir().getPath();
}
public void ensureAssetCopied(String destPath, String zipfile, String exampleFile) {
// We only check for a local copy of a single file before unzipping...
final File local_copy = new File(destPath, exampleFile);

View File

@ -0,0 +1,53 @@
package com.froop.app.kegs;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import com.actionbarsherlock.app.SherlockDialogFragment;
class ErrorDialogFragment extends SherlockDialogFragment {
private int mMessage;
private Runnable mRunnable;
public ErrorDialogFragment(int textId, Runnable runnable) {
super();
mMessage = textId;
mRunnable = runnable; // ok if null
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.error_title);
builder.setIcon(R.drawable.ic_dialog_alert_holo_dark);
builder.setMessage(mMessage);
builder.setPositiveButton(R.string.dialog_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int button) {
dismiss();
}
});
final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false); // prevent accidental dismissal
return dialog;
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
if (mRunnable != null) {
mRunnable.run();
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mRunnable != null) {
mRunnable.run();
}
}
}

View File

@ -26,14 +26,17 @@ import com.actionbarsherlock.app.SherlockDialogFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.StickyReset, AssetImages.AssetsReady, DownloadImage.DownloadReady {
import java.util.ArrayDeque;
public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.StickyReset, AssetImages.AssetsReady, DiskLoader.ImageReady {
private static final String FRAGMENT_ROM = "rom";
private static final String FRAGMENT_DOWNLOAD = "download";
private static final String FRAGMENT_ERROR = "error";
private static final String FRAGMENT_SPEED = "speed";
private static final String FRAGMENT_DISKIMAGE = "diskimage";
private static final String FRAGMENT_ASSET = "asset";
private static final String FRAGMENT_SWAPDISK = "swapdisk";
private static final String FRAGMENT_ZIPDISK = "zipdisk";
private static final String FRAGMENT_LOADING = "loading";
private ConfigFile mConfigFile;
private KegsThread mKegsThread;
@ -45,16 +48,27 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
private KegsKeyboard mKegsKeyboard;
private TouchJoystick mJoystick;
private boolean mAssetsReady = false;
private boolean mDownloadReady = false;
private boolean mDownloadStarted = false;
private boolean mDownloadAborted = false;
private boolean mModeMouse = true;
private boolean mHaveNewIntent = false;
private long mScreenSizeTime = 0;
private boolean mPaused = false;
final ArrayDeque<Runnable> mResumeQueue = new ArrayDeque<Runnable>();
private DiskLoader mDiskLoader = null;
private void withUIActive(final Runnable runnable) {
if(!mPaused) {
runnable.run();
} else {
mResumeQueue.add(runnable);
}
}
public void onAssetsReady(boolean result) {
// TODO: deal with error conditions from assets as a warning.
}
private View.OnClickListener mButtonClick = new View.OnClickListener() {
public void onClick(View v) {
final int click_id = v.getId();
@ -106,81 +120,109 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
((ToggleButton)findViewById(R.id.key_closed_apple)).setChecked(false);
}
public void onAssetsReady(boolean success) {
// TODO FIXME: this needs to throw an error if it fails.
mAssetsReady = success;
}
public void onDownloadReady(boolean success) {
// TODO FIXME: this needs to throw an error if it fails.
mDownloadReady = success;
protected void loadDiskImage(final DiskImage image) {
if (image.action == DiskImage.BOOT) {
getThread().doPowerOff();
}
loadDiskImageWhenReady(image);
}
private void loadDiskImageWhenReady(final DiskImage image) {
final AssetFragment frag = (AssetFragment)getSupportFragmentManager().findFragmentByTag(FRAGMENT_ASSET);
// Only start download when the assets are ready.
if (mAssetsReady && !mDownloadStarted) {
mDownloadStarted = true;
final String imagePath = mConfigFile.getImagePath();
if (android.os.Build.VERSION.SDK_INT >= 11) {
new DownloadImage(this, imagePath, image).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
new DownloadImage(this, imagePath, image).execute();
}
}
// This code is lame.
if (mDownloadAborted) {
if (frag != null) {
frag.dismiss();
}
if (image.primary) {
getThread().allowPowerOn();
}
return; // don't schedule another pass.
}
boolean nativeReady;
if (image.primary) {
if (image.action == DiskImage.BOOT) {
nativeReady = getThread().nowWaitingForPowerOn();
} else {
nativeReady = true;
}
if (!nativeReady || !mAssetsReady || !mDownloadReady) {
if (frag == null && (!mAssetsReady || !mDownloadReady)) {
// Only the asset part will take time, so only show the dialog
// when waiting for the asset.
final SherlockDialogFragment assetProgress = new AssetFragment();
assetProgress.show(getSupportFragmentManager(), FRAGMENT_ASSET);
}
if (!nativeReady) {
mKegsView.postDelayed(new Runnable() {
public void run() { loadDiskImageWhenReady(image); }
public void run() {
loadDiskImageWhenReady(image);
}
}, 100);
} else {
if (frag != null) {
frag.dismiss();
}
if (image.primary) {
mConfigFile.setConfig(image);
getThread().allowPowerOn();
} else {
getThread().getEventQueue().add(image.getDiskImageEvent());
}
return;
}
withUIActive(new Runnable() {
public void run() {
if (image.action == DiskImage.BOOT) {
mConfigFile.setConfig(image);
getThread().allowPowerOn();
} else if (image.action == DiskImage.SWAP) {
getThread().getEventQueue().add(image.getDiskImageEvent());
}
}
});
}
protected void loadDiskImage(DiskImage image) {
if (image.primary) {
getThread().doPowerOff();
public void onImageReady(final boolean result, final DiskImage image) {
mDiskLoader = null;
withUIActive(new Runnable() {
public void run() {
dismissFragment(FRAGMENT_LOADING);
if (!result) {
// TODO: Consider not showing the error if it was cancelled.
new ErrorDialogFragment(R.string.image_error, null).show(getSupportFragmentManager(), FRAGMENT_ERROR);
} else if (image.action != DiskImage.ASK) {
loadDiskImage(image);
} else {
new SwapDiskFragment(image).show(getSupportFragmentManager(),
FRAGMENT_SWAPDISK);
}
}
});
}
public void prepareDiskImage(DiskImage image) {
dismissFragment(FRAGMENT_ROM); // Note: should probably bail if visible.
dismissFragment(FRAGMENT_SPEED);
dismissFragment(FRAGMENT_DISKIMAGE);
dismissFragment(FRAGMENT_SWAPDISK);
dismissFragment(FRAGMENT_ZIPDISK);
if (image.filename.endsWith(".zip") || image.filename.endsWith(".ZIP")) {
final ZipDiskFragment zip = new ZipDiskFragment(image);
if (zip.needsDialog()) {
zip.show(getSupportFragmentManager(), FRAGMENT_ZIPDISK);
return;
} else {
image = zip.getFirstImage();
}
}
runDiskLoader(image);
}
mDownloadStarted = false;
mDownloadReady = false;
mDownloadAborted = false;
// So that other fragments can transition into the DiskLoader.
public void runDiskLoader(final DiskImage image) {
final Runnable cancel = new Runnable() {
public void run() {
if (mDiskLoader != null) {
mDiskLoader.cancel(true);
mDiskLoader = null;
}
}
};
loadDiskImageWhenReady(image);
final DiskLoader.ImageReady notify = this;
withUIActive(new Runnable() {
public void run() {
// In case there was already another DiskLoader.
cancel.run();
dismissFragment(FRAGMENT_LOADING);
mDiskLoader = new DiskLoader(notify, mConfigFile, image);
if (mDiskLoader.willBeSlow()) {
new SpecialProgressDialog(cancel).show(getSupportFragmentManager(),
FRAGMENT_LOADING);
}
if (android.os.Build.VERSION.SDK_INT >= 11) {
mDiskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mDiskLoader.execute();
}
}
});
}
private DiskImage getBootDiskImage(Intent intent) {
@ -200,14 +242,20 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
private void boot() {
final DiskImage image = getBootDiskImage(getIntent());
if (image != null) {
loadDiskImage(image);
prepareDiskImage(image);
} else {
mConfigFile.defaultConfig();
getThread().allowPowerOn();
mKegsView.postDelayed(new Runnable() { public void run() {
new DiskImageFragment().show(getSupportFragmentManager(),
FRAGMENT_DISKIMAGE);
} }, 1000);
mKegsView.postDelayed(new Runnable() {
public void run() {
withUIActive(new Runnable() {
public void run() {
new DiskImageFragment().show(getSupportFragmentManager(),
FRAGMENT_DISKIMAGE);
}
});
}
}, 1000);
}
}
@ -229,19 +277,23 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
"http://jsan.co/KEGS/" + mRomfile,
mConfigFile.getConfigPath() + "/" + mRomfile);
}
protected void onPostExecute(Boolean success) {
final SherlockDialogFragment frag = (SherlockDialogFragment)getSupportFragmentManager().findFragmentByTag(FRAGMENT_DOWNLOAD);
if (frag != null) {
frag.dismiss();
}
if (!success) {
if (!isCancelled()) {
final SherlockDialogFragment dialog = new ErrorDialogFragment();
dialog.show(getSupportFragmentManager(), FRAGMENT_ERROR);
protected void onPostExecute(final Boolean success) {
withUIActive(new Runnable() {
public void run() {
final SherlockDialogFragment frag = (SherlockDialogFragment)getSupportFragmentManager().findFragmentByTag(FRAGMENT_DOWNLOAD);
if (frag != null) {
frag.dismiss();
}
if (!success) {
if (!isCancelled()) {
final Runnable runnable = new Runnable() { public void run() { finish(); } };
new ErrorDialogFragment(R.string.rom_error, runnable).show(getSupportFragmentManager(), FRAGMENT_ERROR);
}
} else {
boot();
}
}
} else {
boot();
}
});
}
}
@ -269,53 +321,6 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
}
}
class ErrorDialogFragment extends SherlockDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(getResources().getText(R.string.rom_error));
final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false); // prevent accidental dismissal
return dialog;
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
finish();
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
finish();
}
}
class AssetFragment extends SherlockDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
ProgressDialog dialog = new ProgressDialog(getActivity());
// TODO: should probably use an XML layout for this.
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage(getResources().getText(R.string.asset_loading));
dialog.setProgressNumberFormat(null);
if (android.os.Build.VERSION.SDK_INT >= 11) {
dialog.setProgressPercentFormat(null);
}
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
mDownloadAborted = true;
}
}
public KegsThread getThread() {
return mKegsThread;
}
@ -447,22 +452,18 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
if (image == null) {
return; // Nothing to do.
}
dismissFragment(FRAGMENT_ROM); // Note: should probably bail if visible.
dismissFragment(FRAGMENT_SPEED);
dismissFragment(FRAGMENT_DISKIMAGE);
dismissFragment(FRAGMENT_SWAPDISK);
// Ask the user what to do with this new disk image.
new SwapDiskFragment(image).show(getSupportFragmentManager(),
FRAGMENT_SWAPDISK);
image.action = DiskImage.ASK;
prepareDiskImage(image);
}
@Override
protected void onNewIntent(Intent intent) {
protected void onNewIntent(final Intent intent) {
// "An activity will always be paused before receiving a new intent."
// Being paused means we can't show a dialog here. Tell onResume to do it.
setIntent(intent);
mHaveNewIntent = true;
withUIActive(new Runnable() {
public void run() {
handleNewIntent(intent);
}
});
}
@Override
@ -530,8 +531,7 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
final String romfile = mConfigFile.whichRomFile();
if (romfile == null) {
final SherlockDialogFragment chooseRom = new RomDialogFragment();
chooseRom.show(getSupportFragmentManager(), FRAGMENT_ROM);
new RomDialogFragment().show(getSupportFragmentManager(), FRAGMENT_ROM);
} else {
boot();
}
@ -544,6 +544,7 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
if (mKegsView instanceof KegsViewGL) {
mKegsView.onPause();
}
mPaused = true;
}
@Override
@ -553,15 +554,18 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
if (mKegsView instanceof KegsViewGL) {
mKegsView.onResume();
}
if (mHaveNewIntent) {
mHaveNewIntent = false;
handleNewIntent(getIntent());
mPaused = false;
Runnable runnable;
while ((runnable = mResumeQueue.poll()) != null) {
runnable.run();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mResumeQueue.clear();
Log.w("kegs", "onDestroy called, halting");
// Force process to exit. We cannot handle another onCreate
// once a KegsView has been active. (JNI kegsmain has already run)

View File

@ -0,0 +1,42 @@
package com.froop.app.kegs;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import com.actionbarsherlock.app.SherlockDialogFragment;
// A predefined progress dialog that calls a Runner when it's cancelled.
public class SpecialProgressDialog extends SherlockDialogFragment {
private Runnable mCancelRunnable;
public SpecialProgressDialog(Runnable runnable) {
super();
mCancelRunnable = runnable;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
ProgressDialog dialog = new ProgressDialog(getActivity());
// TODO: should probably use an XML layout for this.
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage(getResources().getText(R.string.progress_message));
dialog.setProgressNumberFormat(null);
if (android.os.Build.VERSION.SDK_INT >= 11) {
dialog.setProgressPercentFormat(null);
}
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
mCancelRunnable.run();
}
}

View File

@ -28,9 +28,10 @@ public class SwapDiskFragment extends SherlockDialogFragment {
if (item == 0) {
// ignore this image
} else if (item == 1) {
mImage.primary = false; // just a disk insert
mImage.action = DiskImage.SWAP; // just a disk insert
((KegsMain)getActivity()).loadDiskImage(mImage);
} else if (item == 2) {
mImage.action = DiskImage.BOOT; // was ASK
((KegsMain)getActivity()).loadDiskImage(mImage);
}
}

View File

@ -5,24 +5,40 @@ import java.lang.Integer;
import java.io.File;
class DiskImage {
// template
public static final String BOOT_SLOT_5 = "boot_slot_5";
public static final String BOOT_SLOT_6 = "boot_slot_6";
public static final String BOOT_SLOT_7 = "boot_slot_7";
public boolean primary = true;
// origin
public static final int ASSET = 0;
public static final int DOWNLOAD = 1;
public static final int LOCALFILE = 2;
public static final int ERROR = 3;
// action
public static final int BOOT = 0;
public static final int SWAP = 1;
public static final int ASK = 2;
public String filename;
public String drive;
public int speed;
public String template;
public int origin;
public int action;
public String extract_filename = null; // for extracting from zipfiles
// Example:
// DiskImage("XMAS_DEMO.2MG", "s5d1", 2, "boot_slot_5");
// DiskImage("XMAS_DEMO.2MG", "s5d1", 2, "boot_slot_5", LOCALFILE);
public DiskImage(final String filename, final String drive,
final int speed, final String template) {
final int speed, final String template, final int origin) {
this.filename = filename;
this.drive = drive;
this.speed = speed;
this.template = template;
this.origin = origin;
this.action = BOOT;
}
public static DiskImage fromPath(String path) {
@ -32,19 +48,44 @@ class DiskImage {
return null;
} else if (length >= 1024 * 1024) {
// TODO: should insert the disk and use the System 6 template
return new DiskImage(path, "s7d1", 3, BOOT_SLOT_7);
return new DiskImage(path, "s7d1", 3, BOOT_SLOT_7, LOCALFILE);
} else if (length >= 400 * 1024) {
return new DiskImage(path, "s5d1", 2, BOOT_SLOT_5);
return new DiskImage(path, "s5d1", 2, BOOT_SLOT_5, LOCALFILE);
} else if (length > 0) {
return new DiskImage(path, "s6d1", 1, BOOT_SLOT_6);
return new DiskImage(path, "s6d1", 1, BOOT_SLOT_6, LOCALFILE);
} else {
return null;
}
}
public static boolean isDiskImageFilename(String filename) {
if (filename.endsWith(".2mg") ||
filename.endsWith(".dsk") ||
filename.endsWith(".nib") ||
filename.endsWith(".hdv") ||
filename.endsWith(".po") ||
filename.endsWith(".do") ||
filename.endsWith(".bin") ||
filename.endsWith(".2MG") ||
filename.endsWith(".DSK") ||
filename.endsWith(".NIB") ||
filename.endsWith(".HDV") ||
filename.endsWith(".PO") ||
filename.endsWith(".DO") ||
filename.endsWith(".BIN")) {
return true;
} else {
return false;
}
}
public String getBaseFilename() {
int pos = this.filename.lastIndexOf("/");
return this.filename.substring(pos + 1);
}
public String getTitle() {
int pos = this.filename.lastIndexOf("/") + 1;
return this.filename.substring(pos);
return getBaseFilename();
}
public int getIconId() {

View File

@ -32,16 +32,16 @@ public class DiskImageFragment extends SherlockDialogFragment {
DiskImage image = null;
if (item == 0) {
image = new DiskImage("System 6.hdv", "s7d1", 3, DiskImage.BOOT_SLOT_7);
image = new DiskImage("System 6.hdv", "s7d1", 3, DiskImage.BOOT_SLOT_7, DiskImage.ASSET);
} else if (item == 1) {
image = new DiskImage("XMAS_DEMO.2MG", "s5d1", 2, DiskImage.BOOT_SLOT_5);
image = new DiskImage("XMAS_DEMO.2MG", "s5d1", 2, DiskImage.BOOT_SLOT_5, DiskImage.ASSET);
} else if (item == 2) {
// TODO: There should be an adapter on the ListView instead.
image = new DiskImage("prince.2mg", "s5d1", 2, DiskImage.BOOT_SLOT_5);
image = new DiskImage("prince.2mg", "s5d1", 2, DiskImage.BOOT_SLOT_5, DiskImage.DOWNLOAD);
}
if (image != null) {
((KegsMain)getActivity()).loadDiskImage(image);
((KegsMain)getActivity()).prepareDiskImage(image);
}
}
});

View File

@ -0,0 +1,146 @@
package com.froop.app.kegs;
import android.util.Log;
import android.os.AsyncTask;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
class DiskLoader extends AsyncTask<Void, Void, Boolean> {
interface ImageReady {
void onImageReady(boolean result, DiskImage image);
}
private ImageReady mNotify;
private DiskImage mImage;
private boolean mSlow = false;
private String mDestPath;
DiskLoader(final ImageReady notify, final ConfigFile config,
final DiskImage image) {
mNotify = notify;
mImage = image;
if (image.filename.endsWith(".gz") ||
image.filename.endsWith(".zip") ||
image.filename.endsWith(".GZ") ||
image.filename.endsWith(".ZIP") ||
image.origin == DiskImage.ASSET ||
image.origin == DiskImage.DOWNLOAD) {
mSlow = true;
}
if (image.origin == DiskImage.ASSET ||
image.origin == DiskImage.DOWNLOAD) {
mDestPath = config.getImagePath();
final File local_copy = new File(mDestPath, image.filename);
if (local_copy != null && local_copy.exists()) {
// Assume whatever is there will work.
mSlow = false;
}
}
if (image.origin == DiskImage.LOCALFILE) {
// If we need to extract it, it will go here.
mDestPath = config.getCachePath();
}
}
public boolean willBeSlow() {
return mSlow;
}
protected void onPreExecute() {
}
private Boolean extractImage() {
if (mImage.origin == DiskImage.ERROR) {
return false;
}
if (mImage.origin == DiskImage.ASSET) {
// Just keep polling waiting for it, AssetImages is working on it.
final File local_copy = new File(mDestPath, mImage.filename);
while (!local_copy.exists()) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
if (isCancelled()) {
return false;
}
}
return true;
}
if (mImage.origin == DiskImage.DOWNLOAD) {
final File local_copy = new File(mDestPath, mImage.filename);
if (local_copy != null && local_copy.exists()) {
// Assume whatever is there will work.
return true;
} else {
return new DownloadHelper().save(
"http://jsan.co/KEGS/images/" + mImage.filename,
local_copy.getPath());
}
}
if (mImage.filename.endsWith(".gz") || mImage.filename.endsWith(".GZ")) {
final String old_filename = mImage.getBaseFilename();
final String filename = old_filename.substring(0, old_filename.lastIndexOf("."));
try {
GZIPInputStream zipStream = new GZIPInputStream(new BufferedInputStream(new FileInputStream(new File(mImage.filename))));
new CopyHelper(zipStream, mDestPath, filename).copy();
} catch (java.io.IOException e) {
Log.e("kegs", Log.getStackTraceString(e));
return false;
}
mImage.filename = new File(mDestPath, filename).getPath();
return true;
}
if (mImage.filename.endsWith(".zip") || mImage.filename.endsWith(".ZIP")) {
final int pos = mImage.extract_filename.lastIndexOf("/");
final String filename = mImage.extract_filename.substring(pos + 1);
try {
final ZipFile zip = new ZipFile(mImage.filename);
final ZipEntry extract = zip.getEntry(mImage.extract_filename);
if (extract == null) {
zip.close();
Log.e("kegs", mImage.filename + " has no " + mImage.extract_filename);
return false;
}
new CopyHelper(zip.getInputStream(extract), mDestPath, filename).copy();
zip.close();
} catch (java.io.IOException e) {
Log.e("kegs", Log.getStackTraceString(e));
return false;
}
mImage.filename = new File(mDestPath, filename).getPath();
return true;
}
// No special handling required, finish up and run onImageReady.
return true;
}
protected Boolean doInBackground(Void... params) {
Boolean result = extractImage();
nativeSync(); // Flush new disk images before claiming they are ready.
return result;
}
protected void onCancelled(final Boolean result) {
mNotify.onImageReady(result, mImage);
}
protected void onPostExecute(final Boolean result) {
mNotify.onImageReady(result, mImage);
}
// See jni/android_driver.c:nativeSync()
private native void nativeSync();
}

View File

@ -1,56 +0,0 @@
package com.froop.app.kegs;
import android.util.Log;
import android.os.AsyncTask;
import java.io.File;
class DownloadImage extends AsyncTask<Void, Void, Boolean> {
interface DownloadReady {
void onDownloadReady(boolean result);
}
private DownloadReady mNotify;
private DiskImage mImage;
private File mDest;
private boolean mLocalPath = false;
DownloadImage(DownloadReady notify, String imagePath, DiskImage image) {
mNotify = notify;
mImage = image;
if (imagePath == null || image.filename.startsWith("/")) {
mLocalPath = true;
mDest = new File(image.filename);
} else {
mDest = new File(imagePath, image.filename);
}
if (mLocalPath || mDest.exists()) {
// Assume whatever is there will work.
// So the caller will immediately see that it's already done.
mNotify.onDownloadReady(true);
}
}
protected void onPreExecute() {
}
protected Boolean doInBackground(Void... params) {
if (mLocalPath || mDest.exists()) {
// Assume whatever is there will work.
return true;
}
return new DownloadHelper().save(
"http://jsan.co/KEGS/images/" + mImage.filename, mDest.getPath());
}
protected void onCancelled(final Boolean result) {
mNotify.onDownloadReady(false);
}
protected void onPostExecute(final Boolean result) {
mNotify.onDownloadReady(result);
}
}

View File

@ -0,0 +1,105 @@
package com.froop.app.kegs;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import com.actionbarsherlock.app.SherlockDialogFragment;
public class ZipDiskFragment extends SherlockDialogFragment {
private ArrayList<String> mFiles = new ArrayList<String>();
private ArrayList<String> mTitles = new ArrayList<String>();
private DiskImage mImage;
public ZipDiskFragment(DiskImage image) {
super();
mImage = image;
// NOTE: We extract the zipfile table of contents in the UI thread.
getTableOfContents();
}
private void getTableOfContents() {
try {
final ZipFile zip = new ZipFile(mImage.filename);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
final String filename = entry.getName();
if (DiskImage.isDiskImageFilename(filename)) {
mFiles.add(filename);
int pos = filename.lastIndexOf("/");
mTitles.add(filename.substring(pos + 1));
}
}
zip.close();
} catch (java.io.IOException e) {
Log.e("kegs", Log.getStackTraceString(e));
// Do not show any results.
mFiles.clear();
mTitles.clear();
}
}
// Caller may use getFirstImage when there is only one item
public boolean needsDialog() {
return mTitles.size() != 1;
}
public DiskImage getFirstImage() {
mImage.extract_filename = mFiles.get(0);
return mImage;
}
private void forceDiskLoaderError() {
// TODO: Ick. All this just so that whoever opened this fragment
// will get a DiskLoader callback.
mImage.filename = "";
mImage.origin = DiskImage.ERROR;
((KegsMain)getActivity()).runDiskLoader(mImage);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Force 3.5 inch disk icon instead of .getIconId()
// TODO: Could also show a special zipfile icon...?
builder.setIcon(R.drawable.ic_menu_save);
builder.setTitle(mImage.getTitle());
if (mTitles.size() == 0) {
builder.setMessage(R.string.zipfile_nomatches);
builder.setPositiveButton(R.string.dialog_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int button) {
dismiss();
forceDiskLoaderError();
}
});
} else {
builder.setItems(mTitles.toArray(new String[0]), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
dismiss();
mImage.extract_filename = mFiles.get(item);
((KegsMain)getActivity()).runDiskLoader(mImage);
}
});
}
return builder.create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
forceDiskLoaderError();
}
}