ui
[fanfix.git] / src / be / nikiroo / fanfix / library / WebLibraryServer.java
index e0096fcc590b94963137d1476d9f57caf7398124..7c5cf425bd0dd21394c9e4e881c33c5657530ae2 100644 (file)
@@ -93,8 +93,8 @@ public class WebLibraryServer implements Runnable {
                                                subkeys.add("");
 
                                                for (String subkey : subkeys) {
-                                                       if (CookieUtils.validateCookie(
-                                                                       wookie + subkey + opts, rehashed)) {
+                                                       if (CookieUtils.validateCookie(wookie + subkey
+                                                                       + opts, rehashed)) {
                                                                this.wookie = wookie;
                                                                this.token = token;
                                                                this.success = true;
@@ -148,8 +148,7 @@ public class WebLibraryServer implements Runnable {
                Integer port = Instance.getInstance().getConfig()
                                .getInteger(Config.SERVER_PORT);
                if (port == null) {
-                       throw new IOException(
-                                       "Cannot start web server: port not specified");
+                       throw new IOException("Cannot start web server: port not specified");
                }
 
                int cacheMb = Instance.getInstance().getConfig()
@@ -173,8 +172,8 @@ public class WebLibraryServer implements Runnable {
                        if (!keystorePath.isEmpty()) {
                                File keystoreFile = new File(keystorePath);
                                try {
-                                       KeyStore keystore = KeyStore
-                                                       .getInstance(KeyStore.getDefaultType());
+                                       KeyStore keystore = KeyStore.getInstance(KeyStore
+                                                       .getDefaultType());
                                        InputStream keystoreStream = new FileInputStream(
                                                        keystoreFile);
                                        try {
@@ -226,14 +225,19 @@ public class WebLibraryServer implements Runnable {
                                                        params.get("login"), whitelist);
                                } else {
                                        String token = cookies.get("token");
-                                       login = login(who, token, Instance.getInstance().getConfig()
-                                                       .getList(Config.SERVER_ALLOWED_SUBKEYS));
+                                       login = login(who, token, Instance.getInstance()
+                                                       .getConfig().getList(Config.SERVER_ALLOWED_SUBKEYS));
                                }
 
                                if (login.isSuccess()) {
+                                       if (!login.isWl()) {
+                                               whitelist.clear();
+                                       }
+
                                        // refresh token
-                                       session.getCookies().set(new Cookie("token",
-                                                       login.getToken(), "30; path=/"));
+                                       session.getCookies()
+                                                       .set(new Cookie("token", login.getToken(),
+                                                                       "30; path=/"));
 
                                        // set options
                                        String optionName = params.get("optionName");
@@ -243,8 +247,9 @@ public class WebLibraryServer implements Runnable {
                                                        session.getCookies().delete(optionName);
                                                        cookies.remove(optionName);
                                                } else {
-                                                       session.getCookies().set(new Cookie(optionName,
-                                                                       optionValue, "; path=/"));
+                                                       session.getCookies().set(
+                                                                       new Cookie(optionName, optionValue,
+                                                                                       "; path=/"));
                                                        cookies.put(optionName, optionValue);
                                                }
                                        }
@@ -254,7 +259,7 @@ public class WebLibraryServer implements Runnable {
                                if (!login.isSuccess() && (uri.equals("/") //
                                                || uri.startsWith(STORY_URL_BASE) //
                                                || uri.startsWith(VIEWER_URL_BASE) //
-                                               || uri.startsWith(LIST_URL))) {
+                               || uri.startsWith(LIST_URL))) {
                                        rep = loginPage(login, uri);
                                }
 
@@ -298,9 +303,10 @@ public class WebLibraryServer implements Runnable {
                                                                        NanoHTTPD.MIME_PLAINTEXT, "Not Found");
                                                }
                                        } catch (Exception e) {
-                                               Instance.getInstance().getTraceHandler().error(
-                                                               new IOException("Cannot process web request",
-                                                                               e));
+                                               Instance.getInstance()
+                                                               .getTraceHandler()
+                                                               .error(new IOException(
+                                                                               "Cannot process web request", e));
                                                rep = newFixedLengthResponse(Status.INTERNAL_ERROR,
                                                                NanoHTTPD.MIME_PLAINTEXT, "An error occured");
                                        }
@@ -409,8 +415,8 @@ public class WebLibraryServer implements Runnable {
                        wl = false;
                }
 
-               rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW,
-                               rw);
+               rw = Instance.getInstance().getConfig()
+                               .getBoolean(Config.SERVER_RW, rw);
                if (!subkey.isEmpty()) {
                        List<String> allowed = Instance.getInstance().getConfig()
                                        .getList(Config.SERVER_ALLOWED_SUBKEYS);
@@ -444,10 +450,9 @@ public class WebLibraryServer implements Runnable {
                        uri = "/";
                }
 
-               builder.append(
-                               "<form method='POST' action='" + uri + "' class='login'>\n");
-               builder.append(
-                               "<p>You must be logged into the system to see the stories.</p>");
+               builder.append("<form method='POST' action='" + uri
+                               + "' class='login'>\n");
+               builder.append("<p>You must be logged into the system to see the stories.</p>");
                builder.append("\t<input type='text' name='login' />\n");
                builder.append("\t<input type='password' name='password' />\n");
                builder.append("\t<input type='submit' value='Login' />\n");
@@ -470,8 +475,8 @@ public class WebLibraryServer implements Runnable {
                        }
 
                        return newInputStreamResponse("application/json",
-                                       new ByteArrayInputStream(
-                                                       new JSONArray(jsons).toString().getBytes()));
+                                       new ByteArrayInputStream(new JSONArray(jsons).toString()
+                                                       .getBytes()));
                }
 
                return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
@@ -485,20 +490,24 @@ public class WebLibraryServer implements Runnable {
                result = new MetaResultList(result.filter(whitelist, null, null));
                StringBuilder builder = new StringBuilder();
 
+               
                appendPreHtml(builder, true);
 
+               Map<String, String> params = session.getParms();
+               
                String filter = cookies.get("filter");
+               if (params.get("clear") != null)
+                       filter = null;
                if (filter == null) {
                        filter = "";
                }
 
-               Map<String, String> params = session.getParms();
-               String browser = params.get("browser") == null ? ""
-                               : params.get("browser");
-               String browser2 = params.get("browser2") == null ? ""
-                               : params.get("browser2");
-               String browser3 = params.get("browser3") == null ? ""
-                               : params.get("browser3");
+               String browser = params.get("browser") == null ? "" : params
+                               .get("browser");
+               String browser2 = params.get("browser2") == null ? "" : params
+                               .get("browser2");
+               String browser3 = params.get("browser3") == null ? "" : params
+                               .get("browser3");
 
                String filterSource = null;
                String filterAuthor = null;
@@ -582,31 +591,29 @@ public class WebLibraryServer implements Runnable {
                // TODO: javascript in realtime, using visible=false + hide [submit]
                builder.append("<div class='filter'>\n");
                builder.append("\tFilter: \n");
-               builder.append(
-                               "\t<input name='optionName'  type='hidden' value='filter' />\n");
+               builder.append("\t<input name='optionName'  type='hidden' value='filter' />\n");
                builder.append("\t<input name='optionValue' type='text'   value='"
                                + filter + "' place-holder='...' />\n");
-               builder.append(
-                               "\t<input name='submit' type='submit' value='Filter' />\n");
+               builder.append("\t<input name='clear'  type='submit' value='x' />");
+               builder.append("\t<input name='submit' type='submit' value='Filter' />\n");
                builder.append("</div>\n");
                builder.append("</form>\n");
 
                builder.append("\t<div class='books'>");
                for (MetaData meta : result.getMetas()) {
-                       if (!filter.isEmpty() && !meta.getTitle().toLowerCase()
-                                       .contains(filter.toLowerCase())) {
+                       if (!filter.isEmpty()
+                                       && !meta.getTitle().toLowerCase()
+                                                       .contains(filter.toLowerCase())) {
                                continue;
                        }
 
                        // TODO Sub sources
-                       if (filterSource != null
-                                       && !filterSource.equals(meta.getSource())) {
+                       if (filterSource != null && !filterSource.equals(meta.getSource())) {
                                continue;
                        }
 
                        // TODO: sub authors
-                       if (filterAuthor != null
-                                       && !filterAuthor.equals(meta.getAuthor())) {
+                       if (filterAuthor != null && !filterAuthor.equals(meta.getAuthor())) {
                                continue;
                        }
 
@@ -622,12 +629,10 @@ public class WebLibraryServer implements Runnable {
 
                        if (lib.isCached(meta.getLuid())) {
                                // ◉ = &#9673;
-                               builder.append(
-                                               "<span class='cache_icon cached'>&#9673;</span>");
+                               builder.append("<span class='cache_icon cached'>&#9673;</span>");
                        } else {
                                // ○ = &#9675;
-                               builder.append(
-                                               "<span class='cache_icon uncached'>&#9675;</span>");
+                               builder.append("<span class='cache_icon uncached'>&#9675;</span>");
                        }
                        builder.append("<span class='luid'>");
                        builder.append(meta.getLuid());
@@ -651,6 +656,7 @@ public class WebLibraryServer implements Runnable {
        // /story/luid/chapter/para <-- text/image
        // /story/luid/cover <-- image
        // /story/luid/metadata <-- json
+       // /story/luid/json <-- json, whole chapter (no images)
        private Response getStoryPart(String uri, List<String> whitelist) {
                String[] cover = uri.split("/");
                int off = 2;
@@ -667,7 +673,7 @@ public class WebLibraryServer implements Runnable {
                // 1-based (0 = desc)
                int chapter = 0;
                if (chapterStr != null && !"cover".equals(chapterStr)
-                               && !"metadata".equals(chapterStr)) {
+                               && !"metadata".equals(chapterStr) && !"json".equals(chapterStr)) {
                        try {
                                chapter = Integer.parseInt(chapterStr);
                                if (chapter < 0) {
@@ -706,6 +712,11 @@ public class WebLibraryServer implements Runnable {
                                JSONObject json = JsonIO.toJson(meta);
                                mimeType = "application/json";
                                in = new ByteArrayInputStream(json.toString().getBytes());
+                       } else if ("json".equals(chapterStr)) {
+                               Story story = story(luid, whitelist);
+                               JSONObject json = JsonIO.toJson(story);
+                               mimeType = "application/json";
+                               in = new ByteArrayInputStream(json.toString().getBytes());
                        } else {
                                Story story = story(luid, whitelist);
                                if (story != null) {
@@ -718,8 +729,8 @@ public class WebLibraryServer implements Runnable {
                                                        builder.append(p.getContent());
                                                }
 
-                                               in = new ByteArrayInputStream(
-                                                               builder.toString().getBytes("utf-8"));
+                                               in = new ByteArrayInputStream(builder.toString()
+                                                               .getBytes("utf-8"));
                                        } else {
                                                Paragraph para = story.getChapters().get(chapter - 1)
                                                                .getParagraphs().get(paragraph - 1);
@@ -729,8 +740,8 @@ public class WebLibraryServer implements Runnable {
                                                        mimeType = "image/png";
                                                        in = img.newInputStream();
                                                } else {
-                                                       in = new ByteArrayInputStream(
-                                                                       para.getContent().getBytes("utf-8"));
+                                                       in = new ByteArrayInputStream(para.getContent()
+                                                                       .getBytes("utf-8"));
                                                }
                                        }
                                }
@@ -802,121 +813,161 @@ public class WebLibraryServer implements Runnable {
                        StringBuilder builder = new StringBuilder();
                        appendPreHtml(builder, false);
 
-                       if (chapter < 0) {
-                               builder.append(story);
-                       } else {
-                               if (chapter == 0) {
-                                       // TODO: description
+                       // TODO: no desc page for images?
+                       if (story.getMeta().isImageDocument()) {
+                               if (chapter <= 0)
                                        chapter = 1;
-                               }
+                               if (paragraph <= 0)
+                                       paragraph = 1;
+                       }
 
-                               Chapter chap = null;
+                       Chapter chap = null;
+                       if (chapter <= 0) {
+                               chap = story.getMeta().getResume();
+                       } else {
                                try {
                                        chap = story.getChapters().get(chapter - 1);
                                } catch (IndexOutOfBoundsException e) {
                                        return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
                                                        NanoHTTPD.MIME_PLAINTEXT, "Chapter not found");
                                }
+                       }
 
-                               if (story.getMeta().isImageDocument() && paragraph <= 0) {
-                                       paragraph = 1;
+                       String first, previous, next, last;
+
+                       StringBuilder content = new StringBuilder();
+
+                       String disabledLeft = "";
+                       String disabledRight = "";
+                       String disabledZoomReal = "";
+                       String disabledZoomWidth = "";
+                       String disabledZoomHeight = "";
+
+                       if (paragraph <= 0) {
+                               first = getViewUrl(luid, 1, null);
+                               previous = getViewUrl(luid, (Math.max(chapter - 1, 1)), null);
+                               next = getViewUrl(luid,
+                                               (Math.min(chapter + 1, story.getChapters().size())),
+                                               null);
+                               last = getViewUrl(luid, story.getChapters().size(), null);
+
+                               // TODO
+                               StringBuilder desc = new StringBuilder();
+
+                               if (chapter <= 0) {
+                                       desc.append("<div class='desc'>\n");
+                                       desc.append("\t<div class='cover'>\n");
+                                       desc.append("\t\t<img src='/story/" + luid + "/cover'/>\n");
+                                       desc.append("\t</div>\n");
+                                       desc.append("\t<table>\n");
+                                       desc.append("\t\t<tr><th>HEAD 1</th><th>HEAD 2</th></tr>\n");
+                                       desc.append("\t\t<tr><td>KEY 1</td><td>VAL 1</td></tr>\n");
+                                       desc.append("\t\t<tr><td>KEY 2</td><td>VAL 2</td></tr>\n");
+                                       desc.append("\t</table>\n");
+                                       desc.append("</div>\n");
+                                       desc.append("<h1 class='title'>Description</h1>\n");
                                }
 
-                               String first, previous, next, last;
-                               String content;
+                               content.append("<div class='viewer text'>\n");
+                               content.append(desc);
+                               content.append(new TextOutput(false).convert(chap, chapter > 0));
+                               content.append("</div>\n");
 
-                               if (paragraph <= 0) {
-                                       first = getViewUrl(luid, 1, null);
-                                       previous = getViewUrl(luid, (Math.max(chapter - 1, 1)),
-                                                       null);
-                                       next = getViewUrl(luid,
-                                                       (Math.min(chapter + 1, story.getChapters().size())),
-                                                       null);
-                                       last = getViewUrl(luid, story.getChapters().size(), null);
-
-                                       content = "<div class='viewer text'>\n"
-                                                       + new TextOutput(false).convert(chap, true)
-                                                       + "</div>\n";
-                               } else {
-                                       first = getViewUrl(luid, chapter, 1);
-                                       previous = getViewUrl(luid, chapter,
-                                                       (Math.max(paragraph - 1, 1)));
-                                       next = getViewUrl(luid, chapter, (Math.min(paragraph + 1,
-                                                       chap.getParagraphs().size())));
-                                       last = getViewUrl(luid, chapter,
-                                                       chap.getParagraphs().size());
-
-                                       Paragraph para = null;
-                                       try {
-                                               para = chap.getParagraphs().get(paragraph - 1);
-                                       } catch (IndexOutOfBoundsException e) {
-                                               return NanoHTTPD.newFixedLengthResponse(
-                                                               Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,
-                                                               "Paragraph not found");
-                                       }
+                               if (chapter <= 1)
+                                       disabledLeft = " disabled='disbaled'";
+                               if (chapter >= story.getChapters().size())
+                                       disabledRight = " disabled='disbaled'";
+                       } else {
+                               first = getViewUrl(luid, chapter, 1);
+                               previous = getViewUrl(luid, chapter,
+                                               (Math.max(paragraph - 1, 1)));
+                               next = getViewUrl(luid, chapter,
+                                               (Math.min(paragraph + 1, chap.getParagraphs().size())));
+                               last = getViewUrl(luid, chapter, chap.getParagraphs().size());
+
+                               if (paragraph <= 1)
+                                       disabledLeft = " disabled='disbaled'";
+                               if (paragraph >= chap.getParagraphs().size())
+                                       disabledRight = " disabled='disbaled'";
+
+                               Paragraph para = null;
+                               try {
+                                       para = chap.getParagraphs().get(paragraph - 1);
+                               } catch (IndexOutOfBoundsException e) {
+                                       return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
+                                                       NanoHTTPD.MIME_PLAINTEXT, "Paragraph " + paragraph
+                                                                       + " not found");
+                               }
 
-                                       if (para.getType() == ParagraphType.IMAGE) {
-                                               String zoomStyle = "max-width: 100%;";
-                                               String zoomOption = cookies.get("zoom");
-                                               if (zoomOption != null && !zoomOption.isEmpty()) {
-                                                       if (zoomOption.equals("real")) {
-                                                               zoomStyle = "";
-                                                       } else if (zoomOption.equals("width")) {
-                                                               zoomStyle = "max-width: 100%;";
-                                                       } else if (zoomOption.equals("height")) {
-                                                               // see height of navbar + optionbar
-                                                               zoomStyle = "max-height: calc(100% - 128px);";
-                                                       }
+                               if (para.getType() == ParagraphType.IMAGE) {
+                                       String zoomStyle = "max-width: 100%;";
+                                       disabledZoomWidth = " disabled='disabled'";
+                                       String zoomOption = cookies.get("zoom");
+                                       if (zoomOption != null && !zoomOption.isEmpty()) {
+                                               if (zoomOption.equals("real")) {
+                                                       zoomStyle = "";
+                                                       disabledZoomWidth = "";
+                                                       disabledZoomReal = " disabled='disabled'";
+                                               } else if (zoomOption.equals("width")) {
+                                                       zoomStyle = "max-width: 100%;";
+                                               } else if (zoomOption.equals("height")) {
+                                                       // see height of navbar + optionbar
+                                                       zoomStyle = "max-height: calc(100% - 128px);";
+                                                       disabledZoomWidth = "";
+                                                       disabledZoomHeight = " disabled='disabled'";
                                                }
-                                               content = String.format("" //
-                                                               + "<a class='viewer link' href='%s'>" //
-                                                               + "<img class='viewer img' style='%s' src='%s'/>"
-                                                               + "</a>", //
-                                                               next, //
-                                                               zoomStyle, //
-                                                               getStoryUrl(luid, chapter, paragraph));
-                                       } else {
-                                               content = para.getContent();
                                        }
 
-                               }
-
-                               builder.append(String.format("" //
-                                               + "<div class='bar navbar'>\n" //
-                                               + "\t<a class='button first' href='%s'>&lt;&lt;</a>\n"//
-                                               + "\t<a class='button previous' href='%s'>&lt;</a>\n"//
-                                               + "\t<a class='button next' href='%s'>&gt;</a>\n"//
-                                               + "\t<a class='button last' href='%s'>&gt;&gt;</a>\n"//
-                                               + "</div>\n" //
-                                               + "%s", //
-                                               first, //
-                                               previous, //
-                                               next, //
-                                               last, //
-                                               content //
-                               ));
-
-                               builder.append("<div class='bar optionbar ");
-                               if (paragraph > 0) {
-                                       builder.append("s4");
+                                       content.append(String.format("" //
+                                                       + "<a class='viewer link' href='%s'>"
+                                                       + "<img class='viewer img' style='%s' src='%s'/>"
+                                                       + "</a>", //
+                                                       next, //
+                                                       zoomStyle, //
+                                                       getStoryUrl(luid, chapter, paragraph)));
                                } else {
-                                       builder.append("s1");
+                                       content.append(para.getContent());
                                }
-                               builder.append("'>\n");
-                               builder.append(
-                                               "       <a class='button back' href='/'>BACK</a>\n");
-
-                               if (paragraph > 0) {
-                                       builder.append(String.format("" //
-                                                       + "\t<a class='button zoomreal'   href='%s'>REAL</a>\n"//
-                                                       + "\t<a class='button zoomwidth'  href='%s'>WIDTH</a>\n"//
-                                                       + "\t<a class='button zoomheight' href='%s'>HEIGHT</a>\n"//
-                                                       + "</div>\n", //
-                                                       uri + "?optionName=zoom&optionValue=real", //
-                                                       uri + "?optionName=zoom&optionValue=width", //
-                                                       uri + "?optionName=zoom&optionValue=height" //
+                       }
+
+                       builder.append(String.format("" //
+                                       + "<div class='bar navbar'>\n" //
+                                       + "\t<a%s class='button first' href='%s'>&lt;&lt;</a>\n"//
+                                       + "\t<a%s class='button previous' href='%s'>&lt;</a>\n"//
+                                       + "\t<a%s class='button next' href='%s'>&gt;</a>\n"//
+                                       + "\t<a%s class='button last' href='%s'>&gt;&gt;</a>\n"//
+                                       + "</div>\n", //
+                                       disabledLeft, first, //
+                                       disabledLeft, previous, //
+                                       disabledRight, next, //
+                                       disabledRight, last //
                                        ));
-                               }
+
+                       builder.append(content);
+
+                       builder.append("<div class='bar optionbar ");
+                       if (paragraph > 0) {
+                               builder.append("s4");
+                       } else {
+                               builder.append("s1");
+                       }
+                       builder.append("'>\n");
+                       builder.append("        <a class='button back' href='/'>BACK</a>\n");
+
+                       if (paragraph > 0) {
+                               builder.append(String
+                                               .format("" //
+                                                               + "\t<a%s class='button zoomreal'   href='%s'>REAL</a>\n"//
+                                                               + "\t<a%s class='button zoomwidth'  href='%s'>WIDTH</a>\n"//
+                                                               + "\t<a%s class='button zoomheight' href='%s'>HEIGHT</a>\n"//
+                                                               + "</div>\n", //
+                                                               disabledZoomReal, uri
+                                                                               + "?optionName=zoom&optionValue=real", //
+                                                               disabledZoomWidth, uri
+                                                                               + "?optionName=zoom&optionValue=width", //
+                                                               disabledZoomHeight, uri
+                                                                               + "?optionName=zoom&optionValue=height" //
+                                               ));
                        }
 
                        appendPostHtml(builder);
@@ -932,21 +983,23 @@ public class WebLibraryServer implements Runnable {
 
        private Response newInputStreamResponse(String mimeType, InputStream in) {
                if (in == null) {
-                       return NanoHTTPD.newFixedLengthResponse(Status.NO_CONTENT, "",
-                                       null);
+                       return NanoHTTPD
+                                       .newFixedLengthResponse(Status.NO_CONTENT, "", null);
                }
                return NanoHTTPD.newChunkedResponse(Status.OK, mimeType, in);
        }
 
        private String getContentOf(String file) {
-               InputStream in = IOUtils.openResource(WebLibraryServerIndex.class,
-                               file);
+               InputStream in = IOUtils
+                               .openResource(WebLibraryServerIndex.class, file);
                if (in != null) {
                        try {
                                return IOUtils.readSmallStream(in);
                        } catch (IOException e) {
-                               Instance.getInstance().getTraceHandler().error(
-                                               new IOException("Cannot get file: index.pre.html", e));
+                               Instance.getInstance()
+                                               .getTraceHandler()
+                                               .error(new IOException(
+                                                               "Cannot get file: index.pre.html", e));
                        }
                }
 
@@ -988,15 +1041,15 @@ public class WebLibraryServer implements Runnable {
                        throws IOException {
                MetaData meta = meta(luid, whitelist);
                if (meta != null) {
-                       return meta.getCover();
+                       BasicLibrary lib = Instance.getInstance().getLibrary();
+                       return lib.getCover(meta.getLuid());
                }
 
                return null;
        }
 
        // NULL if not whitelist OK or if not found
-       private Story story(String luid, List<String> whitelist)
-                       throws IOException {
+       private Story story(String luid, List<String> whitelist) throws IOException {
                synchronized (storyCache) {
                        if (storyCache.containsKey(luid)) {
                                Story story = storyCache.get(luid);
@@ -1058,12 +1111,12 @@ public class WebLibraryServer implements Runnable {
                        favicon = "icon_" + icon.replace("-", "_") + ".png";
                }
 
-               builder.append(
-                               getContentOf("index.pre.html").replace("favicon.ico", favicon));
+               builder.append(getContentOf("index.pre.html").replace("favicon.ico",
+                               favicon));
 
                if (banner) {
                        builder.append("<div class='banner'>\n");
-                       builder.append("\t<img class='ico' src='") //
+                       builder.append("\t<img class='ico' src='/") //
                                        .append(favicon) //
                                        .append("'/>\n");
                        builder.append("\t<h1>Fanfix</h1>\n");
@@ -1089,4 +1142,10 @@ public class WebLibraryServer implements Runnable {
                }
                builder.append(">").append(name).append("</option>\n");
        }
+
+       public static void main(String[] args) throws IOException {
+               Instance.init();
+               WebLibraryServer web = new WebLibraryServer(false);
+               web.run();
+       }
 }