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 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(); + } +}