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

View File

@@ -263,7 +263,7 @@ void android_config_init(char *output, int maxlen) {
} }
JNIEXPORT void JNICALL 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();
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_title">Checking for ROM</string>
<string name="rom_check">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="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"> <string-array name="rom_choices">
<item>I have a ROM 01</item> <item>I have a ROM 01</item>
@@ -21,7 +24,8 @@
</string-array> </string-array>
<string name="diskimage_title">Choose a disk</string> <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"> <string-array name="swapdisk_choices">
<item>Ignore this disk</item> <item>Ignore this disk</item>

View File

@@ -46,7 +46,6 @@ class AssetImages extends AsyncTask<Void, Void, Boolean> {
mConfigFile.ensureAssetCopied(mConfigFile.getImagePath(), mConfigFile.ensureAssetCopied(mConfigFile.getImagePath(),
"System 6 Shareware.zip", "System 6.hdv"); "System 6 Shareware.zip", "System 6.hdv");
// TODO: could check to make sure they actually exist now. // TODO: could check to make sure they actually exist now.
nativeSync();
return true; return true;
} }
@@ -57,7 +56,4 @@ class AssetImages extends AsyncTask<Void, Void, Boolean> {
protected void onPostExecute(final Boolean result) { protected void onPostExecute(final Boolean result) {
mNotify.onAssetsReady(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"; return mConfigPath + "/images";
} }
public String getCachePath() {
return mContext.getExternalCacheDir().getPath();
}
public void ensureAssetCopied(String destPath, String zipfile, String exampleFile) { public void ensureAssetCopied(String destPath, String zipfile, String exampleFile) {
// We only check for a local copy of a single file before unzipping... // We only check for a local copy of a single file before unzipping...
final File local_copy = new File(destPath, exampleFile); 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.Menu;
import com.actionbarsherlock.view.MenuItem; 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_ROM = "rom";
private static final String FRAGMENT_DOWNLOAD = "download"; private static final String FRAGMENT_DOWNLOAD = "download";
private static final String FRAGMENT_ERROR = "error"; private static final String FRAGMENT_ERROR = "error";
private static final String FRAGMENT_SPEED = "speed"; private static final String FRAGMENT_SPEED = "speed";
private static final String FRAGMENT_DISKIMAGE = "diskimage"; 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_SWAPDISK = "swapdisk";
private static final String FRAGMENT_ZIPDISK = "zipdisk";
private static final String FRAGMENT_LOADING = "loading";
private ConfigFile mConfigFile; private ConfigFile mConfigFile;
private KegsThread mKegsThread; private KegsThread mKegsThread;
@@ -45,16 +48,27 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
private KegsKeyboard mKegsKeyboard; private KegsKeyboard mKegsKeyboard;
private TouchJoystick mJoystick; 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 mModeMouse = true;
private boolean mHaveNewIntent = false;
private long mScreenSizeTime = 0; 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() { private View.OnClickListener mButtonClick = new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
final int click_id = v.getId(); 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); ((ToggleButton)findViewById(R.id.key_closed_apple)).setChecked(false);
} }
public void onAssetsReady(boolean success) { protected void loadDiskImage(final DiskImage image) {
// TODO FIXME: this needs to throw an error if it fails. if (image.action == DiskImage.BOOT) {
mAssetsReady = success; getThread().doPowerOff();
} }
loadDiskImageWhenReady(image);
public void onDownloadReady(boolean success) {
// TODO FIXME: this needs to throw an error if it fails.
mDownloadReady = success;
} }
private void loadDiskImageWhenReady(final DiskImage 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; boolean nativeReady;
if (image.primary) { if (image.action == DiskImage.BOOT) {
nativeReady = getThread().nowWaitingForPowerOn(); nativeReady = getThread().nowWaitingForPowerOn();
} else { } else {
nativeReady = true; nativeReady = true;
} }
if (!nativeReady || !mAssetsReady || !mDownloadReady) { if (!nativeReady) {
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);
}
mKegsView.postDelayed(new Runnable() { mKegsView.postDelayed(new Runnable() {
public void run() { loadDiskImageWhenReady(image); } public void run() {
}, 100); loadDiskImageWhenReady(image);
} else {
if (frag != null) {
frag.dismiss();
} }
if (image.primary) { }, 100);
return;
}
withUIActive(new Runnable() {
public void run() {
if (image.action == DiskImage.BOOT) {
mConfigFile.setConfig(image); mConfigFile.setConfig(image);
getThread().allowPowerOn(); getThread().allowPowerOn();
} else { } else if (image.action == DiskImage.SWAP) {
getThread().getEventQueue().add(image.getDiskImageEvent()); getThread().getEventQueue().add(image.getDiskImageEvent());
} }
} }
});
} }
protected void loadDiskImage(DiskImage image) { public void onImageReady(final boolean result, final DiskImage image) {
if (image.primary) { mDiskLoader = null;
getThread().doPowerOff(); 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);
}
}
});
} }
mDownloadStarted = false; public void prepareDiskImage(DiskImage image) {
mDownloadReady = false; dismissFragment(FRAGMENT_ROM); // Note: should probably bail if visible.
mDownloadAborted = false; dismissFragment(FRAGMENT_SPEED);
dismissFragment(FRAGMENT_DISKIMAGE);
dismissFragment(FRAGMENT_SWAPDISK);
dismissFragment(FRAGMENT_ZIPDISK);
loadDiskImageWhenReady(image); 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);
}
// 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;
}
}
};
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) { private DiskImage getBootDiskImage(Intent intent) {
@@ -200,14 +242,20 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
private void boot() { private void boot() {
final DiskImage image = getBootDiskImage(getIntent()); final DiskImage image = getBootDiskImage(getIntent());
if (image != null) { if (image != null) {
loadDiskImage(image); prepareDiskImage(image);
} else { } else {
mConfigFile.defaultConfig(); mConfigFile.defaultConfig();
getThread().allowPowerOn(); getThread().allowPowerOn();
mKegsView.postDelayed(new Runnable() { public void run() { mKegsView.postDelayed(new Runnable() {
public void run() {
withUIActive(new Runnable() {
public void run() {
new DiskImageFragment().show(getSupportFragmentManager(), new DiskImageFragment().show(getSupportFragmentManager(),
FRAGMENT_DISKIMAGE); FRAGMENT_DISKIMAGE);
} }, 1000); }
});
}
}, 1000);
} }
} }
@@ -229,20 +277,24 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
"http://jsan.co/KEGS/" + mRomfile, "http://jsan.co/KEGS/" + mRomfile,
mConfigFile.getConfigPath() + "/" + mRomfile); mConfigFile.getConfigPath() + "/" + mRomfile);
} }
protected void onPostExecute(Boolean success) { protected void onPostExecute(final Boolean success) {
withUIActive(new Runnable() {
public void run() {
final SherlockDialogFragment frag = (SherlockDialogFragment)getSupportFragmentManager().findFragmentByTag(FRAGMENT_DOWNLOAD); final SherlockDialogFragment frag = (SherlockDialogFragment)getSupportFragmentManager().findFragmentByTag(FRAGMENT_DOWNLOAD);
if (frag != null) { if (frag != null) {
frag.dismiss(); frag.dismiss();
} }
if (!success) { if (!success) {
if (!isCancelled()) { if (!isCancelled()) {
final SherlockDialogFragment dialog = new ErrorDialogFragment(); final Runnable runnable = new Runnable() { public void run() { finish(); } };
dialog.show(getSupportFragmentManager(), FRAGMENT_ERROR); new ErrorDialogFragment(R.string.rom_error, runnable).show(getSupportFragmentManager(), FRAGMENT_ERROR);
} }
} else { } else {
boot(); boot();
} }
} }
});
}
} }
class DownloadDialogFragment extends SherlockDialogFragment { class DownloadDialogFragment extends SherlockDialogFragment {
@@ -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() { public KegsThread getThread() {
return mKegsThread; return mKegsThread;
} }
@@ -447,22 +452,18 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
if (image == null) { if (image == null) {
return; // Nothing to do. return; // Nothing to do.
} }
dismissFragment(FRAGMENT_ROM); // Note: should probably bail if visible. image.action = DiskImage.ASK;
dismissFragment(FRAGMENT_SPEED); prepareDiskImage(image);
dismissFragment(FRAGMENT_DISKIMAGE);
dismissFragment(FRAGMENT_SWAPDISK);
// Ask the user what to do with this new disk image.
new SwapDiskFragment(image).show(getSupportFragmentManager(),
FRAGMENT_SWAPDISK);
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(final Intent intent) {
// "An activity will always be paused before receiving a new 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. withUIActive(new Runnable() {
setIntent(intent); public void run() {
mHaveNewIntent = true; handleNewIntent(intent);
}
});
} }
@Override @Override
@@ -530,8 +531,7 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
final String romfile = mConfigFile.whichRomFile(); final String romfile = mConfigFile.whichRomFile();
if (romfile == null) { if (romfile == null) {
final SherlockDialogFragment chooseRom = new RomDialogFragment(); new RomDialogFragment().show(getSupportFragmentManager(), FRAGMENT_ROM);
chooseRom.show(getSupportFragmentManager(), FRAGMENT_ROM);
} else { } else {
boot(); boot();
} }
@@ -544,6 +544,7 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
if (mKegsView instanceof KegsViewGL) { if (mKegsView instanceof KegsViewGL) {
mKegsView.onPause(); mKegsView.onPause();
} }
mPaused = true;
} }
@Override @Override
@@ -553,15 +554,18 @@ public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.S
if (mKegsView instanceof KegsViewGL) { if (mKegsView instanceof KegsViewGL) {
mKegsView.onResume(); mKegsView.onResume();
} }
if (mHaveNewIntent) { mPaused = false;
mHaveNewIntent = false;
handleNewIntent(getIntent()); Runnable runnable;
while ((runnable = mResumeQueue.poll()) != null) {
runnable.run();
} }
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
mResumeQueue.clear();
Log.w("kegs", "onDestroy called, halting"); Log.w("kegs", "onDestroy called, halting");
// Force process to exit. We cannot handle another onCreate // Force process to exit. We cannot handle another onCreate
// once a KegsView has been active. (JNI kegsmain has already run) // 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) { if (item == 0) {
// ignore this image // ignore this image
} else if (item == 1) { } else if (item == 1) {
mImage.primary = false; // just a disk insert mImage.action = DiskImage.SWAP; // just a disk insert
((KegsMain)getActivity()).loadDiskImage(mImage); ((KegsMain)getActivity()).loadDiskImage(mImage);
} else if (item == 2) { } else if (item == 2) {
mImage.action = DiskImage.BOOT; // was ASK
((KegsMain)getActivity()).loadDiskImage(mImage); ((KegsMain)getActivity()).loadDiskImage(mImage);
} }
} }

View File

@@ -5,24 +5,40 @@ import java.lang.Integer;
import java.io.File; import java.io.File;
class DiskImage { class DiskImage {
// template
public static final String BOOT_SLOT_5 = "boot_slot_5"; 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_6 = "boot_slot_6";
public static final String BOOT_SLOT_7 = "boot_slot_7"; 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 filename;
public String drive; public String drive;
public int speed; public int speed;
public String template; public String template;
public int origin;
public int action;
public String extract_filename = null; // for extracting from zipfiles
// Example: // 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, 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.filename = filename;
this.drive = drive; this.drive = drive;
this.speed = speed; this.speed = speed;
this.template = template; this.template = template;
this.origin = origin;
this.action = BOOT;
} }
public static DiskImage fromPath(String path) { public static DiskImage fromPath(String path) {
@@ -32,19 +48,44 @@ class DiskImage {
return null; return null;
} else if (length >= 1024 * 1024) { } else if (length >= 1024 * 1024) {
// TODO: should insert the disk and use the System 6 template // 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) { } 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) { } else if (length > 0) {
return new DiskImage(path, "s6d1", 1, BOOT_SLOT_6); return new DiskImage(path, "s6d1", 1, BOOT_SLOT_6, LOCALFILE);
} else { } else {
return null; 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() { public String getTitle() {
int pos = this.filename.lastIndexOf("/") + 1; return getBaseFilename();
return this.filename.substring(pos);
} }
public int getIconId() { public int getIconId() {

View File

@@ -32,16 +32,16 @@ public class DiskImageFragment extends SherlockDialogFragment {
DiskImage image = null; DiskImage image = null;
if (item == 0) { 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) { } 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) { } else if (item == 2) {
// TODO: There should be an adapter on the ListView instead. // 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) { 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();
}
}