diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7fd7544..93305e8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,41 +20,51 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jni/android_driver.c b/jni/android_driver.c
index 5e1b1cd..3edcf42 100644
--- a/jni/android_driver.c
+++ b/jni/android_driver.c
@@ -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();
}
diff --git a/res/drawable-xhdpi/ic_dialog_alert_holo_dark.png b/res/drawable-xhdpi/ic_dialog_alert_holo_dark.png
new file mode 100644
index 0000000..f906e2a
Binary files /dev/null and b/res/drawable-xhdpi/ic_dialog_alert_holo_dark.png differ
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3c9e3cd..8172ee8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6,6 +6,9 @@
Checking for ROM
Checking for ROM...
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.
+ Unable to load requested disk image.
+ Error
+ OK
- I have a ROM 01
@@ -21,7 +24,8 @@
Choose a disk
- Preparing disk image...
+ Preparing disk image...
+ No disk images found
- Ignore this disk
diff --git a/src/com/froop/app/kegs/AssetImages.java b/src/com/froop/app/kegs/AssetImages.java
index dc7a13b..a1ae910 100644
--- a/src/com/froop/app/kegs/AssetImages.java
+++ b/src/com/froop/app/kegs/AssetImages.java
@@ -46,7 +46,6 @@ class AssetImages extends AsyncTask {
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 {
protected void onPostExecute(final Boolean result) {
mNotify.onAssetsReady(result);
}
-
- // See jni/android_driver.c:nativeSync()
- private native void nativeSync();
}
diff --git a/src/com/froop/app/kegs/ConfigFile.java b/src/com/froop/app/kegs/ConfigFile.java
index 61f78a4..673f2e4 100644
--- a/src/com/froop/app/kegs/ConfigFile.java
+++ b/src/com/froop/app/kegs/ConfigFile.java
@@ -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);
diff --git a/src/com/froop/app/kegs/ErrorDialogFragment.java b/src/com/froop/app/kegs/ErrorDialogFragment.java
new file mode 100644
index 0000000..e1d124d
--- /dev/null
+++ b/src/com/froop/app/kegs/ErrorDialogFragment.java
@@ -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();
+ }
+ }
+}
diff --git a/src/com/froop/app/kegs/KegsMain.java b/src/com/froop/app/kegs/KegsMain.java
index 3daa4c7..6aac62b 100644
--- a/src/com/froop/app/kegs/KegsMain.java
+++ b/src/com/froop/app/kegs/KegsMain.java
@@ -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 mResumeQueue = new ArrayDeque();
+
+ 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)
diff --git a/src/com/froop/app/kegs/SpecialProgressDialog.java b/src/com/froop/app/kegs/SpecialProgressDialog.java
new file mode 100644
index 0000000..da1bcf5
--- /dev/null
+++ b/src/com/froop/app/kegs/SpecialProgressDialog.java
@@ -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();
+ }
+}
diff --git a/src/com/froop/app/kegs/SwapDiskFragment.java b/src/com/froop/app/kegs/SwapDiskFragment.java
index 02bc649..b4d7f89 100644
--- a/src/com/froop/app/kegs/SwapDiskFragment.java
+++ b/src/com/froop/app/kegs/SwapDiskFragment.java
@@ -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);
}
}
diff --git a/src/com/froop/app/kegs/diskimage/DiskImage.java b/src/com/froop/app/kegs/diskimage/DiskImage.java
index 894ffa4..e2a8619 100644
--- a/src/com/froop/app/kegs/diskimage/DiskImage.java
+++ b/src/com/froop/app/kegs/diskimage/DiskImage.java
@@ -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() {
diff --git a/src/com/froop/app/kegs/diskimage/DiskImageFragment.java b/src/com/froop/app/kegs/diskimage/DiskImageFragment.java
index 60f9213..4cabe52 100644
--- a/src/com/froop/app/kegs/diskimage/DiskImageFragment.java
+++ b/src/com/froop/app/kegs/diskimage/DiskImageFragment.java
@@ -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);
}
}
});
diff --git a/src/com/froop/app/kegs/diskimage/DiskLoader.java b/src/com/froop/app/kegs/diskimage/DiskLoader.java
new file mode 100644
index 0000000..785e7ae
--- /dev/null
+++ b/src/com/froop/app/kegs/diskimage/DiskLoader.java
@@ -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 {
+ 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();
+}
diff --git a/src/com/froop/app/kegs/diskimage/DownloadImage.java b/src/com/froop/app/kegs/diskimage/DownloadImage.java
deleted file mode 100644
index d63bd06..0000000
--- a/src/com/froop/app/kegs/diskimage/DownloadImage.java
+++ /dev/null
@@ -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 {
- 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);
- }
-}
diff --git a/src/com/froop/app/kegs/diskimage/ZipDiskFragment.java b/src/com/froop/app/kegs/diskimage/ZipDiskFragment.java
new file mode 100644
index 0000000..f172798
--- /dev/null
+++ b/src/com/froop/app/kegs/diskimage/ZipDiskFragment.java
@@ -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 mFiles = new ArrayList();
+ private ArrayList mTitles = new ArrayList();
+ 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();
+ }
+}