/*
* This file is a part of Alchemy OS project.
* Copyright (C) 2011-2014, Sergey Basalaev <sbasalaev@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package alchemy.midlet;
import alchemy.fs.Filesystem;
import alchemy.fs.FSDriver;
import alchemy.fs.rms.Driver;
import alchemy.io.IO;
import alchemy.platform.Installer;
import alchemy.util.ArrayList;
import alchemy.util.HashMap;
import alchemy.util.Strings;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
/**
* MIDlet to install Alchemy.
* @author Sergey Basalaev
*/
public class InstallerMIDlet extends MIDlet implements CommandListener {
private static final String ABOUT_TEXT =
"Alchemy OS %0" +
"\n\nCopyright (c) 2011-2014, Sergey Basalaev\n" +
"http://alchemy-os.org\n" +
"\n" +
"This program is free software and is licensed under GNU GPL version 3\n" +
"A copy of the GNU GPL may be found at http://www.gnu.org/licenses/\n";
private Displayable current;
private final Display display;
private final Form messages;
private final List menu;
/** Dialog commands. */
private final Command cmdMenu = new Command("Menu", Command.SCREEN, 1);
private final Command cmdYes = new Command("Yes", Command.OK, 1);
private final Command cmdNo = new Command("No", Command.CANCEL, 2);
private Command selected;
private static final int ACTION_INSTALL = 0;
private static final int ACTION_ABOUT = 1;
private static final int ACTION_QUIT = 2;
private Installer installer;
public InstallerMIDlet() {
// prepare screens
display = Display.getDisplay(this);
messages = new Form("Installer");
messages.addCommand(cmdMenu);
messages.setCommandListener(this);
menu = new List("Menu", List.IMPLICIT);
menu.setCommandListener(this);
current = messages;
display.setCurrent(current);
// prepare installer and menu
try {
installer = new Installer();
} catch (Exception e) {
messages.append("Fatal error: "+"cannot read setup.cfg"+'\n'+e+'\n');
return;
}
menu.append(installer.isInstalled() ? "Uninstall" : "Install", null);
menu.append("About", null);
menu.append("Quit", null);
// display.callSerially(new InstallerThread(0));
}
protected void startApp() throws MIDletStateChangeException {
display.setCurrent(current);
}
protected void pauseApp() {
current = display.getCurrent();
}
protected void destroyApp(boolean unconditional) {
Filesystem.unmountAll();
notifyDestroyed();
}
public void commandAction(Command c, Displayable d) {
if (c == List.SELECT_COMMAND) {
new InstallerThread(menu.getSelectedIndex()).start();
} else if (c == cmdMenu) {
current = menu;
display.setCurrent(menu);
} else {
selected = c;
synchronized (this) {
this.notify();
}
}
}
private void install() throws Exception {
messages.deleteAll();
HashMap instCfg = installer.getInstalledConfig();
HashMap setupCfg = installer.getSetupConfig();
instCfg.set("rms.name", "rsfiles");
//choosing filesystem
ArrayList filesystems = new ArrayList();
String[] fstypes = Strings.split((String)setupCfg.get("install.fs"), ' ', true);
for (int i=0; i<fstypes.length; i++) {
try {
Class.forName((String)setupCfg.get("install.fs." + fstypes[i] + ".test"));
filesystems.add(fstypes[i]);
} catch (ClassNotFoundException cnfe) {
// skip this file system
}
}
final List fschoice = new List("Choose filesystem", Choice.IMPLICIT);
for (int i=0; i<filesystems.size(); i++) {
fschoice.append((String)setupCfg.get("install.fs." + filesystems.get(i) + ".name"), null);
}
fschoice.setCommandListener(this);
display.setCurrent(fschoice);
synchronized (fschoice) {
fschoice.wait();
}
String selectedfs = filesystems.get(fschoice.getSelectedIndex()).toString();
messages.append("Selected filesystem: "+fschoice.getString(fschoice.getSelectedIndex())+'\n');
//choosing root path if needed
String fsinit = (String) setupCfg.get("install.fs." + selectedfs + ".init");
if (fsinit == null) fsinit = "";
String neednav = (String) setupCfg.get("install.fs." + selectedfs + ".nav");
if ("true".equals(neednav)) {
final FSNavigator navigator = new FSNavigator(display, selectedfs);
display.setCurrent(navigator);
synchronized (navigator) {
navigator.wait();
}
String path = navigator.getCurrentDir();
if (path == null) throw new Exception("Installation aborted");
fsinit = path;
messages.append("Selected path: "+path+'\n');
}
display.setCurrent(messages);
//installing base files
installer.install(selectedfs, fsinit);
//writing install config
messages.append("Saving configuration..."+'\n');
installer.saveInstalledConfig();
messages.append("Launch Alchemy OS to finish installation"+'\n');
}
private void uninstall() throws Exception {
messages.deleteAll();
messages.append("Uninstalling..."+'\n');
HashMap instCfg = installer.getInstalledConfig();
//purging filesystem
if (question("Do you want to erase internal file system?")) {
if (instCfg.get(Installer.FS_DRIVER).equals("rms")) {
try {
RecordStore.deleteRecordStore((String)instCfg.get("rms.name"));
messages.append("File system erased"+'\n');
} catch (RecordStoreException rse) { }
}
}
//removing config
installer.removeInstalledConfig();
messages.append("Configuration removed"+'\n');
}
/** Should be only available when RMS file system is in use. */
private void rebuildFileSystem() throws Exception {
messages.deleteAll();
messages.append("Optimizing RMS filesystem...\n");
// opening old FS
HashMap cfg = installer.getInstalledConfig();
String oldname = (String)cfg.get("rms.name");
Driver oldfs = new Driver();
long oldlen = oldfs.spaceUsed();
// creating new FS
String newname = (oldname.equals("rsfiles")) ? "rsfiles2" : "rsfiles";
cfg.set("rms.name", newname);
try {
RecordStore.deleteRecordStore(newname);
} catch (RecordStoreException rse) { }
Driver newfs = new Driver();
// copying all files from the old FS to the new
String[] list = oldfs.list("");
for (int i=0; i<list.length; i++) {
copyTree(oldfs, newfs, "/"+list[i]);
}
oldfs.close();
// computing new len
newfs.close();
newfs = new Driver();
long newlen = newfs.spaceUsed();
newfs.close();
// writing configuration
messages.append("Saving configuration...\n");
cfg.set("rms.name", newname);
installer.saveInstalledConfig();
// removing old FS
RecordStore.deleteRecordStore(oldname);
messages.append("Optimization complete.\n");
messages.append("" + (oldlen - newlen) + " bytes saved.\n");
}
private void copyTree(FSDriver from, FSDriver to, String file) throws IOException {
file = Filesystem.normalize(file);
boolean fRead = from.canRead(file);
boolean fWrite = from.canWrite(file);
if (!fRead) from.setRead(file, true);
if (!fWrite) from.setWrite(file, true);
if (from.isDirectory(file)) {
to.mkdir(file);
String[] list = from.list(file);
for (int i=0; i<list.length; i++) {
String subfile = file+'/'+list[i];
copyTree(from, to, subfile);
}
} else {
to.create(file);
InputStream in = from.read(file);
OutputStream out = to.write(file);
IO.writeAll(in, out);
in.close();
out.flush();
out.close();
}
to.setExec(file, from.canExec(file));
to.setWrite(file, fWrite);
to.setRead(file, fRead);
}
private boolean question(String msg) {
final Alert alert = new Alert("Installer", msg, null, AlertType.WARNING);
alert.setTimeout(Alert.FOREVER);
alert.addCommand(cmdYes);
alert.addCommand(cmdNo);
alert.setCommandListener(this);
display.setCurrent(alert);
try {
synchronized (this) {
this.wait();
}
return selected == cmdYes;
} catch (InterruptedException ie) {
return false;
}
}
private class InstallerThread extends Thread {
private final int action;
public InstallerThread(int action) {
this.action = action;
}
public void run() {
display.setCurrent(messages);
try {
switch(action) {
case ACTION_INSTALL:
if (installer.isInstalled())
uninstall();
else
install();
break;
case ACTION_ABOUT:
messages.append(Strings.format(
ABOUT_TEXT,
new Object[] { installer.getSetupConfig().get(Installer.VERSION) }));
break;
case ACTION_QUIT:
destroyApp(true);
}
} catch (Throwable e) {
e.printStackTrace();
messages.append("Fatal error: "+e.toString()+'\n');
}
}
}
}