diff --git a/webui/WebUIApp-compile b/webui/WebUIApp-compile new file mode 100755 index 000000000..e79b59f8a --- /dev/null +++ b/webui/WebUIApp-compile @@ -0,0 +1,3 @@ +#!/bin/sh +APPDIR=`dirname $0`; +java -cp "$APPDIR/src:$APPDIR/bin:/home/alon/Temp/gwt-linux-1.2.22/gwt-user.jar:/home/alon/Temp/gwt-linux-1.2.22/gwt-dev-linux.jar" com.google.gwt.dev.GWTCompiler -out "$APPDIR/www" "$@" com.WebUI.WebUIApp; diff --git a/webui/WebUIApp-shell b/webui/WebUIApp-shell new file mode 100755 index 000000000..6c6ab19df --- /dev/null +++ b/webui/WebUIApp-shell @@ -0,0 +1,3 @@ +#!/bin/sh +APPDIR=`dirname $0`; +java -cp "$APPDIR/src:$APPDIR/bin:/home/alon/Temp/gwt-linux-1.2.22/gwt-user.jar:/home/alon/Temp/gwt-linux-1.2.22/gwt-dev-linux.jar" com.google.gwt.dev.GWTShell -out "$APPDIR/www" "$@" com.WebUI.WebUIApp/WebUIApp.html; diff --git a/webui/json.py b/webui/json.py new file mode 100644 index 000000000..a28a13e39 --- /dev/null +++ b/webui/json.py @@ -0,0 +1,310 @@ +import string +import types + +## json.py implements a JSON (http://json.org) reader and writer. +## Copyright (C) 2005 Patrick D. Logan +## Contact mailto:patrickdlogan@stardecisions.com +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library 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 +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +class _StringGenerator(object): + def __init__(self, string): + self.string = string + self.index = -1 + def peek(self): + i = self.index + 1 + if i < len(self.string): + return self.string[i] + else: + return None + def next(self): + self.index += 1 + if self.index < len(self.string): + return self.string[self.index] + else: + raise StopIteration + def all(self): + return self.string + +class WriteException(Exception): + pass + +class ReadException(Exception): + pass + +class JsonReader(object): + hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15} + escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'} + + def read(self, s): + self._generator = _StringGenerator(s) + result = self._read() + return result + + def _read(self): + self._eatWhitespace() + peek = self._peek() + if peek is None: + raise ReadException, "Nothing to read: '%s'" % self._generator.all() + if peek == '{': + return self._readObject() + elif peek == '[': + return self._readArray() + elif peek == '"': + return self._readString() + elif peek == '-' or peek.isdigit(): + return self._readNumber() + elif peek == 't': + return self._readTrue() + elif peek == 'f': + return self._readFalse() + elif peek == 'n': + return self._readNull() + elif peek == '/': + self._readComment() + return self._read() + else: + raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all() + + def _readTrue(self): + self._assertNext('t', "true") + self._assertNext('r', "true") + self._assertNext('u', "true") + self._assertNext('e', "true") + return True + + def _readFalse(self): + self._assertNext('f', "false") + self._assertNext('a', "false") + self._assertNext('l', "false") + self._assertNext('s', "false") + self._assertNext('e', "false") + return False + + def _readNull(self): + self._assertNext('n', "null") + self._assertNext('u', "null") + self._assertNext('l', "null") + self._assertNext('l', "null") + return None + + def _assertNext(self, ch, target): + if self._next() != ch: + raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all()) + + def _readNumber(self): + isfloat = False + result = self._next() + peek = self._peek() + while peek is not None and (peek.isdigit() or peek == "."): + isfloat = isfloat or peek == "." + result = result + self._next() + peek = self._peek() + try: + if isfloat: + return float(result) + else: + return int(result) + except ValueError: + raise ReadException, "Not a valid JSON number: '%s'" % result + + def _readString(self): + result = "" + assert self._next() == '"' + try: + while self._peek() != '"': + ch = self._next() + if ch == "\\": + ch = self._next() + if ch in 'brnft': + ch = self.escapes[ch] + elif ch == "u": + ch4096 = self._next() + ch256 = self._next() + ch16 = self._next() + ch1 = self._next() + n = 4096 * self._hexDigitToInt(ch4096) + n += 256 * self._hexDigitToInt(ch256) + n += 16 * self._hexDigitToInt(ch16) + n += self._hexDigitToInt(ch1) + ch = unichr(n) + elif ch not in '"/\\': + raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all()) + result = result + ch + except StopIteration: + raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all() + assert self._next() == '"' + return result + + def _hexDigitToInt(self, ch): + try: + result = self.hex_digits[ch.upper()] + except KeyError: + try: + result = int(ch) + except ValueError: + raise ReadException, "The character %s is not a hex digit." % ch + return result + + def _readComment(self): + assert self._next() == "/" + second = self._next() + if second == "/": + self._readDoubleSolidusComment() + elif second == '*': + self._readCStyleComment() + else: + raise ReadException, "Not a valid JSON comment: %s" % self._generator.all() + + def _readCStyleComment(self): + try: + done = False + while not done: + ch = self._next() + done = (ch == "*" and self._peek() == "/") + if not done and ch == "/" and self._peek() == "*": + raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all() + self._next() + except StopIteration: + raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all() + + def _readDoubleSolidusComment(self): + try: + ch = self._next() + while ch != "\r" and ch != "\n": + ch = self._next() + except StopIteration: + pass + + def _readArray(self): + result = [] + assert self._next() == '[' + done = self._peek() == ']' + while not done: + item = self._read() + result.append(item) + self._eatWhitespace() + done = self._peek() == ']' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert ']' == self._next() + return result + + def _readObject(self): + result = {} + assert self._next() == '{' + done = self._peek() == '}' + while not done: + key = self._read() + if type(key) is not types.StringType: + raise ReadException, "Not a valid JSON object key (should be a string): %s" % key + self._eatWhitespace() + ch = self._next() + if ch != ":": + raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch) + self._eatWhitespace() + val = self._read() + result[key] = val + self._eatWhitespace() + done = self._peek() == '}' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert self._next() == "}" + return result + + def _eatWhitespace(self): + p = self._peek() + while p is not None and p in string.whitespace or p == '/': + if p == '/': + self._readComment() + else: + self._next() + p = self._peek() + + def _peek(self): + return self._generator.peek() + + def _next(self): + return self._generator.next() + +class JsonWriter(object): + + def _append(self, s): + self._results.append(s) + + def write(self, obj, escaped_forward_slash=False): + self._escaped_forward_slash = escaped_forward_slash + self._results = [] + self._write(obj) + return "".join(self._results) + + def _write(self, obj): + ty = type(obj) + if ty is types.DictType: + n = len(obj) + self._append("{") + for k, v in obj.items(): + self._write(k) + self._append(":") + self._write(v) + n = n - 1 + if n > 0: + self._append(",") + self._append("}") + elif ty is types.ListType or ty is types.TupleType: + n = len(obj) + self._append("[") + for item in obj: + self._write(item) + n = n - 1 + if n > 0: + self._append(",") + self._append("]") + elif ty is types.StringType or ty is types.UnicodeType: + self._append('"') + obj = obj.replace('\\', r'\\') + if self._escaped_forward_slash: + obj = obj.replace('/', r'\/') + obj = obj.replace('"', r'\"') + obj = obj.replace('\b', r'\b') + obj = obj.replace('\f', r'\f') + obj = obj.replace('\n', r'\n') + obj = obj.replace('\r', r'\r') + obj = obj.replace('\t', r'\t') + self._append(obj) + self._append('"') + elif ty is types.IntType or ty is types.LongType: + self._append(str(obj)) + elif ty is types.FloatType: + self._append("%f" % obj) + elif obj is True: + self._append("true") + elif obj is False: + self._append("false") + elif obj is None: + self._append("null") + else: + raise WriteException, "Cannot write in JSON: %s" % repr(obj) + +def write(obj, escaped_forward_slash=False): + return JsonWriter().write(obj, escaped_forward_slash) + +def read(s): + return JsonReader().read(s) diff --git a/webui/src/com/WebUI/WebUIApp.gwt.xml b/webui/src/com/WebUI/WebUIApp.gwt.xml new file mode 100644 index 000000000..3134f92d0 --- /dev/null +++ b/webui/src/com/WebUI/WebUIApp.gwt.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/webui/src/com/WebUI/client/WebUI.java b/webui/src/com/WebUI/client/WebUI.java new file mode 100644 index 000000000..66e93dbd1 --- /dev/null +++ b/webui/src/com/WebUI/client/WebUI.java @@ -0,0 +1,497 @@ +/* + Copyright (c) 2006 Alon Zakai ('Kripken') + + 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 2, 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package com.WebUI.client; + +import java.lang.Throwable; +import java.lang.System; + +//import java.util.Map; +//import java.util.HashMap; +import java.util.Iterator; + +//import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Label; +//import com.google.gwt.user.client.HistoryListener; + +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Command; +import com.google.gwt.core.client.EntryPoint; + +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.MenuItem; +import com.google.gwt.user.client.ui.DockPanel; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.SourcesTableEvents; +import com.google.gwt.user.client.ui.TableListener; + +import com.google.gwt.http.client.RequestBuilder; +import com.google.gwt.http.client.RequestException; +import com.google.gwt.http.client.RequestCallback; +import com.google.gwt.http.client.Request; +import com.google.gwt.http.client.Response; +import com.google.gwt.http.client.RequestTimeoutException; + +import com.google.gwt.json.client.JSONParser; +import com.google.gwt.json.client.JSONValue; +import com.google.gwt.json.client.JSONObject; + +public class WebUIUtilities { + public static String round(double value, int places) { + String ret = String.valueOf(value); + int temp = ret.indexOf("."); + + if (temp == -1) { + return ret; + } + + return (ret.substring(0, temp + places)); + } + + public static String getDataRate(double value) { + return (getDataAmount(value) + "/s"); + } + + public static String getDataAmount(double value) { + double val = 0; + String units; + + if (value < 1048576) { + val = value / 1024.0; + units = "KB"; + } else if (value < 1073741824) { + val = value / 1048576.0; + units = "MB"; + } else { + val = value / 1073741824.0; + units = "GB"; + } + + return (round(val, 2) + " " + units); + } + + public static native void gotoURL(String newURL) /*-{ + window.alert(location.href); + location.href = "www.cnn.com"; + window.alert(location.href); + }-*/; +} + +public class TorrentInfo { + public long unique_ID; + public long queue_pos; + public String name; + public double download_rate; + public double upload_rate; + public long total_seeds; + public long total_peers; + public long num_seeds; + public long num_peers; +} + +public class TorrentListAction implements TableListener { + private TorrentList mainList; + + public TorrentListAction(TorrentList list) { + mainList = list; + } + + public void onCellClicked(SourcesTableEvents sender, int row, int cell) { + // Select the row that was clicked (-1 to account for header row). + if (row > 0) + mainList.selectRow(row - 1); + } +} + +public class TorrentList extends FlexTable { + private int selectedRow = -1; + private TorrentListAction action = new TorrentListAction(this); + private TorrentInfo[] oldTorrents = null; + + public void init() { + setStyleName("torrentlist"); + setCellSpacing(0); + setCellPadding(2); + + // Headers row + setText(0, 0, "#"); // We need a hashmap for names to columns, for the future + setText(0, 1, "Name"); + setText(0, 2, "Seeds"); + setText(0, 3, "Peers"); + setText(0, 4, "Download"); + setText(0, 5, "Upload"); + getRowFormatter().addStyleName(0, "torrentList-Title"); + + addTableListener(action); + } + + public void applyTorrents(TorrentInfo[] torrents) { +//Window.alert("Applying torrents in tablelist"); + // Add new torrents and update existing ones + for (int x = 0; x < torrents.length; x++) { + int tempIndex = getIndexByUniqueID(torrents[x].unique_ID); + if (tempIndex == -1) { + tempIndex = getRowCount()-1; // (-1 because of headers) + } + updateTorrent(tempIndex, torrents[x]); + } + + // Delete torrents no longer with us. CHANGE selectedRow to -1 if the selected row is dead! + // ... + + // Save the new torrents, for the next comparison + oldTorrents = torrents; + + // Select a row, if none is currently selected + if (getRowCount() > 1 && selectedRow == -1) { +// Window.alert("Selecting 1"); + selectRow(0); + } else { +// Window.alert("No Selecting"); + } + } + + private int getIndexByUniqueID(long unique_ID) { + if (oldTorrents == null) { + return -1; + } + + for (int x = 0; x < oldTorrents.length; x++) { + if (oldTorrents[x].unique_ID == unique_ID) { + return x; + } + } + + return -1; + } + + private void updateTorrent(int row, TorrentInfo torrent) { + row = row + 1; +//Window.alert("Updating torrent in tablelist: " + String.valueOf(row)); + setText(row, 0, String.valueOf(torrent.queue_pos)+1); // Humans like queues starting at 1 + setText(row, 1, torrent.name); + setText(row, 2, String.valueOf(torrent.num_seeds) + " (" + + String.valueOf(torrent.total_seeds) + ")"); + setText(row, 3, String.valueOf(torrent.num_peers) + " (" + + String.valueOf(torrent.total_peers) + ")"); + setText(row, 4, WebUIUtilities.getDataRate(torrent.download_rate)); + setText(row, 5, WebUIUtilities.getDataRate(torrent.upload_rate)); + setWidth("100%"); + } + + public void selectRow(int row) { + styleRow(selectedRow, false); + styleRow(row, true); + + selectedRow = row; +// Mail.get().displayItem(item); + } + + private void styleRow(int row, boolean selected) { + if (row != -1) { + if (selected) + getRowFormatter().addStyleName(row + 1, "torrentList-SelectedRow"); + else + getRowFormatter().removeStyleName(row + 1, "torrentList-SelectedRow"); + } + } } + +public class WebUIApp implements EntryPoint { + + private static final int STATUS_CODE_OK = 200; + private static final int TIMEOUT = 3000; + private static final int TIMER = 2000; // SHOULD BE 1000 - but more allows for debug + private static final int MAX_TIMER = TIMEOUT/TIMER; + + private DockPanel panel = new DockPanel(); + private MenuBar menu = new MenuBar(); + private TorrentList torrentList = new TorrentList(); + private Label statusBar = new Label(); + + private Request currRequest; + private boolean waiting = false; + private int currTimer = 0; + private JSONValue torrentsJSON; + private TorrentInfo[] currTorrents; + + public void onModuleLoad() { +//Window.alert("Module Load"); + + // Buttons +// final Button button = new Button("Click here..."); + + // Menus + MenuBar menu0 = new MenuBar(true); + + menu0.addItem("Quit", true, new Command() { + public void execute() { + doPost("/", "quit", new RequestCallback() { + public void onResponseReceived(Request request, Response response) { + } + public void onError(Request request, Throwable e) { + } + }); + + // Move to new page, a "bye" page. + quit(); +// WebUIUtilities.gotoURL("about:blank"); +// Window.alert("Bye."); + } + }); + + menu.addItem(new MenuItem("File", menu0)); + menu.setWidth("100%"); + + // Table list + torrentList.init(); +// torrentList.addTorrent("a.torrent"); + +// torrentList.selectRow(0); + + // Timer + Timer t = new Timer() { + public void run() { + heartBeat(); + } + }; + + t.scheduleRepeating(TIMER); // + + // Set up main panel + RootPanel.get().setStyleName("webui-Info"); + + panel.add(torrentList, DockPanel.CENTER); +// panel.add(statusBar, DockPanel.SOUTH); + + RootPanel.get().add(menu); + RootPanel.get().add(panel); + RootPanel.get().add(statusBar); +//Window.alert("end Module Load"); + } + + private void quit() { + RootPanel.get().remove(menu); + RootPanel.get().remove(panel); + RootPanel.get().remove(statusBar); + } + + // Called once per TIMER tick (usually 1 second?) + private void heartBeat() { + + // FOR NOW, just do it to fill torrentsJSON, that's it +// if (torrentsJSON == null) { + + if (true) { // We always tick, don't we? + // Send and/or cancel current server request + if (!waiting || currTimer == MAX_TIMER) { + if (currRequest != null) { + currRequest.cancel(); + } + doPost("/", "list", new RequestCallback() { + public void onResponseReceived(Request request, Response response) { + waiting = false; + + if (STATUS_CODE_OK == response.getStatusCode()) { + setStatusBar("Server responding.");// + response.getText()); + torrentsJSON = JSONParser.parse(response.getText()); + updateTorrentList(); + } else { + setStatusBar("Server gives an error: " + response.getHeadersAsString() + "," + response.getStatusCode() + "," + response.getStatusText() + "," + response.getText()); + } + } + + public void onError(Request request, Throwable e) { + waiting = false; + + if (e instanceof RequestTimeoutException) { + setStatusBar("Server timed out.");// + e.getMessage()); + } else { + setStatusBar("Server gave an ODD error: " + e.getMessage()); + } + } + }); + +// setStatusBar("Sent request..."); + waiting = true; + currTimer = 0; + } else { +// setStatusBar("Still waiting..." + String.valueOf(currTimer)); + currTimer = currTimer + 1; + } + } else { + setStatusBar("Core off."); + } +// } // FOR NOW + + // Update torrent list? +// updateTorrentList(); + } + + private void setStatusBar(String text) { + statusBar.setText(String.valueOf(System.currentTimeMillis()) + ":" + text); +// statusBar.setText(String.valueOf(System.currentTimeMillis()) + ":" + text + "\r\n
" + statusBar.getText()); + } + + // Need to allow different callbacks from the post... + private void doPost(String url, String postData, RequestCallback callback) { + RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url); + builder.setTimeoutMillis(TIMEOUT); + + try { + currRequest = builder.sendRequest(postData, callback); + } catch (RequestException e) { + waiting = false; + setStatusBar("Failed to send a POST request: " + e.getMessage()); + } + } + + // Update torrent list, using torrentsJSON (which was updated in the POST callback) + private void updateTorrentList() { + if (torrentsJSON == null) { + return; + } +//Window.alert("UpdateTorrentList - got torrentsJSON"); + currTorrents = new TorrentInfo[torrentsJSON.isArray().size()]; + + JSONObject curr; + + for (int x = 0; x < torrentsJSON.isArray().size(); x++) { + curr = torrentsJSON.isArray().get(x).isObject(); + + currTorrents[x] = new TorrentInfo(); + currTorrents[x].unique_ID = (long) curr.get("unique_ID").isNumber().getValue(); + currTorrents[x].queue_pos = (long) curr.get("queue_pos").isNumber().getValue(); + currTorrents[x].name = curr.get("name").isString().stringValue(); + currTorrents[x].download_rate = curr.get("download_rate").isNumber().getValue(); + currTorrents[x].upload_rate = curr.get("upload_rate").isNumber().getValue(); + currTorrents[x].total_seeds = (long)curr.get("total_seeds").isNumber().getValue(); + currTorrents[x].total_peers = (long)curr.get("total_peers").isNumber().getValue(); + currTorrents[x].num_seeds = (long)curr.get("num_seeds").isNumber().getValue(); + currTorrents[x].num_peers = (long)curr.get("num_peers").isNumber().getValue(); + } + + torrentList.applyTorrents(currTorrents); +//Window.alert("end UpdateTorrentList"); + } + + // A debug convenience function + private void dumpJSON(JSONValue value) { + if (value.isArray() != null) { + Window.alert("Array; size: " + String.valueOf(value.isArray().size())); + + for (int x = 0; x < value.isArray().size(); x++) { + dumpJSON(value.isArray().get(x)); + } + } else if (value.isBoolean() != null) { + Window.alert("Boolean" + String.valueOf(value.isBoolean().booleanValue())); + } else if (value.isNull() != null) { + Window.alert("NULL"); + } else if (value.isNumber() != null) { + Window.alert("Number" + String.valueOf(value.isNumber().getValue())); + } else if (value.isObject() != null) { + Window.alert("Object size: " + String.valueOf(value.isObject().size())); + + Iterator it = value.isObject().keySet().iterator(); + String key; + for (int x = 0; x < value.isObject().size(); x++) { + key = String.valueOf(it.next()); + Window.alert("(Key:)" + key); + dumpJSON(value.isObject().get(key)); + } + } else if (value.isString() != null) { + Window.alert("String: " + value.isString().stringValue()); + } else { + Window.alert("WHAT IS THIS JSON?!"); + } + } + +} + + + + +/*public class MenuAction implements Command { + public void execute() { + Window.alert("Thank you for selecting a menu item."); +// Window.alert("Thank you for selecting a menu item."); + } +}*/ + +/* +Map phoneBook = new HashMap(); +phoneBook.put("Sally Smart", "555-9999"); +phoneBook.put("John Doe", "555-1212"); +phoneBook.put("J. Random Hacker", "555-1337"); + +The get method is used to access a key; for example, the value of the expression phoneBook.get("Sally Smart") is "555-9999". +*/ + + + + + +/* final Label label = new Label(); +// History.addHistoryListener(this); +// Window.alert("Nifty, eh?"); + + button.addClickListener(new ClickListener() { + public void onClick(Widget sender) { + if (label.getText().equals("")) + label.setText("Good, it works."); + else + label.setText(""); + } + }); + + // Assume that the host HTML has elements defined whose + // IDs are "slot1", "slot2". In a real app, you probably would not want + // to hard-code IDs. Instead, you could, for example, search for all + // elements with a particular CSS class and replace them with widgets. + // + RootPanel.get("slot1").add(button); + RootPanel.get("slot2").add(label); +*/ + + +/* private HashMap parsePythonDict(String pythonDict) { + HashMap ret = new HashMap(); + int startIndex, endIndex = 0; + String key, val; + + while (pythonDict.indexOf("'", endIndex + 1) != -1) { + startIndex = pythonDict.indexOf("'", endIndex + 1) + 1; + endIndex = pythonDict.indexOf("'", startIndex + 1); + key = pythonDict.substring(startIndex, endIndex); + + startIndex = endIndex + 3; + endIndex = pythonDict.indexOf(",", startIndex + 1); // BUG POTENTIAL + if (endIndex == -1) { + endIndex = pythonDict.lastIndexOf("}"); + } + val = pythonDict.substring(startIndex, endIndex); + + ret.put(key, val); + Window.alert(key + " :: " + val); + } + + return ret; + }*/ diff --git a/webui/src/com/WebUI/public/WebUI.css b/webui/src/com/WebUI/public/WebUI.css new file mode 100644 index 000000000..4f75caa73 --- /dev/null +++ b/webui/src/com/WebUI/public/WebUI.css @@ -0,0 +1,268 @@ +body { + background-color: white; + color: black; + font-family: Arial, sans-serif; + font-size: medium; + margin: 20px 20px 20px 20px; +} + +code { + font-size: small; +} + +a { + color: darkblue; +} + +a:visited { + color: darkblue; +} + +.gwt-BorderedPanel { +} + +.gwt-Button { +} + +.gwt-Canvas { +} + +.gwt-CheckBox { + font-size: smaller; +} + +.gwt-DialogBox { + sborder: 8px solid #C3D9FF; + border: 2px outset; + background-color: white; +} + +.gwt-DialogBox .Caption { + background-color: #C3D9FF; + padding: 3px; + margin: 2px; + font-weight: bold; + cursor: default; +} + +.gwt-FileUpload { +} + +.gwt-Frame { +} + +.gwt-HorizontalSplitter .Bar { + width: 8px; + background-color: #C3D9FF; +} + +.gwt-VerticalSplitter .Bar { + height: 8px; + background-color: #C3D9FF; +} + +.gwt-HTML { + font-size: small; +} + +.gwt-Hyperlink { +} + +.gwt-Image { +} + +.gwt-Label { + font-size: medium; +} + +.gwt-ListBox { +} + +.gwt-MenuBar { + background-color: #C3D9FF; + border: 1px solid #87B3FF; + cursor: default; +} + +.gwt-MenuBar .gwt-MenuItem { + padding: 1px 4px 1px 4px; + font-size: medium; + cursor: default; +} + +.gwt-MenuBar .gwt-MenuItem-selected { + background-color: #E8EEF7; +} + +.gwt-PasswordTextBox { +} + +.gwt-RadioButton { + font-size: smaller; +} + +.gwt-TabPanel { +} + +.gwt-TabPanelBottom { + border-left: 1px solid #87B3FF; +} + +.gwt-TabBar { + background-color: #C3D9FF; + font-size: smaller; +} + +.gwt-TabBar .gwt-TabBarFirst { + height: 100%; + border-bottom: 1px solid #87B3FF; + padding-left: 3px; +} + +.gwt-TabBar .gwt-TabBarRest { + border-bottom: 1px solid #87B3FF; + padding-right: 3px; +} + +.gwt-TabBar .gwt-TabBarItem { + border-top: 1px solid #C3D9FF; + border-bottom: 1px solid #87B3FF; + padding: 2px; + cursor: pointer; + cursor: hand; +} + +.gwt-TabBar .gwt-TabBarItem-selected { + font-weight: bold; + background-color: #E8EEF7; + border-top: 1px solid #87B3FF; + border-left: 1px solid #87B3FF; + border-right: 1px solid #87B3FF; + border-bottom: 1px solid #E8EEF7; + padding: 2px; + cursor: default; +} + +.gwt-TextArea { +} + +.gwt-TextBox { +} + +.gwt-Tree { +} + +.gwt-Tree .gwt-TreeItem { + font-size: smaller; +} + +.gwt-Tree .gwt-TreeItem-selected { + background-color: #C3D9FF; +} + +.gwt-StackPanel { +} + +.gwt-StackPanel .gwt-StackPanelItem { + background-color: #C3D9FF; + cursor: pointer; + cursor: hand; +} + +.gwt-StackPanel .gwt-StackPanelItem-selected { +} + +.webui-Info { + background-color: #C3D9FF; + padding: 10px 10px 2px 10px; + font-size: smaller; +} + +/* -------------------------------------------------------------------------- +.ks-Sink { + border: 8px solid #C3D9FF; + background-color: #E8EEF7; + width: 100%; + height: 24em; +} + + +.ks-List { + margin-top: 8px; + margin-bottom: 8px; + font-size: smaller; +} + +.ks-List .ks-SinkItem { + width: 100%; + padding: 0.3em; + padding-right: 16px; + cursor: pointer; + cursor: hand; +} + +.ks-List .ks-SinkItem-selected { + background-color: #C3D9FF; +} + +.ks-images-Image { + margin: 8px; +} + +.ks-images-Button { + margin: 8px; + cursor: pointer; + cursor: hand; +} + +.ks-layouts { + margin: 8px; +} + +.ks-layouts-Label { + background-color: #C3D9FF; + font-weight: bold; + margin-top: 1em; + padding: 2px 0px 2px 0px; + width: 100%; +} + +.ks-layouts-Scroller { + height: 128px; + border: 2px solid #C3D9FF; + padding: 8px; + margin: 8px; +} + +.ks-popups-Popup { + background-color: white; + border: 1px solid #87B3FF; + padding: 4px; +} + +.infoProse { + margin: 8px; +} + +------*/ + +table.torrentlist { + margin: 1ex 0ex 1ex 0ex; +// border-spacing: 5.5ex 5.5ex 6.5ex 5.5ex; + border: medium solid black; +// border-color: black; +} + +table.torrentlist td { +// border: 50em; + padding: 1ex; +} + +.torrentList-Title { + background-color: rgb(175,175,255); +} + +.torrentList-SelectedRow { + background-color: rgb(140,140,255); +} + diff --git a/webui/src/com/WebUI/public/WebUI.html b/webui/src/com/WebUI/public/WebUI.html new file mode 100644 index 000000000..296c6b368 --- /dev/null +++ b/webui/src/com/WebUI/public/WebUI.html @@ -0,0 +1,58 @@ + + + + + + + WebUI 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webui/webuiserver.py b/webui/webuiserver.py new file mode 100644 index 000000000..0a3c60759 --- /dev/null +++ b/webui/webuiserver.py @@ -0,0 +1,211 @@ +# +# Copyright (c) 2006 Alon Zakai ('Kripken') +# +# 2006-15-9 +# +# 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 2, 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +# Docs: +# +# All httpserver-related issues are done through GET (html, javascript, css, +# etc.). All torrentcore issues are doen through POST. +# + +import time +import BaseHTTPServer +import sys, os +import webbrowser + +sys.path.append("/media/sda2/svn/deluge-trac/trunk/library") + +import flood # or whatever the core is renamed to be +import json + +# Constants + +HOST_NAME = 'localhost' +PORT_NUMBER = 9999 + +HTML_DIR = "www/com.WebUI.WebUIApp" + +HEADERS_TEXT = "text/plain" +HEADERS_HTML = "text/html" +HEADERS_CSS = "text/css" +HEADERS_JS = "text/javascript" + +manager = None +httpd = None + +class webuiServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def get_secret_str(self): + return "?" + secret + + def pulse(self): + global manager + + manager.handle_events() + + def write_headers(self, content_type, data_length=None): + self.send_response(200) + self.send_header("Content-type", content_type) + if data_length is not None: + self.send_header("Content-length", data_length) + self.end_headers() + + def do_POST(self): + global manager + + input_length = int(self.headers.get('Content-length')) + command = self.rfile.read(input_length) + print "POST command:", command + + if command == "quit": + httpd.ready = False + +# self.write_headers(HEADERS_TEXT) +# self.wfile.write("OK: quit") + # List torrents, and pulse the heartbeat + elif command == "list": + self.pulse() # Start by ticking the clock + + data = [] + unique_IDs = manager.get_unique_IDs() + for unique_ID in unique_IDs: + temp = manager.get_torrent_state(unique_ID) + temp["unique_ID"] = unique_ID # We add the unique_ID ourselves + data.append(temp) + + self.write_headers(HEADERS_TEXT) + self.wfile.write(json.write(data)) + else: + # Basically we can just send Python commands, to be run in exec(command)... but that + # would be slow, I guess + print "UNKNOWN POST COMMAND:", command + + def do_GET(self): +# self.wfile.write("

webuiServer 0.5.1.1

") + + print "Contacted from:", self.client_address + + if "?" in self.path: + command = self.path[1:self.path.find("?")] + else: + command = self.path[1:] + + if command == "": + command = "WebUI.html" + + if not self.path[-(len(self.get_secret_str())):] == self.get_secret_str(): + self.write_headers(HEADERS_HTML) + self.wfile.write("webuiServer") + self.wfile.write("

Invalid access. Run 'webuiserver SECRET', then access 'localhost:9999/?SECRET'.

") + self.wfile.write("") + return + + if "." in command: + extension = command[command.rfind("."):] + else: + extension = "" + + print "Handling: ", self.path, ":", command, ":", extension + + try: + filey = open("./" + HTML_DIR + "/" + command, 'rb') + lines = filey.readlines() + filey.close() + + data = "".join(lines) + + if extension == ".html": + self.write_headers(HEADERS_HTML, len(data)) + elif extension == ".js": + self.write_headers(HEADERS_JS, len(data)) + elif extension == ".css": + self.write_headers(HEADERS_CSS, len(data)) + else: + print "What is this?", extension + + self.wfile.write(data) + except IOError: + self.write_headers(HEADERS_HTML) + self.wfile.write("webuiServer") + self.wfile.write("

webuiServer 0.5.1.1

") + self.wfile.write("No such command: " + command) + + +class webuiServer(BaseHTTPServer.HTTPServer): + def serve_forever(self): + self.ready = True + while self.ready: + self.handle_request() + self.server_close() + +######## +# Main # +######## + +print "-------------------" +print "webuiServer 0.5.1.1" +print "-------------------" +print "" + +try: + secret = sys.argv[1] +except IndexError: + print "USAGE: 'webuiserver.py S', where S is the secret password used to access via a browser" + secret = "" + +if not secret == "": + +# manager.add_torrent("xubuntu-6.10-desktop-i386.iso.torrent", +# os.path.expanduser("~") + "/Temp", True) + + httpd = webuiServer((HOST_NAME, PORT_NUMBER), webuiServerHandler) + print time.asctime(), "HTTP Server Started - %s:%s" % (HOST_NAME, PORT_NUMBER) + + manager = flood.manager("FL", "0500", "webui", + os.path.expanduser("~") + "/Temp")#, blank_slate=True) + + webbrowser.open("localhost:9999/?" + secret) + + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + + print time.asctime(), "HTTP Server Stopped - %s:%s" % (HOST_NAME, PORT_NUMBER) + + print "Shutting down manager..." + manager.quit() + + +### OLD +# # Check if the manager is running +# if not command == "init": +# if manager is None: +# self.write_headers(HEADERS_TEXT) +# self.wfile.write("ERROR: manager is None") +# return +# +# if command == "init": +# if manager is not None: +# print "ERROR: Trying to init, but already active" +# return +# +# manager = webui.manager("FL", "0500", "webui", +# os.path.expanduser("~") + "/Temp")#, blank_slate=True) +# self.write_headers(HEADERS_TEXT) +# self.wfile.write("OK: init") diff --git a/webui/www/com.WebUI.WebUIApp/10953E833A2B9B18DAA93BB081DC8A7D.cache.html b/webui/www/com.WebUI.WebUIApp/10953E833A2B9B18DAA93BB081DC8A7D.cache.html new file mode 100644 index 000000000..5163e1d44 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/10953E833A2B9B18DAA93BB081DC8A7D.cache.html @@ -0,0 +1,797 @@ + + + +This script is part of module +com.WebUI.WebUIApp + diff --git a/webui/www/com.WebUI.WebUIApp/10953E833A2B9B18DAA93BB081DC8A7D.cache.xml b/webui/www/com.WebUI.WebUIApp/10953E833A2B9B18DAA93BB081DC8A7D.cache.xml new file mode 100644 index 000000000..03ee61345 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/10953E833A2B9B18DAA93BB081DC8A7D.cache.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/webui/www/com.WebUI.WebUIApp/11D20AAC454AFEE8A60D7380D38B0693.cache.html b/webui/www/com.WebUI.WebUIApp/11D20AAC454AFEE8A60D7380D38B0693.cache.html new file mode 100644 index 000000000..41b3db173 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/11D20AAC454AFEE8A60D7380D38B0693.cache.html @@ -0,0 +1,800 @@ + + + +This script is part of module +com.WebUI.WebUIApp + diff --git a/webui/www/com.WebUI.WebUIApp/11D20AAC454AFEE8A60D7380D38B0693.cache.xml b/webui/www/com.WebUI.WebUIApp/11D20AAC454AFEE8A60D7380D38B0693.cache.xml new file mode 100644 index 000000000..ad65ed482 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/11D20AAC454AFEE8A60D7380D38B0693.cache.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/webui/www/com.WebUI.WebUIApp/6A33FFDB2E9418B40D144BFD01370A52.cache.html b/webui/www/com.WebUI.WebUIApp/6A33FFDB2E9418B40D144BFD01370A52.cache.html new file mode 100644 index 000000000..a94f4e078 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/6A33FFDB2E9418B40D144BFD01370A52.cache.html @@ -0,0 +1,797 @@ + + + +This script is part of module +com.WebUI.WebUIApp + diff --git a/webui/www/com.WebUI.WebUIApp/6A33FFDB2E9418B40D144BFD01370A52.cache.xml b/webui/www/com.WebUI.WebUIApp/6A33FFDB2E9418B40D144BFD01370A52.cache.xml new file mode 100644 index 000000000..cc77b668d --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/6A33FFDB2E9418B40D144BFD01370A52.cache.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/webui/www/com.WebUI.WebUIApp/C7527B531338C30501EFDC3455BB151E.cache.html b/webui/www/com.WebUI.WebUIApp/C7527B531338C30501EFDC3455BB151E.cache.html new file mode 100644 index 000000000..5db1aff47 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/C7527B531338C30501EFDC3455BB151E.cache.html @@ -0,0 +1,797 @@ + + + +This script is part of module +com.WebUI.WebUIApp + diff --git a/webui/www/com.WebUI.WebUIApp/C7527B531338C30501EFDC3455BB151E.cache.xml b/webui/www/com.WebUI.WebUIApp/C7527B531338C30501EFDC3455BB151E.cache.xml new file mode 100644 index 000000000..6534357ad --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/C7527B531338C30501EFDC3455BB151E.cache.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/webui/www/com.WebUI.WebUIApp/WebUI.css b/webui/www/com.WebUI.WebUIApp/WebUI.css new file mode 100644 index 000000000..4f75caa73 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/WebUI.css @@ -0,0 +1,268 @@ +body { + background-color: white; + color: black; + font-family: Arial, sans-serif; + font-size: medium; + margin: 20px 20px 20px 20px; +} + +code { + font-size: small; +} + +a { + color: darkblue; +} + +a:visited { + color: darkblue; +} + +.gwt-BorderedPanel { +} + +.gwt-Button { +} + +.gwt-Canvas { +} + +.gwt-CheckBox { + font-size: smaller; +} + +.gwt-DialogBox { + sborder: 8px solid #C3D9FF; + border: 2px outset; + background-color: white; +} + +.gwt-DialogBox .Caption { + background-color: #C3D9FF; + padding: 3px; + margin: 2px; + font-weight: bold; + cursor: default; +} + +.gwt-FileUpload { +} + +.gwt-Frame { +} + +.gwt-HorizontalSplitter .Bar { + width: 8px; + background-color: #C3D9FF; +} + +.gwt-VerticalSplitter .Bar { + height: 8px; + background-color: #C3D9FF; +} + +.gwt-HTML { + font-size: small; +} + +.gwt-Hyperlink { +} + +.gwt-Image { +} + +.gwt-Label { + font-size: medium; +} + +.gwt-ListBox { +} + +.gwt-MenuBar { + background-color: #C3D9FF; + border: 1px solid #87B3FF; + cursor: default; +} + +.gwt-MenuBar .gwt-MenuItem { + padding: 1px 4px 1px 4px; + font-size: medium; + cursor: default; +} + +.gwt-MenuBar .gwt-MenuItem-selected { + background-color: #E8EEF7; +} + +.gwt-PasswordTextBox { +} + +.gwt-RadioButton { + font-size: smaller; +} + +.gwt-TabPanel { +} + +.gwt-TabPanelBottom { + border-left: 1px solid #87B3FF; +} + +.gwt-TabBar { + background-color: #C3D9FF; + font-size: smaller; +} + +.gwt-TabBar .gwt-TabBarFirst { + height: 100%; + border-bottom: 1px solid #87B3FF; + padding-left: 3px; +} + +.gwt-TabBar .gwt-TabBarRest { + border-bottom: 1px solid #87B3FF; + padding-right: 3px; +} + +.gwt-TabBar .gwt-TabBarItem { + border-top: 1px solid #C3D9FF; + border-bottom: 1px solid #87B3FF; + padding: 2px; + cursor: pointer; + cursor: hand; +} + +.gwt-TabBar .gwt-TabBarItem-selected { + font-weight: bold; + background-color: #E8EEF7; + border-top: 1px solid #87B3FF; + border-left: 1px solid #87B3FF; + border-right: 1px solid #87B3FF; + border-bottom: 1px solid #E8EEF7; + padding: 2px; + cursor: default; +} + +.gwt-TextArea { +} + +.gwt-TextBox { +} + +.gwt-Tree { +} + +.gwt-Tree .gwt-TreeItem { + font-size: smaller; +} + +.gwt-Tree .gwt-TreeItem-selected { + background-color: #C3D9FF; +} + +.gwt-StackPanel { +} + +.gwt-StackPanel .gwt-StackPanelItem { + background-color: #C3D9FF; + cursor: pointer; + cursor: hand; +} + +.gwt-StackPanel .gwt-StackPanelItem-selected { +} + +.webui-Info { + background-color: #C3D9FF; + padding: 10px 10px 2px 10px; + font-size: smaller; +} + +/* -------------------------------------------------------------------------- +.ks-Sink { + border: 8px solid #C3D9FF; + background-color: #E8EEF7; + width: 100%; + height: 24em; +} + + +.ks-List { + margin-top: 8px; + margin-bottom: 8px; + font-size: smaller; +} + +.ks-List .ks-SinkItem { + width: 100%; + padding: 0.3em; + padding-right: 16px; + cursor: pointer; + cursor: hand; +} + +.ks-List .ks-SinkItem-selected { + background-color: #C3D9FF; +} + +.ks-images-Image { + margin: 8px; +} + +.ks-images-Button { + margin: 8px; + cursor: pointer; + cursor: hand; +} + +.ks-layouts { + margin: 8px; +} + +.ks-layouts-Label { + background-color: #C3D9FF; + font-weight: bold; + margin-top: 1em; + padding: 2px 0px 2px 0px; + width: 100%; +} + +.ks-layouts-Scroller { + height: 128px; + border: 2px solid #C3D9FF; + padding: 8px; + margin: 8px; +} + +.ks-popups-Popup { + background-color: white; + border: 1px solid #87B3FF; + padding: 4px; +} + +.infoProse { + margin: 8px; +} + +------*/ + +table.torrentlist { + margin: 1ex 0ex 1ex 0ex; +// border-spacing: 5.5ex 5.5ex 6.5ex 5.5ex; + border: medium solid black; +// border-color: black; +} + +table.torrentlist td { +// border: 50em; + padding: 1ex; +} + +.torrentList-Title { + background-color: rgb(175,175,255); +} + +.torrentList-SelectedRow { + background-color: rgb(140,140,255); +} + diff --git a/webui/www/com.WebUI.WebUIApp/WebUI.html b/webui/www/com.WebUI.WebUIApp/WebUI.html new file mode 100644 index 000000000..296c6b368 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/WebUI.html @@ -0,0 +1,58 @@ + + + + + + + WebUI 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webui/www/com.WebUI.WebUIApp/com.WebUI.WebUIApp.nocache.html b/webui/www/com.WebUI.WebUIApp/com.WebUI.WebUIApp.nocache.html new file mode 100644 index 000000000..693952658 --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/com.WebUI.WebUIApp.nocache.html @@ -0,0 +1,118 @@ + + + +This script is part of module com.WebUI.WebUIApp + + diff --git a/webui/www/com.WebUI.WebUIApp/gwt.js b/webui/www/com.WebUI.WebUIApp/gwt.js new file mode 100644 index 000000000..916359fdd --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/gwt.js @@ -0,0 +1,578 @@ +// Copyright 2006 Google Inc. All Rights Reserved. +// This startup script should be included in host pages either just after +// or inside the after module tags. +// + +////////////////////////////////////////////////////////////////////////////// +// DynamicResources +// + +function DynamicResources() { + this.pendingElemsBySrc_ = {}; + this.pendingScriptElems_ = new Array(); +} +DynamicResources.prototype = {}; + +// The array is set up such that, pairwise, the entries are (src, readyFnStr). +// Called once for each module that is attached to the host page. +// It is theoretically possible that addScripts() could be called reentrantly +// if the browser event loop is pumped during this function and an iframe loads; +// we may want to enhance this method in the future to support that case. +DynamicResources.prototype.addScripts = function(scriptArray, insertBeforeElem) { + var wasEmpty = (this.pendingScriptElems_.length == 0); + var anyAdded = false; + for (var i = 0, n = scriptArray.length; i < n; i += 2) { + var src = scriptArray[i]; + if (this.pendingElemsBySrc_[src]) { + // Don't load the same script twice. + continue; + } + // Set up the element but don't add it to the DOM until its turn. + anyAdded = true; + var e = document.createElement("script"); + this.pendingElemsBySrc_[src] = e; + var readyFn; + eval("readyFn = " + scriptArray[i+1]); + e.__readyFn = readyFn; + e.type = "text/javascript"; + e.src = src; + e.__insertBeforeElem = insertBeforeElem; + this.pendingScriptElems_ = this.pendingScriptElems_.concat(e); + } + + if (wasEmpty && anyAdded) { + // Kickstart. + this.injectScript(this.pendingScriptElems_[0]); + } +} + +DynamicResources.prototype.injectScript = function(scriptElem) { + var parentElem = scriptElem.__insertBeforeElem.parentNode; + parentElem.insertBefore(scriptElem, scriptElem.__insertBeforeElem); +} + +DynamicResources.prototype.addStyles = function(styleSrcArray, insertBeforeElem) { + var parent = insertBeforeElem.parentNode; + for (var i = 0, n = styleSrcArray.length; i < n; ++i) { + var src = styleSrcArray[i]; + if (this.pendingElemsBySrc_[src]) + continue; + var e = document.createElement("link"); + this.pendingElemsBySrc_[src] = e; + e.type = "text/css"; + e.rel = "stylesheet"; + e.href = src; + parent.insertBefore(e, insertBeforeElem); + } +} + +DynamicResources.prototype.isReady = function() { + var elems = this.pendingScriptElems_; + if (elems.length > 0) { + var e = elems[0]; + if (!e.__readyFn()) { + // The pending script isn't ready yet. + return false; + } + + // The pending script has now finished loading. Enqueue the next, if any. + e.__readyFn = null; + elems.shift(); + if (elems.length > 0) { + // There is another script. + this.injectScript(elems[0]); + return false; + } + } + + // There are no more pending scripts. + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ModuleControlBlock +// +function ModuleControlBlock(metaElem, rawName) { + var parts = ["", rawName]; + var i = rawName.lastIndexOf("="); + if (i != -1) { + parts[0] = rawName.substring(0, i) + '/'; + parts[1] = rawName.substring(i+1); + } + + this.metaElem_ = metaElem; + this.baseUrl_ = parts[0]; + this.name_ = parts[1]; + this.compilationLoaded_ = false; + this.frameWnd_ = null; +} +ModuleControlBlock.prototype = {}; + +/** + * Determines whether this module is fully loaded and ready to run. + */ +ModuleControlBlock.prototype.isReady = function() { + return this.compilationLoaded_; +}; + +/** + * Called when the compilation for this module is loaded. + */ +ModuleControlBlock.prototype.compilationLoaded = function(frameWnd) { + this.frameWnd_ = frameWnd; + this.compilationLoaded_ = true; +} + +/** + * Gets the logical module name, not including a base url prefix if one was + * specified. + */ +ModuleControlBlock.prototype.getName = function() { + return this.name_; +} + +/** + * Gets the base URL of the module, guaranteed to end with a slash. + */ +ModuleControlBlock.prototype.getBaseURL = function() { + return this.baseUrl_; +} + +/** + * Gets the window of the module's frame. + */ +ModuleControlBlock.prototype.getModuleFrameWindow = function() { + return this.frameWnd_; +} + +/** + * Injects a set of dynamic scripts. + * The array is set up such that, pairwise, the entries are (src, readyFnStr). + */ +ModuleControlBlock.prototype.addScripts = function(scriptSrcArray) { + return ModuleControlBlocks.dynamicResources_.addScripts(scriptSrcArray, this.metaElem_); +} + +/** + * Injects a set of dynamic styles. + */ +ModuleControlBlock.prototype.addStyles = function(styleSrcArray) { + return ModuleControlBlocks.dynamicResources_.addStyles(styleSrcArray, this.metaElem_); +} + +////////////////////////////////////////////////////////////////////////////// +// ModuleControlBlocks +// +function ModuleControlBlocks() { + this.blocks_ = []; +} +ModuleControlBlocks.dynamicResources_ = new DynamicResources(); // "static" +ModuleControlBlocks.prototype = {}; + +/** + * Adds a module control control block for the named module. + * @param metaElem the meta element that caused the module to be added + * @param name the name of the module being added, optionally preceded by + * an alternate base url of the form "_path_=_module_". + */ +ModuleControlBlocks.prototype.add = function(metaElem, name) { + var mcb = new ModuleControlBlock(metaElem, name); + this.blocks_ = this.blocks_.concat(mcb); +}; + +/** + * Determines whether all the modules are loaded and ready to run. + */ +ModuleControlBlocks.prototype.isReady = function() { + for (var i = 0, n = this.blocks_.length; i < n; ++i) { + var mcb = this.blocks_[i]; + if (!mcb.isReady()) { + return false; + } + } + + // Are there any pending dynamic resources (e.g. styles, scripts)? + if (!ModuleControlBlocks.dynamicResources_.isReady()) { + // No, we're still waiting on one or more dynamic resources. + return false; + } + + return true; +} + +/** + * Determines whether there are any module control blocks. + */ +ModuleControlBlocks.prototype.isEmpty = function() { + return this.blocks_.length == 0; +} + +/** + * Gets the module control block at the specified index. + */ +ModuleControlBlocks.prototype.get = function(index) { + return this.blocks_[index]; +} + +/** + * Injects an iframe for each module. + */ +ModuleControlBlocks.prototype.injectFrames = function() { + for (var i = 0, n = this.blocks_.length; i < n; ++i) { + var mcb = this.blocks_[i]; + + // Insert an iframe for the module + var iframe = document.createElement("iframe"); + var selectorUrl = mcb.getBaseURL() + mcb.getName() + ".nocache.html"; + selectorUrl += "?" + (__gwt_isHosted() ? "h&" : "" ) + i; + var unique = new Date().getTime(); + selectorUrl += "&" + unique; + iframe.style.border = '0px'; + iframe.style.width = '0px'; + iframe.style.height = '0px'; + + // Fragile browser-specific ordering issues below + +/*@cc_on + // prevent extra clicky noises on IE + iframe.src = selectorUrl; +@*/ + + if (document.body.firstChild) { + document.body.insertBefore(iframe, document.body.firstChild); + } else { + document.body.appendChild(iframe); + } + +/*@cc_on + // prevent extra clicky noises on IE + return; +@*/ + + if (iframe.contentWindow) { + // Older Mozilla has a caching bug for the iframe and won't reload the nocache. + iframe.contentWindow.location.replace(selectorUrl); + } else { + // Older Safari doesn't have a contentWindow. + iframe.src = selectorUrl; + } + } +} + +/** + * Runs the entry point for each module. + */ +ModuleControlBlocks.prototype.run = function() { + for (var i = 0, n = this.blocks_.length; i < n; ++i) { + var mcb = this.blocks_[i]; + var name = mcb.getName(); + var frameWnd = mcb.getModuleFrameWindow(); + if (__gwt_isHosted()) { + if (!window.external.gwtOnLoad(frameWnd, name)) { + // Module failed to load. + if (__gwt_onLoadError) { + __gwt_onLoadError(name); + } else { + window.alert("Failed to load module '" + name + + "'.\nPlease see the log in the development shell for details."); + } + } + } else { + // The compilation itself handles calling the error function. + frameWnd.gwtOnLoad(__gwt_onLoadError, name); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// Globals +// + +var __gwt_retryWaitMillis = 10; +var __gwt_isHostPageLoaded = false; +var __gwt_metaProps = {}; +var __gwt_onPropertyError = null; +var __gwt_onLoadError = null; +var __gwt_moduleControlBlocks = new ModuleControlBlocks(); + +////////////////////////////////////////////////////////////////////////////// +// Common +// + +/** + * Determines whether or not the page is being loaded in the GWT hosted browser. + */ +function __gwt_isHosted() { + if (window.external && window.external.gwtOnLoad) { + // gwt.hybrid makes the hosted browser pretend not to be + if (document.location.href.indexOf("gwt.hybrid") == -1) { + return true; + } + } + return false; +} + +/** + * Tries to get a module control block based on a query string passed in from + * the caller. Used by iframes to get references back to their mcbs. + * @param queryString the entire query string as returned by location.search, + * which notably includes the leading '?' if one is specified + * @return the relevant module control block, or null if it cannot + * be derived based on queryString + */ +function __gwt_tryGetModuleControlBlock(queryString) { + if (queryString.length > 0) { + // The pattern is ?[h&][&] + var queryString = queryString.substring(1); + if (queryString.indexOf("h&") == 0) { + // Ignore the hosted mode flag here; only GWTShellServlet cares about it. + queryString = queryString.substring(2); + } + var pos = queryString.indexOf("&"); + if (pos >= 0) { + queryString = queryString.substring(0, pos); + } + var mcbIndex = parseInt(queryString); + if (!isNaN(mcbIndex)) { + var mcb = __gwt_moduleControlBlocks.get(mcbIndex); + return mcb; + } + // Ignore the unique number that remains on the query string. + } + return null; +} + +/** + * Parses meta tags from the host html. + * + * + * causes the specified module to be loaded + * + * + * statically defines a deferred binding client property + * + * + * specifies the name of a function to call if a client property is set to + * an invalid value (meaning that no matching compilation will be found) + * + * + * specifies the name of a function to call if an exception happens during + * bootstrapping or if a module throws an exception out of onModuleLoad(); + * the function should take a message parameter + */ +function __gwt_processMetas() { + var metas = document.getElementsByTagName("meta"); + for (var i = 0, n = metas.length; i < n; ++i) { + var meta = metas[i]; + var name = meta.getAttribute("name"); + if (name) { + if (name == "gwt:module") { + var moduleName = meta.getAttribute("content"); + if (moduleName) { + __gwt_moduleControlBlocks.add(meta, moduleName); + } + } else if (name == "gwt:property") { + var content = meta.getAttribute("content"); + if (content) { + var name = content, value = ""; + var eq = content.indexOf("="); + if (eq != -1) { + name = content.substring(0, eq); + value = content.substring(eq+1); + } + __gwt_metaProps[name] = value; + } + } else if (name == "gwt:onPropertyErrorFn") { + var content = meta.getAttribute("content"); + if (content) { + try { + __gwt_onPropertyError = eval(content); + } catch (e) { + window.alert("Bad handler \"" + content + + "\" for \"gwt:onPropertyErrorFn\""); + } + } + } else if (name == "gwt:onLoadErrorFn") { + var content = meta.getAttribute("content"); + if (content) { + try { + __gwt_onLoadError = eval(content); + } catch (e) { + window.alert("Bad handler \"" + content + + "\" for \"gwt:onLoadErrorFn\""); + } + } + } + } + } +} + +/** + * Determines the value of a deferred binding client property specified + * statically in host html. + */ +function __gwt_getMetaProperty(name) { + var value = __gwt_metaProps[name]; + if (value) { + return value; + } else { + return null; + } +} + +/** + * Determines whether or not a particular property value is allowed. + * @param wnd the caller's window object (not $wnd!) + * @param propName the name of the property being checked + * @param propValue the property value being tested + */ +function __gwt_isKnownPropertyValue(wnd, propName, propValue) { + return propValue in wnd["values$" + propName]; +} + +/** + * Called by the selection script when a property has a bad value or is missing. + * 'allowedValues' is an array of strings. Can be hooked in the host page using + * gwt:onPropertyErrorFn. + */ +function __gwt_onBadProperty(moduleName, propName, allowedValues, badValue) { + if (__gwt_onPropertyError) { + __gwt_onPropertyError(moduleName, propName, allowedValues, badValue); + return; + } else { + var msg = "While attempting to load module \"" + moduleName + "\", "; + if (badValue != null) { + msg += "property \"" + propName + "\" was set to the unexpected value \"" + + badValue + "\""; + } else { + msg += "property \"" + propName + "\" was not specified"; + } + + msg += "\n\nAllowed values: " + allowedValues; + + window.alert(msg); + } +} + +/** + * Called directly from compiled code. + */ +function __gwt_initHandlers(resize, beforeunload, unload) { + var oldOnResize = window.onresize; + window.onresize = function() { + resize(); + if (oldOnResize) + oldOnResize(); + }; + + var oldOnBeforeUnload = window.onbeforeunload; + window.onbeforeunload = function() { + var ret = beforeunload(); + + var oldRet; + if (oldOnBeforeUnload) + oldRet = oldOnBeforeUnload(); + + if (ret !== null) + return ret; + return oldRet; + }; + + var oldOnUnload = window.onunload; + window.onunload = function() { + unload(); + if (oldOnUnload) + oldOnUnload(); + }; +} + +////////////////////////////////////////////////////////////////////////////// +// Hosted Mode +// +function __gwt_onUnloadHostedMode() { + window.external.gwtOnLoad(null, null); + if (__gwt_onUnloadHostedMode.oldUnloadHandler) { + __gwt_onUnloadHostedMode.oldUnloadHandler(); + } +} + +////////////////////////////////////////////////////////////////////////////// +// Bootstrap +// + +/** + * Waits until all startup preconditions are satisfied, then launches the + * user-defined startup code for each module. + */ +function __gwt_latchAndLaunch() { + var ready = true; + + // Are there any compilations still pending? + if (ready && !__gwt_moduleControlBlocks.isReady()) { + // Yes, we're still waiting on one or more compilations. + ready = false; + } + + // Has the host html onload event fired? + if (ready && !__gwt_isHostPageLoaded) { + // No, the host html page hasn't fully loaded. + ready = false; + } + + // Are we ready to run user code? + if (ready) { + // Yes: run entry points. + __gwt_moduleControlBlocks.run(); + } else { + // No: try again soon. + window.setTimeout(__gwt_latchAndLaunch, __gwt_retryWaitMillis); + } +} + +/** + * Starts the module-loading sequence after meta tags have been processed and + * the body element exists. + */ +function __gwt_loadModules() { + // Make sure the body element exists before starting. + if (!document.body) { + // Try again soon. + window.setTimeout(__gwt_loadModules, __gwt_retryWaitMillis); + return; + } + + // Inject a frame for each module. + __gwt_moduleControlBlocks.injectFrames(); + + // Try to launch module entry points once everything is ready. + __gwt_latchAndLaunch(); +} + +/** + * The very first thing to run, and it runs exactly once unconditionally. + */ +function __gwt_bootstrap() { + // Hook onunload for hosted mode. + if (__gwt_isHosted()) { + __gwt_onUnloadHostedMode.oldUnloadHandler = window.onunload; + window.onunload = __gwt_onUnloadHostedMode; + } + + // Hook the current window onload handler. + var oldHandler = window.onload; + window.onload = function() { + __gwt_isHostPageLoaded = true; + if (oldHandler) { + oldHandler(); + } + }; + + // Parse meta tags from host html. + __gwt_processMetas(); + + // Load any modules. + __gwt_loadModules(); +} + +// Go. +__gwt_bootstrap(); diff --git a/webui/www/com.WebUI.WebUIApp/history.html b/webui/www/com.WebUI.WebUIApp/history.html new file mode 100644 index 000000000..409eaf2dc --- /dev/null +++ b/webui/www/com.WebUI.WebUIApp/history.html @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/webui/www/com.WebUI.WebUIApp/tree_closed.gif b/webui/www/com.WebUI.WebUIApp/tree_closed.gif new file mode 100644 index 000000000..1348f450c Binary files /dev/null and b/webui/www/com.WebUI.WebUIApp/tree_closed.gif differ diff --git a/webui/www/com.WebUI.WebUIApp/tree_open.gif b/webui/www/com.WebUI.WebUIApp/tree_open.gif new file mode 100644 index 000000000..c1ff81f40 Binary files /dev/null and b/webui/www/com.WebUI.WebUIApp/tree_open.gif differ diff --git a/webui/www/com.WebUI.WebUIApp/tree_white.gif b/webui/www/com.WebUI.WebUIApp/tree_white.gif new file mode 100644 index 000000000..ce4881fda Binary files /dev/null and b/webui/www/com.WebUI.WebUIApp/tree_white.gif differ diff --git a/webui/xubuntu-6.10-desktop-i386.iso.torrent b/webui/xubuntu-6.10-desktop-i386.iso.torrent new file mode 100644 index 000000000..3b4e9bf12 Binary files /dev/null and b/webui/xubuntu-6.10-desktop-i386.iso.torrent differ