Persist disk image 'was_gzipped' state

- 'was_gzipped' state is persisted in .apple2.json preferences
    - This removes all file-renaming codepaths at the cost of temporarily nonconformance to naming convesion (e.g., disk
      images inserted read/write are still called 'foo.dsk.gz' although they are not compressed)
    - Unclean shutdown of emulator leaves any disk images that were inserted read/write in their non-gzipped state
      (regardless of file name extension) on disk until emulator restarted and disk images explictly ejected or clean
      shudown.  App removal after unclean shutdown will potentially leave mis-named disk images lingering.
    - Emulator will handle edge cases of non-gzipped disk images with '.gz' extension and gzipped disk images without
      '.gz' extension
This commit is contained in:
Aaron Culliney 2017-05-29 07:24:30 -10:00
parent 399daf16fa
commit b300e60e2a
8 changed files with 91 additions and 75 deletions

View File

@ -143,8 +143,8 @@ public class Apple2Activity extends Activity /*implements Apple2DiskChooserActiv
boolean switchingToPortrait = Apple2VideoSettingsMenu.SETTINGS.applyLandscapeMode(this);
Apple2Preferences.sync(this, null);
Apple2DisksMenu.insertDisk((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A), /*driveA:*/true, /*isReadOnly:*/(boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO));
Apple2DisksMenu.insertDisk((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B), /*driveA:*/false, /*isReadOnly:*/(boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_RO));
Apple2DisksMenu.insertDisk((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A), /*driveA:*/true, /*isReadOnly:*/(boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO), /*onLaunch:*/true);
Apple2DisksMenu.insertDisk((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B), /*driveA:*/false, /*isReadOnly:*/(boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_RO), /*onLaunch:*/true);
showSplashScreen(!firstTime);

View File

@ -115,6 +115,17 @@ public class Apple2DisksMenu implements Apple2MenuView {
return true;
}
},
CURRENT_DISK_PATH_A_GZ {
@Override
public String getPrefKey() {
return "driveAInsertedDiskGZ";
}
@Override
public Object getPrefDefault() {
return true;
}
},
CURRENT_DISK_PATH_B {
@Override
public String getPrefKey() {
@ -132,6 +143,17 @@ public class Apple2DisksMenu implements Apple2MenuView {
return "driveBInsertedDiskRO";
}
@Override
public Object getPrefDefault() {
return true;
}
},
CURRENT_DISK_PATH_B_GZ {
@Override
public String getPrefKey() {
return "driveBInsertedDiskGZ";
}
@Override
public Object getPrefDefault() {
return true;
@ -260,7 +282,7 @@ public class Apple2DisksMenu implements Apple2MenuView {
return path;
}
public static void insertDisk(String imageName, boolean isDriveA, boolean isReadOnly) {
public static void insertDisk(String imageName, boolean isDriveA, boolean isReadOnly, boolean onLaunch) {
try {
JSONObject map = new JSONObject();
@ -271,13 +293,8 @@ public class Apple2DisksMenu implements Apple2MenuView {
////map.put("fd", fd);
} else {
File file = new File(imageName);
if (!file.exists()) {
imageName = addGzipExtension(imageName);
file = new File(imageName);
if (!file.exists()) {
throw new RuntimeException("cannot insert : " + imageName);
}
throw new RuntimeException("cannot insert : " + imageName);
}
}
@ -292,9 +309,22 @@ public class Apple2DisksMenu implements Apple2MenuView {
map.put("disk", imageName);
map.put("drive", isDriveA ? "0" : "1");
map.put("readOnly", isReadOnly ? "true" : "false");
if (onLaunch) {
boolean wasGzipped = (boolean) (isDriveA ? Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_A_GZ) : Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_B_GZ));
map.put("wasGzipped", wasGzipped ? "true" : "false");
}
String jsonString = nativeChooseDisk(map.toString());
map = new JSONObject(jsonString);
boolean inserted = map.getBoolean("inserted");
if (inserted) {
boolean wasGzipped = map.getBoolean("wasGzipped");
Apple2Preferences.setJSONPref(isDriveA ? Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_GZ : Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_GZ, wasGzipped);
} else {
ejectDisk(isDriveA);
}
} catch (Throwable t) {
Log.d(TAG, "OOPS: " + t);
}
@ -355,7 +385,7 @@ public class Apple2DisksMenu implements Apple2MenuView {
boolean isDriveA = driveA.isChecked();
boolean diskReadOnly = readOnly.isChecked();
insertDisk(imagePath, isDriveA, diskReadOnly);
insertDisk(imagePath, isDriveA, diskReadOnly, /*onLaunch:*/false);
dialog.dismiss();
mActivity.dismissAllMenus();
@ -407,20 +437,6 @@ public class Apple2DisksMenu implements Apple2MenuView {
return (suffix.equalsIgnoreCase(".dsk.gz") || suffix.equalsIgnoreCase(".nib.gz"));
}
public static boolean isGzipExtension(String name) {
int len = name.length();
String ext = name.substring(len - 3, len);
return ext.equals(".gz");
}
public static String removeGzipExtention(String name) {
return isGzipExtension(name) ? name.substring(0, name.length() - 3) : name;
}
public static String addGzipExtension(String name) {
return isGzipExtension(name) ? name : name + ".gz";
}
// ------------------------------------------------------------------------
// internals ...

View File

@ -364,23 +364,24 @@ public class Apple2MainMenu {
////int fd = Apple2DiskChooserActivity.openFileDescriptor(diskPath, /*isReadOnly:*/readOnly);
////map.put(fdKeys[i], fd);
} else {
boolean isGzip = Apple2DisksMenu.isGzipExtension(diskPath);
boolean exists = new File(diskPath).exists();
if (!exists) {
diskPath = isGzip ? Apple2DisksMenu.removeGzipExtention(diskPath) : Apple2DisksMenu.addGzipExtension(diskPath);
isGzip = !isGzip;
exists = new File(diskPath).exists();
if (!exists) {
Log.e(TAG, "Did not find path(s) for drive #" + i + " specified in emulator.state file : " + diskPath);
}
Log.e(TAG, "Did not find path(s) for drive #" + i + " specified in emulator.state file : " + diskPath);
}
Apple2Preferences.setJSONPref(i == 0 ? Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A : Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B, diskPath);
}
}
jsonString = mActivity.loadState(map.toString());
map = new JSONObject(jsonString);
{
boolean wasGzippedA = map.getBoolean("wasGzippedA");
Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_GZ, wasGzippedA);
}
{
boolean wasGzippedB = map.getBoolean("wasGzippedB");
Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_GZ, wasGzippedB);
}
// FIXME TODO : what to do if state load failed?

View File

@ -407,8 +407,16 @@ jstring Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeChooseDisk(JNIEnv *env,
inserted = false;
} else {
video_animations->animation_showDiskChosen(drive);
// possibly override was_gzipped, if specified in args ...
bool wasGzipped = false;
if (json_mapParseBoolValue(jsonData, "wasGzipped", &wasGzipped)) {
disk6.disk[drive].was_gzipped = wasGzipped;
}
}
// remember if image was gzipped
prefs_setBoolValue(PREF_DOMAIN_VM, drive == 0 ? PREF_DISK_DRIVEA_GZ : PREF_DISK_DRIVEB_GZ, disk6.disk[drive].was_gzipped); // HACK FIXME TODO ... refactor : this is erased on the Java side when we resume emulation
json_mapSetBoolValue(jsonData, "wasGzipped", disk6.disk[drive].was_gzipped);
json_mapSetBoolValue(jsonData, "inserted", inserted);
if (createdFd) {
@ -452,26 +460,6 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSaveState(JNIEnv *env, jcla
(*env)->ReleaseStringUTFChars(env, jPath, path);
}
static int _insert_path(char *path, bool readOnly) {
int fd = -1;
if (strlen(path) > 0) {
TEMP_FAILURE_RETRY(fd = open(path, readOnly ? O_RDONLY : O_RDWR));
if (fd == -1) {
ASPRINTF(&path, "%s.gz", path);
if (path != NULL) {
TEMP_FAILURE_RETRY(fd = open(path, readOnly ? O_RDONLY : O_RDWR));
if (fd == -1) {
LOG("OOPS could not open disk path %s", path);
}
}
FREE(path);
}
}
return fd;
}
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, jclass cls, jstring jJsonString) {
assert(cpu_isPaused() && "considered dangerous to load state when CPU thread is running");
@ -498,7 +486,10 @@ jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, j
json_mapCopyStringValue(jsonData, "diskA", &pathA);
json_unescapeSlashes(&pathA);
fdA = _insert_path(pathA, readOnlyA);
TEMP_FAILURE_RETRY(fdA = open(pathA, readOnlyA ? O_RDONLY : O_RDWR));
if (fdA == -1) {
LOG("OOPS could not open disk path %s", pathA);
}
createdFdA = fdA > 0;
FREE(pathA);
@ -514,16 +505,23 @@ jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, j
json_mapCopyStringValue(jsonData, "diskB", &pathB);
json_unescapeSlashes(&pathB);
fdB = _insert_path(pathB, readOnlyB);
TEMP_FAILURE_RETRY(fdB = open(pathB, readOnlyB ? O_RDONLY : O_RDWR));
if (fdB == -1) {
LOG("OOPS could not open disk path %s", pathB);
}
createdFdB = fdB > 0;
FREE(pathB);
}
bool loadStateSuccess = true;
if (!emulator_loadState(path, (int)fdA, (int)fdB)) {
if (emulator_loadState(path, (int)fdA, (int)fdB)) {
json_mapSetBoolValue(jsonData, "wasGzippedA", disk6.disk[0].was_gzipped);
json_mapSetBoolValue(jsonData, "wasGzippedB", disk6.disk[1].was_gzipped);
} else {
loadStateSuccess = false;
LOG("OOPS, could not load emulator state");
// FIXME TODO : should show invalid state animation here ...
}
if (createdFdA) {

View File

@ -1007,6 +1007,8 @@ void disk6_flush(int drive) {
__sync_synchronize();
LOG("FLUSHING disk image I/O");
int ret = -1;
TEMP_FAILURE_RETRY(ret = msync(disk6.disk[drive].raw_image_data, disk6.disk[drive].whole_len, MS_SYNC));
if (ret) {
@ -1093,8 +1095,7 @@ bool disk6_saveState(StateHelper_s *helper) {
}
LOG("SAVE stepper_phases[%lu] = %02x", i, stepper_phases);
// Save unused placeholder -- backwards compatibility
state = 0x0;
state = disk6.disk[i].was_gzipped;
if (!helper->save(fd, &state, 1)) {
break;
}
@ -1235,10 +1236,16 @@ static bool _disk6_loadState(StateHelper_s *helper, JSON_ref *json) {
stepper_phases = state & 0x3; // HACK NOTE : this is unnecessarily encoded twice ...
}
// load placeholder
// format A2V3+ : was_gzipped, (otherwise placeholder)
if (!helper->load(fd, &state, 1)) {
break;
}
if (helper->version >= 3) {
disk6.disk[i].was_gzipped = (state != 0);
// remember if image was gzipped
prefs_setBoolValue(PREF_DOMAIN_VM, i == 0 ? PREF_DISK_DRIVEA_GZ : PREF_DISK_DRIVEB_GZ, disk6.disk[i].was_gzipped);
}
if (!helper->load(fd, &state, 1)) {
break;

View File

@ -17,7 +17,8 @@
#define SAVE_MAGICK "A2VM"
#define SAVE_MAGICK2 "A2V2"
#define SAVE_VERSION 2
#define SAVE_MAGICK3 "A2V3"
#define SAVE_VERSION 3
#define SAVE_MAGICK_LEN sizeof(SAVE_MAGICK)
typedef struct module_ctor_node_s {
@ -112,10 +113,12 @@ static int _load_magick(int fd) {
// check header
if (memcmp(magick, SAVE_MAGICK, SAVE_MAGICK_LEN) == 0) {
return 1;
if (memcmp(magick, SAVE_MAGICK3, SAVE_MAGICK_LEN) == 0) {
return 3;
} else if (memcmp(magick, SAVE_MAGICK2, SAVE_MAGICK_LEN) == 0) {
return 2;
} else if (memcmp(magick, SAVE_MAGICK, SAVE_MAGICK_LEN) == 0) {
return 1;
}
ERRLOG("bad header magick in emulator save state file");
@ -138,7 +141,7 @@ bool emulator_saveState(const char * const path) {
assert(fd != 0 && "crazy platform");
// save header
if (!_save_state(fd, (const uint8_t *)SAVE_MAGICK2, SAVE_MAGICK_LEN)) {
if (!_save_state(fd, (const uint8_t *)SAVE_MAGICK3, SAVE_MAGICK_LEN)) {
break;
}

View File

@ -86,6 +86,8 @@
// vm
#define PREF_CPU_SCALE "cpuScale"
#define PREF_CPU_SCALE_ALT "cpuScaleAlt"
#define PREF_DISK_DRIVEA_GZ "driveAInsertedDiskGZ"
#define PREF_DISK_DRIVEB_GZ "driveBInsertedDiskGZ"
typedef void (*prefs_change_callback_f)(const char * _NONNULL domain);

View File

@ -227,17 +227,6 @@ int test_setup_boot_disk(const char *fileName, int readonly) {
break;
}
}
size_t len = strlen(disk);
disk[len-3] = '\0'; // try again without '.gz' extension
TEMP_FAILURE_RETRY(fd = open(disk, readonly ? O_RDONLY : O_RDWR));
if (fd != -1) {
err = disk6_insert(fd, /*drive:*/0, disk, readonly) != NULL;
TEMP_FAILURE_RETRY(close(fd));
if (!err) {
break;
}
}
}
path = &paths[0];