/* * Protocoder * A prototyping platform for Android devices * * Victor Diaz Barrales victormdb@gmail.com * * Copyright (C) 2014 Victor Diaz * Copyright (C) 2013 Motorola Mobility LLC * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software * is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ package org.protocoder.network; import android.content.Context; import android.content.res.AssetManager; import org.apache.commons.lang3.StringEscapeUtils; import org.json.JSONArray; import org.json.JSONObject; import org.protocoderrunner.apidoc.APIManager; import org.protocoderrunner.apprunner.api.PApp; import org.protocoderrunner.apprunner.api.PBoards; import org.protocoderrunner.apprunner.api.PConsole; import org.protocoderrunner.apprunner.api.PDashboard; import org.protocoderrunner.apprunner.api.PDevice; import org.protocoderrunner.apprunner.api.PFileIO; import org.protocoderrunner.apprunner.api.PMedia; import org.protocoderrunner.apprunner.api.PNetwork; import org.protocoderrunner.apprunner.api.PProtocoder; import org.protocoderrunner.apprunner.api.PSensors; import org.protocoderrunner.apprunner.api.PUI; import org.protocoderrunner.apprunner.api.PUtil; import org.protocoderrunner.apprunner.api.boards.PArduino; import org.protocoderrunner.apprunner.api.boards.PIOIO; import org.protocoderrunner.apprunner.api.boards.PSerial; import org.protocoderrunner.apprunner.api.dashboard.PDashboardBackground; import org.protocoderrunner.apprunner.api.dashboard.PDashboardButton; import org.protocoderrunner.apprunner.api.dashboard.PDashboardCustomWidget; import org.protocoderrunner.apprunner.api.dashboard.PDashboardHTML; import org.protocoderrunner.apprunner.api.dashboard.PDashboardImage; import org.protocoderrunner.apprunner.api.dashboard.PDashboardInput; import org.protocoderrunner.apprunner.api.dashboard.PDashboardPlot; import org.protocoderrunner.apprunner.api.dashboard.PDashboardSlider; import org.protocoderrunner.apprunner.api.dashboard.PDashboardText; import org.protocoderrunner.apprunner.api.dashboard.PDashboardVideoCamera; import org.protocoderrunner.apprunner.api.other.PCamera; import org.protocoderrunner.apprunner.api.other.PDeviceEditor; import org.protocoderrunner.apprunner.api.other.PEvents; import org.protocoderrunner.apprunner.api.other.PLiveCodingFeedback; import org.protocoderrunner.apprunner.api.other.PMidi; import org.protocoderrunner.apprunner.api.other.PProcessing; import org.protocoderrunner.apprunner.api.other.PPureData; import org.protocoderrunner.apprunner.api.other.PSimpleHttpServer; import org.protocoderrunner.apprunner.api.other.PSocketIOClient; import org.protocoderrunner.apprunner.api.other.PSqLite; import org.protocoderrunner.apprunner.api.other.PVideo; import org.protocoderrunner.apprunner.api.other.PWebEditor; import org.protocoderrunner.apprunner.api.widgets.PAbsoluteLayout; import org.protocoderrunner.apprunner.api.widgets.PButton; import org.protocoderrunner.apprunner.api.widgets.PCanvasView; import org.protocoderrunner.apprunner.api.widgets.PCard; import org.protocoderrunner.apprunner.api.widgets.PCheckBox; import org.protocoderrunner.apprunner.api.widgets.PEditText; import org.protocoderrunner.apprunner.api.widgets.PGrid; import org.protocoderrunner.apprunner.api.widgets.PGridRow; import org.protocoderrunner.apprunner.api.widgets.PImageButton; import org.protocoderrunner.apprunner.api.widgets.PImageView; import org.protocoderrunner.apprunner.api.widgets.PList; import org.protocoderrunner.apprunner.api.widgets.PListItem; import org.protocoderrunner.apprunner.api.widgets.PMap; import org.protocoderrunner.apprunner.api.widgets.PNumberPicker; import org.protocoderrunner.apprunner.api.widgets.PPadView; import org.protocoderrunner.apprunner.api.widgets.PPlotView; import org.protocoderrunner.apprunner.api.widgets.PPopupCustomFragment; import org.protocoderrunner.apprunner.api.widgets.PProgressBar; import org.protocoderrunner.apprunner.api.widgets.PRadioButton; import org.protocoderrunner.apprunner.api.widgets.PRow; import org.protocoderrunner.apprunner.api.widgets.PScrollView; import org.protocoderrunner.apprunner.api.widgets.PSlider; import org.protocoderrunner.apprunner.api.widgets.PSpinner; import org.protocoderrunner.apprunner.api.widgets.PSwitch; import org.protocoderrunner.apprunner.api.widgets.PTextView; import org.protocoderrunner.apprunner.api.widgets.PToggleButton; import org.protocoderrunner.apprunner.api.widgets.PVerticalSeekbar; import org.protocoderrunner.apprunner.api.widgets.PWebView; import org.protocoderrunner.apprunner.api.widgets.PWindow; import org.protocoderrunner.events.Events; import org.protocoderrunner.events.Events.ProjectEvent; import org.protocoderrunner.network.NanoHTTPD; import org.protocoderrunner.network.NetworkUtils; import org.protocoderrunner.project.Project; import org.protocoderrunner.project.ProjectManager; import org.protocoderrunner.utils.FileIO; import org.protocoderrunner.utils.MLog; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties; import de.greenrobot.event.EventBus; /** * An example of subclassing NanoHTTPD to make a custom HTTP server. */ public class ProtocoderHttpServer extends NanoHTTPD { public static final String TAG = "myHTTPServer"; private final WeakReference<Context> ctx; private final String WEBAPP_DIR = "webapp/"; String projectURLPrefix = "/apps"; private static final Map<String, String> MIME_TYPES = new HashMap<String, String>() { { put("css", "text/css"); put("htm", "text/html"); put("html", "text/html"); put("xml", "text/xml"); put("txt", "text/plain"); put("asc", "text/plain"); put("gif", "image/gif"); put("jpg", "image/jpeg"); put("jpeg", "image/jpeg"); put("png", "image/png"); put("mp3", "audio/mpeg"); put("m3u", "audio/mpeg-url"); put("mp4", "video/mp4"); put("ogv", "video/ogg"); put("flv", "video/x-flv"); put("mov", "video/quicktime"); put("swf", "application/x-shockwave-flash"); put("js", "application/javascript"); put("pdf", "application/pdf"); put("doc", "application/msword"); put("ogg", "application/x-ogg"); put("zip", "application/octet-stream"); put("exe", "application/octet-stream"); put("class", "application/octet-stream"); } }; private static ProtocoderHttpServer instance; public static ProtocoderHttpServer getInstance(Context aCtx, int port) { MLog.d(TAG, "launching web server..."); if (instance == null) { try { MLog.d(TAG, "ok..."); instance = new ProtocoderHttpServer(aCtx, port); } catch (IOException e) { MLog.d(TAG, "nop :(..."); e.printStackTrace(); } } return instance; } public ProtocoderHttpServer(Context aCtx, int port) throws IOException { super(port); ctx = new WeakReference<Context>(aCtx); String ip = NetworkUtils.getLocalIpAddress(aCtx); if (ip == null) { MLog.d(TAG, "No IP found. Please connect to a newwork and try again"); } else { MLog.d(TAG, "Launched server at http://" + ip.toString() + ":" + port); } } @Override public Response serve(String uri, String method, Properties header, Properties parms, Properties files) { Response res = null; try { MLog.d(TAG, "received String" + uri + " " + method + " " + header + " " + " " + parms + " " + files); if (uri.startsWith(projectURLPrefix)) { // checking if we are inside the directory so we sandbox the app // TODO its pretty hack so this deserves coding it again Project p = ProjectManager.getInstance().getCurrentProject(); String projectFolder = "/" + p.getFolder() + "/" + p.getName(); // MLog.d("qq", "project folder is " + projectFolder); if (uri.replace(projectURLPrefix, "").contains(projectFolder)) { // MLog.d("qq", "inside project"); return serveFile(uri.substring(uri.lastIndexOf('/') + 1, uri.length()), header, new File(p.getStoragePath()), false); } else { // MLog.d("qq", "outside project"); new Response(HTTP_NOTFOUND, MIME_HTML, "resource not found"); } } // file upload if (!files.isEmpty()) { String name = parms.getProperty("name").toString(); String folder = parms.getProperty("fileType").toString(); Project p = ProjectManager.getInstance().get(folder, name); File src = new File(files.getProperty("pic").toString()); File dst = new File(p.getStoragePath() + "/" + parms.getProperty("pic").toString()); FileIO.copyFile(src, dst); JSONObject data = new JSONObject(); data.put("result", "OK"); return new Response("200", MIME_TYPES.get("txt"), data.toString()); } // webapi JSONObject data = new JSONObject(); // splitting the string into command and parameters String[] m = uri.split("="); String userCmd = m[0]; String params = ""; if (m.length > 1) { params = m[1]; } MLog.d(TAG, "cmd " + userCmd); if (userCmd.contains("cmd")) { JSONObject obj = new JSONObject(params); Project foundProject; String name; String url; String newCode; String folder; MLog.d(TAG, "params " + obj.toString(2)); String cmd = obj.getString("cmd"); // fetch code if (cmd.equals("fetch_code")) { MLog.d(TAG, "--> fetch code"); name = obj.getString("name"); folder = obj.getString("type"); Project p = ProjectManager.getInstance().get(folder, name); // TODO add type data.put("code", ProjectManager.getInstance().getCode(p)); // list apps } else if (cmd.equals("list_apps")) { MLog.d(TAG, "--> list apps"); folder = obj.getString("filter"); ArrayList<Project> projects = ProjectManager.getInstance().list(folder, false); JSONArray projectsArray = new JSONArray(); for (Project project : projects) { projectsArray.put(ProjectManager.getInstance().toJson(project)); } data.put("projects", projectsArray); // run app } else if (cmd.equals("run_app")) { MLog.d(TAG, "--> run app"); name = obj.getString("name"); folder = obj.getString("type"); Project p = ProjectManager.getInstance().get(folder, name); ProjectManager.getInstance().setRemoteIP(obj.getString("remoteIP")); ProjectEvent evt = new ProjectEvent(p, "run"); EventBus.getDefault().post(evt); MLog.i(TAG, "Running..."); // execute app } else if (cmd.equals("execute_code")) { MLog.d(TAG, "--> execute code"); // Save and run String code = parms.get("codeToSend").toString(); Events.ExecuteCodeEvent evt = new Events.ExecuteCodeEvent(code); EventBus.getDefault().post(evt); MLog.i(TAG, "Execute..."); // save_code } else if (cmd.equals("push_code")) { MLog.d(TAG, "--> push code " + method + " " + header); name = parms.get("name").toString(); String fileName = parms.get("fileName").toString(); newCode = parms.get("code").toString(); MLog.d(TAG, "fileName -> " + fileName); // MLog.d(TAG, "code -> " + newCode); folder = parms.get("type").toString(); // add type Project p = ProjectManager.getInstance().get(folder, name); ProjectManager.getInstance().writeNewCode(p, newCode, fileName); data.put("project", ProjectManager.getInstance().toJson(p)); ProjectEvent evt = new ProjectEvent(p, "save"); EventBus.getDefault().post(evt); MLog.i(TAG, "Saved"); // list files in project } else if (cmd.equals("list_files_in_project")) { MLog.d(TAG, "--> create new project"); name = obj.getString("name"); folder = obj.getString("type"); Project p = new Project(folder, name); JSONArray array = ProjectManager.getInstance().listFilesInProjectJSON(p); data.put("files", array); // ProjectEvent evt = new ProjectEvent(p, "new"); // EventBus.getDefault().post(evt); } else if (cmd.equals("create_new_project")) { MLog.d(TAG, "--> create new project"); name = obj.getString("name"); Project p = new Project(ProjectManager.FOLDER_USER_PROJECTS, name); ProjectEvent evt = new ProjectEvent(p, "new"); EventBus.getDefault().post(evt); Project newProject = ProjectManager.getInstance().addNewProject(ctx.get(), name, ProjectManager.FOLDER_USER_PROJECTS, name); // remove app } else if (cmd.equals("remove_app")) { MLog.d(TAG, "--> remove app"); name = obj.getString("name"); folder = obj.getString("type"); Project p = new Project(folder, name); ProjectManager.getInstance().deleteProject(p); ProjectEvent evt = new ProjectEvent(p, "update"); EventBus.getDefault().post(evt); // rename app } else if (cmd.equals("rename_app")) { MLog.d(TAG, "--> rename app"); // get help } else if (cmd.equals("get_documentation")) { MLog.d(TAG, "--> get documentation"); // TODO do it automatically //main objects APIManager.getInstance().clear(); APIManager.getInstance().addClass(PApp.class, true); APIManager.getInstance().addClass(PBoards.class, true); APIManager.getInstance().addClass(PConsole.class, true); APIManager.getInstance().addClass(PDashboard.class, true); APIManager.getInstance().addClass(PDevice.class, true); APIManager.getInstance().addClass(PFileIO.class, true); APIManager.getInstance().addClass(PMedia.class, true); APIManager.getInstance().addClass(PNetwork.class, true); APIManager.getInstance().addClass(PProtocoder.class, true); APIManager.getInstance().addClass(PSensors.class, true); APIManager.getInstance().addClass(PUI.class, true); APIManager.getInstance().addClass(PUtil.class, true); //boards APIManager.getInstance().addClass(PArduino.class, false); APIManager.getInstance().addClass(PSerial.class, false); APIManager.getInstance().addClass(PIOIO.class, false); //other APIManager.getInstance().addClass(PCamera.class, false); APIManager.getInstance().addClass(PDeviceEditor.class, false); APIManager.getInstance().addClass(PEvents.class, false); APIManager.getInstance().addClass(PMidi.class, false); APIManager.getInstance().addClass(PProcessing.class, false); APIManager.getInstance().addClass(PLiveCodingFeedback.class, false); APIManager.getInstance().addClass(PPureData.class, false); APIManager.getInstance().addClass(PSimpleHttpServer.class, false); APIManager.getInstance().addClass(PSocketIOClient.class, false); APIManager.getInstance().addClass(PSqLite.class, false); APIManager.getInstance().addClass(PVideo.class, false); APIManager.getInstance().addClass(PWebEditor.class, false); //widgets APIManager.getInstance().addClass(PAbsoluteLayout.class, false); APIManager.getInstance().addClass(PButton.class, false); APIManager.getInstance().addClass(PCanvasView.class, false); APIManager.getInstance().addClass(PCard.class, false); APIManager.getInstance().addClass(PCheckBox.class, false); APIManager.getInstance().addClass(PEditText.class, false); APIManager.getInstance().addClass(PGrid.class, false); APIManager.getInstance().addClass(PGridRow.class, false); APIManager.getInstance().addClass(PImageButton.class, false); APIManager.getInstance().addClass(PImageView.class, false); // TODO add plist item APIManager.getInstance().addClass(PList.class, false); APIManager.getInstance().addClass(PListItem.class, false); APIManager.getInstance().addClass(PMap.class, false); APIManager.getInstance().addClass(PNumberPicker.class, false); APIManager.getInstance().addClass(PPadView.class, false); APIManager.getInstance().addClass(PPlotView.class, false); APIManager.getInstance().addClass(PPopupCustomFragment.class, false); APIManager.getInstance().addClass(PProgressBar.class, false); APIManager.getInstance().addClass(PRadioButton.class, false); APIManager.getInstance().addClass(PRow.class, false); APIManager.getInstance().addClass(PScrollView.class, false); APIManager.getInstance().addClass(PSlider.class, false); APIManager.getInstance().addClass(PSpinner.class, false); APIManager.getInstance().addClass(PSwitch.class, false); APIManager.getInstance().addClass(PTextView.class, false); APIManager.getInstance().addClass(PToggleButton.class, false); APIManager.getInstance().addClass(PVerticalSeekbar.class, false); APIManager.getInstance().addClass(PWebView.class, false); APIManager.getInstance().addClass(PWindow.class, false); //dashboard APIManager.getInstance().addClass(PDashboardBackground.class, false); APIManager.getInstance().addClass(PDashboardButton.class, false); APIManager.getInstance().addClass(PDashboardCustomWidget.class, false); APIManager.getInstance().addClass(PDashboardHTML.class, false); APIManager.getInstance().addClass(PDashboardImage.class, false); APIManager.getInstance().addClass(PDashboardInput.class, false); APIManager.getInstance().addClass(PDashboardPlot.class, false); APIManager.getInstance().addClass(PDashboardSlider.class, false); APIManager.getInstance().addClass(PDashboardText.class, false); APIManager.getInstance().addClass(PDashboardVideoCamera.class, false); data.put("api", APIManager.getInstance().getDocumentation()); } res = new Response("200", MIME_TYPES.get("txt"), data.toString()); } else if (uri.contains("apps")) { String[] u = uri.split("/"); // server webui } else { res = sendWebAppFile(uri, method, header, parms, files); } } catch (Exception e) { MLog.d(TAG, "response error " + e.toString()); } // return serveFile(uri, header, servingFolder, true); return res; } private Response sendProjectFile(String uri, String method, Properties header, Properties parms, Properties files) { Response res = null; // Clean up uri uri = uri.trim().replace(File.separatorChar, '/'); MLog.d(TAG, uri); // have the object build the directory structure, if needed. AssetManager am = ctx.get().getAssets(); try { MLog.d(TAG, WEBAPP_DIR + uri); InputStream fi = am.open(WEBAPP_DIR + uri); // Get MIME type from file name extension, if possible String mime = null; int dot = uri.lastIndexOf('.'); if (dot >= 0) { mime = MIME_TYPES.get(uri.substring(dot + 1).toLowerCase()); } if (mime == null) { mime = NanoHTTPD.MIME_DEFAULT_BINARY; } res = new Response(HTTP_OK, mime, fi); } catch (IOException e) { e.printStackTrace(); MLog.d(TAG, e.getStackTrace().toString()); res = new Response(HTTP_INTERNALERROR, "text/html", "ERROR: " + e.getMessage()); } return res; } private Response sendWebAppFile(String uri, String method, Properties header, Properties parms, Properties files) { Response res = null; MLog.d(TAG, "" + method + " '" + uri + " " + /* header + */" " + parms); String escapedCode = parms.getProperty("code"); String unescapedCode = StringEscapeUtils.unescapeEcmaScript(escapedCode); MLog.d("HTTP Code", "" + escapedCode); MLog.d("HTTP Code", "" + unescapedCode); // Clean up uri uri = uri.trim().replace(File.separatorChar, '/'); if (uri.indexOf('?') >= 0) { uri = uri.substring(0, uri.indexOf('?')); } // We never want to request just the '/' if (uri.length() == 1) { uri = "index.html"; } // We're using assets, so we can't have a leading '/' if (uri.charAt(0) == '/') { uri = uri.substring(1, uri.length()); } // have the object build the directory structure, if needed. AssetManager am = ctx.get().getAssets(); try { MLog.d(TAG, WEBAPP_DIR + uri); InputStream fi = am.open(WEBAPP_DIR + uri); // Get MIME type from file name extension, if possible String mime = null; int dot = uri.lastIndexOf('.'); if (dot >= 0) { mime = MIME_TYPES.get(uri.substring(dot + 1).toLowerCase()); } if (mime == null) { mime = NanoHTTPD.MIME_DEFAULT_BINARY; } res = new Response(HTTP_OK, mime, fi); } catch (IOException e) { e.printStackTrace(); MLog.d(TAG, e.getStackTrace().toString()); res = new Response(HTTP_INTERNALERROR, "text/html", "ERROR: " + e.getMessage()); } return res; } public void close() { stop(); instance = null; } }