problems = new ArrayList<>();
567 |
568 | //for (JSONObject obj : result.)
569 | for (int i = 0; i < result.size(); i++) {
570 | JSONObject obj = result.getJSONObject(i);
571 |
572 | String errorCode = obj.getString("code", null);
573 | final boolean error = errorCode != null && errorCode.startsWith("E");
574 |
575 | final String message = obj.getString("reason");
576 |
577 | final int line = obj.getInt("line", 1) - 1;
578 | final int start = getLineStartOffset(line);
579 | // use the evidence or the entire line
580 | String evidence = obj.getString("evidence", getLineText(line));
581 | final int stop = start + evidence.length();
582 |
583 | problems.add(new Problem() {
584 |
585 | @Override
586 | public boolean isWarning() {
587 | return !error;
588 | }
589 |
590 | @Override
591 | public boolean isError() {
592 | return error;
593 | }
594 |
595 | @Override
596 | public int getTabIndex() {
597 | return tabIndex;
598 | }
599 |
600 | @Override
601 | public int getStartOffset() {
602 | return start;
603 | }
604 |
605 | @Override
606 | public int getStopOffset() {
607 | return stop;
608 | }
609 |
610 | @Override
611 | public String getMessage() {
612 | return message;
613 | }
614 |
615 | @Override
616 | public int getLineNumber() {
617 | return line;
618 | }
619 | });
620 | }
621 | setProblemList(problems);
622 | }
623 |
624 |
625 | static final int DELAY_BEFORE_UPDATE = 650;
626 | long nextUpdate;
627 |
628 |
629 | @Override
630 | public void sketchChanged() {
631 | nextUpdate = System.currentTimeMillis() + DELAY_BEFORE_UPDATE;
632 | }
633 |
634 |
635 | final DocumentListener sketchChangedListener = new DocumentListener() {
636 | @Override
637 | public void insertUpdate(DocumentEvent e) {
638 | sketchChanged();
639 | }
640 |
641 | @Override
642 | public void removeUpdate(DocumentEvent e) {
643 | sketchChanged();
644 | }
645 |
646 | @Override
647 | public void changedUpdate(DocumentEvent e) {
648 | sketchChanged();
649 | }
650 | };
651 |
652 |
653 | private boolean hasListener(Document doc) {
654 | for (DocumentListener dl : ((AbstractDocument) doc).getDocumentListeners()) {
655 | if (dl == sketchChangedListener) {
656 | return true;
657 | }
658 | }
659 | return false;
660 | }
661 |
662 |
663 | private void checkDocumentListener(SketchCode sketchCode) {
664 | //if (sketchCode.isExtension("js") || sketchCode.isExtension("json")) {
665 | if (sketchCode.isExtension("js")) {
666 | Document doc = sketchCode.getDocument();
667 | if (doc != null) {
668 | if (!hasListener(doc)) {
669 | doc.addDocumentListener(sketchChangedListener);
670 | }
671 | }
672 | }
673 | }
674 |
675 |
676 | /**
677 | * Event handler called when switching between tabs.
678 | * @param code tab to switch to
679 | */
680 | @Override
681 | public void setCode(SketchCode code) {
682 | super.setCode(code);
683 | checkDocumentListener(code);
684 | }
685 |
686 |
687 | @Override
688 | public void dispose() {
689 | // set the error thread to null when closing
690 | stopWatcher();
691 | }
692 |
693 |
694 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
695 |
696 |
697 | protected boolean rebuildHtml() {
698 | try {
699 | p5jsBuild.updateHtml(sketch);
700 | return true;
701 | } catch (Exception e) {
702 | statusError(e);
703 | }
704 | return false;
705 | }
706 |
707 |
708 | @Override
709 | public boolean handleSaveAs() {
710 | if (super.handleSaveAs()) {
711 | EventQueue.invokeLater(() -> {
712 | while (sketch.isSaving()) { // wait until Save As completes
713 | try {
714 | Thread.sleep(5);
715 | } catch (InterruptedException ignored) { }
716 | }
717 | // This will rebuild the index.html code in the Editor
718 | rebuildHtml();
719 | // …but we still need to manually save index.html with the change.
720 | SketchCode indexHtmlCode = p5jsMode.findIndexHtml(sketch);
721 | if (indexHtmlCode != null) {
722 | try {
723 | indexHtmlCode.save();
724 | } catch (IOException e) {
725 | e.printStackTrace();
726 | }
727 | }
728 | });
729 | return true; // kind of a farce
730 | }
731 | return false;
732 | }
733 |
734 |
735 | /**
736 | * Start the internal server for this sketch.
737 | */
738 | protected void startServer() {
739 | // System.out.println("restarting server? " + server + " " + (server != null && server.isDead()));
740 | if (server != null && server.isDead()) {
741 | // if server hung or something else went wrong... stop it.
742 | server.stop();
743 | server = null;
744 | }
745 |
746 | if (port == 0) {
747 | resetPort();
748 | }
749 | try {
750 | server = new HttpServer(this, port);
751 |
752 | } catch (BindException be) {
753 | // If the port is in use, try another. Only do this once,
754 | // because it may be due to a firewall or other circumstances.
755 | resetPort();
756 | try {
757 | server = new HttpServer(this, port);
758 | } catch (IOException ioe) {
759 | statusError(ioe); // error out here if still trouble
760 | }
761 | } catch (IOException e) { // other unknown type of exception
762 | statusError(e);
763 | }
764 |
765 | if (server != null) { // actually kick off the listening threads
766 | server.start();
767 | }
768 | }
769 |
770 |
771 | void resetPort() {
772 | port = (int) (8000 + Math.random() * 1000);
773 | }
774 |
775 |
776 | // method is still here, though we're never gonna stop the server
777 | protected void stopServer() {
778 | if (server != null) {
779 | server.stop();
780 | }
781 | }
782 |
783 |
784 | /**
785 | * Create or get the sketch's properties file
786 | * @return the sketch properties file or null
787 | */
788 | protected File getSketchPropertiesFile() {
789 | File sketchPropsFile =
790 | new File(getSketch().getFolder(), "sketch.properties");
791 | if (!sketchPropsFile.exists()) {
792 | try {
793 | sketchPropsFile.createNewFile();
794 | } catch (IOException ioe) {
795 | ioe.printStackTrace();
796 | statusError("Unable to create sketch properties file!");
797 | return null;
798 | }
799 | }
800 | return sketchPropsFile;
801 | }
802 |
803 |
804 | @Override
805 | public void handleImportLibrary(String name) {
806 | // unlike the other Modes, this is actually adding the library code
807 | Library library = mode.findLibraryByName(name);
808 | File folder = new File(library.getFolder(), "library");
809 | try {
810 | Util.copyDir(folder, new File(sketch.getFolder(), "libraries"));
811 | statusNotice("Copied " + name + " to the libraries folder of this sketch.");
812 | } catch (IOException e) {
813 | statusError(e);
814 | }
815 | }
816 | }
817 |
--------------------------------------------------------------------------------
/src/processing/mode/p5js/p5jsLibrary.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js;
2 |
3 | import java.io.File;
4 |
5 | import processing.app.Library;
6 |
7 |
8 | public class p5jsLibrary extends Library {
9 |
10 | public p5jsLibrary(File folder) {
11 | super(folder);
12 | }
13 |
14 |
15 | @Override
16 | protected void handle() {
17 | // no platform-specific stuff to do here; clear out the superclass
18 | // parsing of the .jar file and whatnot
19 | }
20 | }
--------------------------------------------------------------------------------
/src/processing/mode/p5js/p5jsMode.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.ArrayList;
6 |
7 | import processing.app.*;
8 | import processing.app.syntax.TokenMarker;
9 | import processing.app.ui.*;
10 | import processing.core.PApplet;
11 |
12 |
13 | public class p5jsMode extends Mode {
14 | private p5jsEditor jsEditor;
15 |
16 |
17 | public p5jsMode (Base base, File folder) {
18 | super(base, folder);
19 | }
20 |
21 |
22 | /**
23 | * Called to create the actual editor when needed (once per Sketch)
24 | */
25 | @Override
26 | public Editor createEditor(Base base, String path,
27 | EditorState state) throws EditorException {
28 | jsEditor = new p5jsEditor(base, path, state, this);
29 | return jsEditor;
30 | }
31 |
32 |
33 | /**
34 | * Called from Base to get the Editor for this mode.
35 | */
36 | public Editor getEditor() {
37 | return jsEditor;
38 | }
39 |
40 |
41 | @Override
42 | public File[] getKeywordFiles() {
43 | return new File[] {
44 | Platform.getContentFile("modes/java/keywords.txt"),
45 | new File(folder, "keywords.txt")
46 | };
47 | }
48 |
49 |
50 | @Override
51 | public TokenMarker getTokenMarker(SketchCode code) {
52 | String ext = code.getExtension();
53 |
54 | if (ext.equals("js") || ext.equals("json")) {
55 | return tokenMarker;
56 |
57 | } else if (code.isExtension("html")) {
58 | return new HtmlTokenMarker();
59 |
60 | // } else if (code.isExtension("css")) {
61 | // System.out.println("no highlight for " + code.getFile());
62 | // return null;
63 | }
64 | return null; // no styling
65 | }
66 |
67 |
68 | /**
69 | * Return pretty title of this mode for menu listing and such
70 | */
71 | @Override
72 | public String getTitle() {
73 | return "p5.js";
74 | }
75 |
76 |
77 | // public EditorToolbar createToolbar(Editor editor) { }
78 |
79 |
80 | // public Formatter createFormatter() { }
81 |
82 |
83 | // public Editor createEditor(Base base, String path, int[] location) { }
84 |
85 |
86 | /**
87 | * Get a list of folders that contain examples, ordered by the way they
88 | * should show up in the window or menu.
89 | */
90 | @Override
91 | public File[] getExampleCategoryFolders() {
92 | final String[] titles = {
93 | "Structure", "Form", "Data", "Arrays", "Control", "Image", "Color",
94 | "Math", "Simulate", "Interaction", "Objects", "Lights", "Motion",
95 | "Instance Mode", "DOM", "Drawing", "Transform", "Typography",
96 | "3D", "Input", "Advanced Data", "Sound", "Mobile", "Hello P5"
97 | };
98 |
99 | File[] outgoing = new File[titles.length];
100 | for (int i = 0; i < titles.length; i++) {
101 | outgoing[i] = new File(examplesFolder, titles[i]);
102 | }
103 | return outgoing;
104 | }
105 |
106 |
107 | @Override
108 | public void rebuildLibraryList() {
109 | coreLibraries = new ArrayList<>();
110 | Library soundLibrary =
111 | new p5jsLibrary(new File(getLibrariesFolder(), "p5.sound"));
112 | coreLibraries.add(soundLibrary);
113 |
114 | // no contribs for now, figure this out later
115 | contribLibraries = new ArrayList<>();
116 | }
117 |
118 |
119 | @Override
120 | public boolean requireExampleCompatibility() {
121 | return true;
122 | }
123 |
124 |
125 | /**
126 | * Return the default extension for this mode.
127 | */
128 | @Override
129 | public String getDefaultExtension() {
130 | return "js";
131 | }
132 |
133 |
134 | /**
135 | * The list of extensions that should show up as tabs.
136 | */
137 | @Override
138 | public String[] getExtensions () {
139 | return new String[] { "js", "html", "css" };
140 | }
141 |
142 |
143 | /**
144 | * Return list of file and folder names that should be ignored on Save As.
145 | * Starting in Processing 3.2, this can return null (otherwise it should be
146 | * a zero length String array if there's nothing to ignore).
147 | */
148 | @Override
149 | public String[] getIgnorable() {
150 | return null;
151 | }
152 |
153 |
154 | /**
155 | * Calls the superclass implementation, then calls buildIndex()
156 | * to rewrite index.html with the sketch name.
157 | */
158 | @Override
159 | public File addTemplateFiles(File sketchFolder,
160 | String sketchName) throws IOException {
161 | File mainFile = super.addTemplateFiles(sketchFolder, sketchName);
162 | insertSketchName(sketchFolder, sketchName);
163 | return mainFile;
164 | }
165 |
166 |
167 | /**
168 | * Write the index.html file. Broken out for ImportExamples.
169 | * Only handles cases where the sketch has no libraries and only
170 | * a single tab: 1) ImportExamples and 2) an Untitled sketch.
171 | */
172 | static public void insertSketchName(File sketchFolder,
173 | String sketchName) throws IOException {
174 | File indexFile = new File(sketchFolder, "index.html");
175 | String[] lines = PApplet.loadStrings(indexFile);
176 | if (lines == null) {
177 | throw new IOException("Could not read " + indexFile);
178 | }
179 | String program = PApplet.join(lines, "\n");
180 | program = program.replaceAll("@@sketch@@", sketchName + ".js");
181 | PApplet.saveStrings(indexFile, PApplet.split(program, '\n'));
182 | }
183 |
184 |
185 | static SketchCode findIndexHtml(Sketch sketch) {
186 | for (SketchCode code : sketch.getCode()) {
187 | if (code.getFileName().equals("index.html")) {
188 | return code;
189 | }
190 | }
191 | return null;
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/processing/mode/p5js/p5jsToolbar.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js;
2 |
3 | import processing.app.ui.Editor;
4 | import processing.app.ui.EditorToolbar;
5 |
6 |
7 | public class p5jsToolbar extends EditorToolbar {
8 |
9 | public p5jsToolbar(Editor editor) {
10 | super(editor);
11 | }
12 |
13 |
14 | @Override
15 | public void handleRun(int modifiers) {
16 | p5jsEditor jsEditor = (p5jsEditor) editor;
17 | jsEditor.handleRun();
18 | }
19 |
20 |
21 | @Override
22 | public void handleStop() {
23 | p5jsEditor jsEditor = (p5jsEditor) editor;
24 | jsEditor.handleStop();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/CarlOrff.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 | import java.io.*;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.zip.GZIPOutputStream;
6 |
7 |
8 | /**
9 | * A wrapped PrintStream that uses CRLF to indicate newlines.
10 | */
11 | public class CarlOrff {
12 | PrintStream ps;
13 |
14 |
15 | public CarlOrff(final OutputStream out) {
16 | ps = new PrintStream(out);
17 | }
18 |
19 |
20 | public void print(String s) {
21 | ps.print(s);
22 | }
23 |
24 |
25 | public void println(String s) {
26 | print(s);
27 | println();
28 | }
29 |
30 |
31 | public void println() {
32 | ps.print("\r\n");
33 | }
34 |
35 |
36 | public void flush() {
37 | ps.flush();
38 | }
39 |
40 |
41 | public void close() {
42 | ps.flush();
43 | ps.close();
44 | }
45 |
46 |
47 | public void write(byte[] b) {
48 | ps.write(b, 0, b.length);
49 | }
50 |
51 |
52 | public void write(byte[] b, int off, int length) {
53 | ps.write(b, off, length);
54 | }
55 |
56 |
57 | public void writeGZ(String what) throws IOException {
58 | byte[] b = what.getBytes(StandardCharsets.UTF_8);
59 | writeGZ(b);
60 | }
61 |
62 |
63 | public void writeGZ(byte[] b) throws IOException {
64 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
65 | GZIPOutputStream gzos = new GZIPOutputStream(baos);
66 | gzos.write(b);
67 | gzos.flush();
68 | gzos.close();
69 | write(baos.toByteArray());
70 | println();
71 | }
72 |
73 |
74 | public void write(Throwable t) {
75 | t.printStackTrace(ps);
76 | }
77 |
78 |
79 | public void status(int code) {
80 | ps.println("HTTP/1.1 " + code + " " + HttpServer.getStatusMessage(code));
81 | }
82 | }
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/EditorHandler.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 | import processing.app.SketchCode;
4 |
5 | import javax.swing.text.BadLocationException;
6 | import java.io.*;
7 | import java.nio.charset.StandardCharsets;
8 |
9 |
10 | public class EditorHandler extends GenericHandler {
11 |
12 | public EditorHandler(HttpServer server) {
13 | super(server);
14 | }
15 |
16 |
17 | byte[] loadBytes(String path, File target) throws IOException {
18 | // if this is one of the editor files, send the version on-screen
19 | // (with any edits) rather than the version on disk.
20 | for (SketchCode code : server.editor.getSketch().getCode()) {
21 | if (code.getFileName().equals(path)) {
22 | if (code.isModified()) {
23 | String program = null;
24 | if (code.getDocument() != null) {
25 | try {
26 | // if actively editing, use the text from the Document object
27 | program = code.getDocumentText();
28 | } catch (BadLocationException ignored) { }
29 | }
30 | if (program == null) {
31 | // fall back to just getting the last code (setProgram() is
32 | // called in prepareRun() or whenever switching away from a tab)
33 | program = code.getProgram();
34 | }
35 | //code.setProgram(program);
36 | return program.getBytes(StandardCharsets.UTF_8);
37 | }
38 | }
39 | }
40 |
41 | // no changes, or an error, send the file from disk
42 | return super.loadBytes(path, target);
43 | }
44 | }
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/FavIconHandler.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import processing.core.PApplet;
7 |
8 |
9 | public class FavIconHandler extends Handler {
10 |
11 | /**
12 | * Send the Processing icon as a default favicon, avoiding incessant
13 | * 404s in the web browser console (to reduce unnecessary confusion).
14 | */
15 | public FavIconHandler(HttpServer server) {
16 | super(server);
17 | }
18 |
19 |
20 | @Override
21 | public void handle(String params, CarlOrff ps) {
22 | // Nah, don't bother: why deal with guessing the mime type?
23 | //File f = new File(server.getRoot(), "favicon.ico");
24 |
25 | final String ICON_PATH = "/icon/icon-32.png";
26 | try (InputStream input = PApplet.class.getResourceAsStream(ICON_PATH)) {
27 | if (input == null) {
28 | ps.status(HttpServer.HTTP_NOT_FOUND);
29 | ps.println("Content-Type: text/html");
30 | ps.println();
31 | ps.println("Not Found");
32 | ps.println("" + ICON_PATH + " not found.
");
33 |
34 | } else {
35 | try {
36 | byte[] b = PApplet.loadBytes(input);
37 | if (b == null) {
38 | throw new IOException("Could not read " + ICON_PATH);
39 | }
40 | ps.status(HttpServer.HTTP_OK);
41 | ps.println("Content-Type: image/png");
42 | ps.println("Content-Length: " + b.length);
43 | ps.println();
44 | ps.write(b);
45 |
46 | } catch (Exception e) {
47 | ps.status(HttpServer.HTTP_SERVER_ERROR);
48 | ps.println("Content-Type: text/html");
49 | ps.println();
50 | ps.println("Server Error");
51 | ps.println("Error while reading " + ICON_PATH);
52 | ps.write(e);
53 | }
54 | }
55 | } catch (IOException ignored) { }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/GenericHandler.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 | import java.io.*;
4 |
5 | import processing.core.PApplet;
6 |
7 |
8 | public class GenericHandler extends Handler {
9 |
10 | public GenericHandler(HttpServer server) {
11 | super(server);
12 | }
13 |
14 |
15 | @Override
16 | public void handle(String path, CarlOrff ps) {
17 | File target = new File(server.getRoot(), path); // leading slash already removed
18 | handleFile(path, ps, target);
19 | }
20 |
21 |
22 | void handleFile(String path, CarlOrff ps, File target) {
23 | if (target.exists()) {
24 | if (target.isDirectory()) {
25 | File indexFile = new File(target, "index.html");
26 | if (indexFile.exists()) {
27 | if (path.length() > 0 && !path.endsWith("/")) {
28 | // if it's a folder with a slash on the end, and there's an index
29 | // file in this folder, redirect to include the slash at the end,
30 | // so that relative URLs in the index.html work properly.
31 | ps.status(HttpServer.HTTP_FOUND);
32 | ps.println("Location: " + path + "/");
33 | ps.println();
34 |
35 | } else {
36 | // if there's already a slash, send back the index.html file
37 | path += "index.html";
38 | handle(path, ps);
39 | }
40 | } else {
41 | // if no index.html exists in this folder, then deny with a 401.
42 | ps.status(HttpServer.HTTP_UNAUTHORIZED);
43 | ps.println("Content-type: text/html");
44 | ps.println();
45 | ps.println("");
46 | ps.println("Directory Listing Denied
");
47 | ps.println("" + path + "
");
48 | ps.println("" + target.getAbsolutePath() + "
");
49 | ps.println("");
50 | }
51 | } else {
52 | try {
53 | String canonicalPath = target.getCanonicalPath();
54 | if (!canonicalPath.endsWith(path)) {
55 | if (canonicalPath.length() > path.length()) { // just to make sure
56 | String canonicalPiece =
57 | canonicalPath.substring(canonicalPath.length() - path.length());
58 | if (path.equalsIgnoreCase(canonicalPiece)) {
59 | System.err.println("Mismatched text found: " +
60 | path + " was requested, but the file is " + canonicalPiece);
61 | System.err.println("Fix the capitalization to avoid " +
62 | "problems when running this code on a server.");
63 | }
64 | }
65 | }
66 | byte[] b = loadBytes(path, target);
67 |
68 | ps.status(HttpServer.HTTP_OK);
69 | String contentType = "content/unknown";
70 | String localPath = target.getAbsolutePath();
71 | int dot = localPath.lastIndexOf('.');
72 | if (dot != -1) {
73 | String extension = localPath.substring(dot);
74 | String found = HttpServer.getMimeType(extension);
75 | if (found != null) {
76 | contentType = found;
77 | } else {
78 | System.err.println("no content type found for " + extension);
79 | }
80 | }
81 |
82 | ps.println("Content-Type: " + contentType);
83 | ps.println("Content-Length: " + b.length);
84 | ps.println();
85 | ps.write(b);
86 |
87 | } catch (Exception e) {
88 | ps.status(HttpServer.HTTP_SERVER_ERROR);
89 | ps.println("Content-type: text/html");
90 | ps.println();
91 | ps.println("");
92 | ps.println("" + HttpServer.HTTP_SERVER_ERROR + " Exception
");
93 | ps.println("");
94 | e.printStackTrace(ps.ps);
95 | ps.println("
");
96 | ps.println("");
97 | }
98 | }
99 | } else {
100 | ps.status(HttpServer.HTTP_NOT_FOUND);
101 | ps.println("Content-type: text/html");
102 | ps.println();
103 | ps.println("");
104 | ps.println("" + HttpServer.HTTP_NOT_FOUND + " Not Found
");
105 | ps.println("" + path + "
");
106 | ps.println("" + target.getAbsolutePath() + "
");
107 | ps.println("");
108 | }
109 | }
110 |
111 |
112 | byte[] loadBytes(String path, File target) throws IOException {
113 | byte[] b = PApplet.loadBytes(target);
114 | if (b == null) {
115 | throw new IOException("Could not read " + target);
116 | }
117 | return b;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/Handler.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 |
4 | abstract public class Handler {
5 | HttpServer server;
6 |
7 |
8 | public Handler(HttpServer server) {
9 | this.server = server;
10 | }
11 |
12 |
13 | abstract public void handle(String params, CarlOrff ps);
14 | }
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/HttpServer.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.net.InetAddress;
6 | import java.net.Inet4Address;
7 | import java.net.NetworkInterface;
8 | import java.net.ServerSocket;
9 | import java.net.Socket;
10 | import java.util.ArrayList;
11 | import java.util.Enumeration;
12 | import java.util.HashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.concurrent.ConcurrentHashMap;
16 |
17 | import processing.data.StringList;
18 | import processing.mode.p5js.p5jsEditor;
19 |
20 |
21 | /**
22 | * This is a very simple, multi-threaded HTTP server, originally based on
23 | * this article on java.sun.com.
24 | */
25 | public class HttpServer {
26 | // Where worker threads stand idle
27 | final List threads = new ArrayList<>();
28 |
29 | // timeout on client connections
30 | static final int TIMEOUT = 10000;
31 |
32 | // max # worker threads
33 | static final int WORKERS = 5;
34 |
35 | Map handlerMap = new ConcurrentHashMap<>();
36 | Handler genericHandler;
37 |
38 | ServerSocket socket;
39 | Thread thread;
40 | int port;
41 | boolean running;
42 |
43 | p5jsEditor editor;
44 | //File root;
45 |
46 |
47 | /*
48 | public HttpServer(p5jsEditor editor) {
49 | this(editor, (int) (8000 + Math.random() * 1000));
50 | }
51 | */
52 |
53 |
54 | public HttpServer(p5jsEditor editor, int port) throws IOException {
55 | this.editor = editor;
56 | this.port = port;
57 |
58 | /*
59 | handlerMap.put("details", new DetailsHandler(this));
60 | handlerMap.put("header", new HeaderHandler(this));
61 | handlerMap.put("page", new PageHandler(this));
62 | handlerMap.put("sample", new SampleHandler(this));
63 | handlerMap.put("skip", new SkipHandler(this));
64 | handlerMap.put("status", new StatusHandler(this));
65 | handlerMap.put("types", new TypesHandler(this));
66 | */
67 | // handlerMap.put("libraries", new LibrariesHandler(this));
68 | //handlerMap.put("libraries", new GenericHandler(this, getLibrariesFolder()));
69 | handlerMap.put("favicon.ico", new FavIconHandler(this));
70 | //genericHandler = new GenericHandler(this);
71 | genericHandler = new EditorHandler(this);
72 |
73 | for (int i = 0; i < WORKERS; i++) {
74 | HttpWorker w = new HttpWorker(this);
75 | new Thread(w, "HttpServer Worker " + (i+1)).start();
76 | threads.add(w);
77 | }
78 |
79 | // try {
80 | socket = new ServerSocket(port);
81 | // } catch (BindException be) {
82 | // // socket already in use; try another
83 | // }
84 |
85 | /*
86 | String url = "http://localhost:" + port + "/index.html";
87 | System.out.println(url);
88 | PApplet.launch(url);
89 | */
90 | }
91 |
92 |
93 | /*
94 | public void start() {
95 | if (thread == null) {
96 | thread = new Thread(new Runnable() {
97 | @Override
98 | public void run() {
99 | running = true;
100 | ServerSocket socket = null;
101 | try {
102 | // try {
103 | socket = new ServerSocket(port);
104 | // } catch (BindException be) {
105 | // // socket already in use; try another
106 | // }
107 | while (Thread.currentThread() == thread) {
108 | @SuppressWarnings("resource")
109 | Socket s = socket.accept();
110 | // Worker w = null;
111 | synchronized (threads) {
112 | if (threads.isEmpty()) {
113 | HttpWorker worker = new HttpWorker(HttpServer.this);
114 | worker.setSocket(s);
115 | (new Thread(worker, "additional worker")).start();
116 | } else {
117 | // w = threads.get(0);
118 | // threads.remove(0);
119 | HttpWorker w = threads.remove(0);
120 | w.setSocket(s);
121 | }
122 | }
123 | }
124 |
125 | } catch (IOException e) {
126 | e.printStackTrace();
127 |
128 | } finally {
129 | if (socket != null) {
130 | try {
131 | socket.close();
132 | } catch (IOException e) {
133 | e.printStackTrace();
134 | }
135 | }
136 | }
137 | running = false;
138 | }
139 | });
140 | }
141 | thread.start();
142 | }
143 | */
144 |
145 |
146 | public void start() {
147 | if (thread == null) {
148 | thread = new Thread(() -> {
149 | running = true;
150 | try {
151 | while (Thread.currentThread() == thread) {
152 | Socket s = socket.accept();
153 | synchronized (threads) {
154 | if (threads.isEmpty()) {
155 | HttpWorker worker = new HttpWorker(HttpServer.this);
156 | worker.setSocket(s);
157 | (new Thread(worker, "additional worker")).start();
158 | } else {
159 | HttpWorker w = threads.remove(0);
160 | w.setSocket(s);
161 | }
162 | }
163 | }
164 |
165 | } catch (IOException e) {
166 | e.printStackTrace();
167 |
168 | } finally {
169 | if (socket != null) {
170 | try {
171 | socket.close();
172 | } catch (IOException e) {
173 | e.printStackTrace();
174 | }
175 | }
176 | }
177 | running = false;
178 | });
179 | }
180 | thread.start();
181 | }
182 |
183 |
184 | public void stop() {
185 | // new Exception("server.stop() called").printStackTrace(System.out);
186 | thread = null;
187 | }
188 |
189 |
190 | public boolean addWorker(HttpWorker worker) {
191 | synchronized (threads) {
192 | if (threads.size() >= WORKERS) {
193 | // too many threads, exit this one
194 | return false;
195 | }
196 | threads.add(worker);
197 | return true;
198 | }
199 | }
200 |
201 |
202 | /*
203 | public boolean isRunning () {
204 | return running;
205 | }
206 | */
207 |
208 |
209 | public boolean isDead() {
210 | return !running || thread == null;
211 | }
212 |
213 |
214 | public String getAddress() {
215 | return "http://127.0.0.1:" + port + "/";
216 | }
217 |
218 |
219 | /**
220 | * Return the first local IPv4 address that is not 127.0.0.1,
221 | * or null if there's nothing that matches those criteria.
222 | */
223 | public StringList getLocalAddresses() {
224 | StringList outgoing = new StringList();
225 | try {
226 | Enumeration e = NetworkInterface.getNetworkInterfaces();
227 | while (e.hasMoreElements()) {
228 | NetworkInterface n = e.nextElement();
229 | Enumeration ee = n.getInetAddresses();
230 | while (ee.hasMoreElements()) {
231 | InetAddress i = ee.nextElement();
232 | if (i instanceof Inet4Address) {
233 | String addr = i.getHostAddress();
234 | if (!addr.equals("127.0.0.1")) { // not useful
235 | outgoing.append("http://" + addr + ":" + port + "/");
236 | }
237 | }
238 | }
239 | }
240 | } catch (IOException e) {
241 | e.printStackTrace();
242 | }
243 | return outgoing;
244 | }
245 |
246 |
247 | File getRoot() {
248 | // Makes it persist properly even as the sketch is saved to new locations.
249 | // A sketch from a different editor will be running its own server.
250 | return editor.getSketch().getFolder();
251 | //return root;
252 | }
253 |
254 |
255 | /*
256 | File getLibrariesFolder() {
257 | return new File(editor.getTemplateFolder(), "libraries");
258 | }
259 | */
260 |
261 |
262 | /**
263 | * For use when the first entry of a path is a REST API command or something
264 | * like that, i.e. /list/blah should go to a 'list' handler.
265 | */
266 | Handler getHandler(String prefix) {
267 | return handlerMap.get(prefix);
268 | }
269 |
270 |
271 | Handler getGenericHandler() {
272 | return genericHandler;
273 | }
274 |
275 |
276 | /*
277 | static void log(String s) {
278 | if (false) {
279 | System.out.println(s);
280 | }
281 | }
282 | */
283 |
284 |
285 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
286 |
287 |
288 | /** mapping of file extensions to content-types */
289 | static Map mimeTypes = new HashMap<>();
290 |
291 |
292 | static String getMimeType(String extension) {
293 | return mimeTypes.get(extension);
294 | }
295 |
296 |
297 | static {
298 | mimeTypes.put("", "content/unknown");
299 |
300 | mimeTypes.put(".uu", "application/octet-stream");
301 | mimeTypes.put(".exe", "application/octet-stream");
302 | mimeTypes.put(".ps", "application/postscript");
303 | mimeTypes.put(".zip", "application/zip");
304 | mimeTypes.put(".sh", "application/x-shar");
305 | mimeTypes.put(".tar", "application/x-tar");
306 | mimeTypes.put(".snd", "audio/basic");
307 | mimeTypes.put(".au", "audio/basic");
308 | mimeTypes.put(".wav", "audio/x-wav");
309 |
310 | mimeTypes.put(".gif", "image/gif");
311 | mimeTypes.put(".jpg", "image/jpeg");
312 | mimeTypes.put(".jpeg", "image/jpeg");
313 | mimeTypes.put(".png", "image/png");
314 | mimeTypes.put(".svg", "image/svg+xml");
315 |
316 | mimeTypes.put(".htm", "text/html");
317 | mimeTypes.put(".html", "text/html");
318 | mimeTypes.put(".css", "text/css");
319 | mimeTypes.put(".js", "text/javascript");
320 | // 'application/xml' is an alternative for .xml's mime type
321 | mimeTypes.put(".xml", "text/xml");
322 |
323 | mimeTypes.put(".json", "application/json");
324 | mimeTypes.put(".jsonp", "application/javascript");
325 |
326 | mimeTypes.put(".csv", "text/csv");
327 | mimeTypes.put(".tsv", "text/tab-separated-values");
328 |
329 | // 'application/x-font-opentype' is another .otf alternative
330 | mimeTypes.put(".otf", "font/opentype");
331 | // 'application/x-font-truetype' is an alternative for .ttf
332 | mimeTypes.put(".ttf", "application/x-font-ttf");
333 | mimeTypes.put(".woff", "application/font-woff");
334 | mimeTypes.put(".woff2", "application/font-woff2");
335 | // 'font/opentype' is an alternate for .eot's mime type
336 | mimeTypes.put(".eot", "application/vnd.ms-fontobject");
337 |
338 | mimeTypes.put(".cur", "image/vnd.microsoft.icon");
339 |
340 | mimeTypes.put(".txt", "text/plain");
341 | mimeTypes.put(".java", "text/plain"); // x-java-source -> plain is better
342 | }
343 |
344 |
345 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
346 |
347 |
348 | /** 2XX: generally "OK" */
349 | static public final int HTTP_OK = 200;
350 | // static private final int HTTP_CREATED = 201;
351 | // static private final int HTTP_ACCEPTED = 202;
352 | // static private final int HTTP_NOT_AUTHORITATIVE = 203;
353 | // static private final int HTTP_NO_CONTENT = 204;
354 | // static private final int HTTP_RESET = 205;
355 | // static private final int HTTP_PARTIAL = 206;
356 |
357 | /** 3XX: relocation/redirect */
358 | // static private final int HTTP_MULT_CHOICE = 300;
359 | // static private final int HTTP_MOVED_PERM = 301;
360 | static public final int HTTP_FOUND = 302;
361 | // static private final int HTTP_SEE_OTHER = 303;
362 | // static private final int HTTP_NOT_MODIFIED = 304;
363 | // static private final int HTTP_USE_PROXY = 305;
364 |
365 | /** 4XX: client error */
366 | // static private final int HTTP_BAD_REQUEST = 400;
367 | static public final int HTTP_UNAUTHORIZED = 401;
368 | // static private final int HTTP_PAYMENT_REQUIRED = 402;
369 | // static private final int HTTP_FORBIDDEN = 403;
370 | static public final int HTTP_NOT_FOUND = 404;
371 | static public final int HTTP_BAD_METHOD = 405;
372 | // static private final int HTTP_NOT_ACCEPTABLE = 406;
373 | // static private final int HTTP_PROXY_AUTH = 407;
374 | // static private final int HTTP_CLIENT_TIMEOUT = 408;
375 | // static private final int HTTP_CONFLICT = 409;
376 | // static private final int HTTP_GONE = 410;
377 | // static private final int HTTP_LENGTH_REQUIRED = 411;
378 | // static private final int HTTP_PRECON_FAILED = 412;
379 | // static private final int HTTP_ENTITY_TOO_LARGE = 413;
380 | // static private final int HTTP_REQ_TOO_LONG = 414;
381 | // static private final int HTTP_UNSUPPORTED_TYPE = 415;
382 |
383 | /** 5XX: server error */
384 | static public final int HTTP_SERVER_ERROR = 500;
385 | // static private final int HTTP_INTERNAL_ERROR = 501;
386 | // static private final int HTTP_BAD_GATEWAY = 502;
387 | // static private final int HTTP_UNAVAILABLE = 503;
388 | // static private final int HTTP_GATEWAY_TIMEOUT = 504;
389 | // static private final int HTTP_VERSION = 505;
390 |
391 |
392 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
393 |
394 |
395 | static Map statusMessages = new HashMap<>();
396 |
397 |
398 | static String getStatusMessage(int code) {
399 | return statusMessages.get(code);
400 | }
401 |
402 |
403 | static {
404 | statusMessages.put(HTTP_OK, "OK");
405 | statusMessages.put(HTTP_FOUND, "Found");
406 | statusMessages.put(HTTP_UNAUTHORIZED, "Found");
407 | statusMessages.put(HTTP_NOT_FOUND, "Not Found");
408 | statusMessages.put(HTTP_BAD_METHOD, "Bad Method");
409 | statusMessages.put(HTTP_SERVER_ERROR, "Server Error");
410 | }
411 | }
--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/HttpWorker.java:
--------------------------------------------------------------------------------
1 | package processing.mode.p5js.server;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 | import java.net.Socket;
8 | import java.net.SocketTimeoutException;
9 |
10 | import processing.core.PApplet;
11 |
12 |
13 | public class HttpWorker implements Runnable {
14 | private HttpServer server;
15 | private Socket socket;
16 |
17 |
18 | HttpWorker(HttpServer server) {
19 | this.server = server;
20 | socket = null;
21 | }
22 |
23 |
24 | /**
25 | * Set the Socket for this worker. The Socket will be closed by this class
26 | * once finished.
27 | */
28 | synchronized void setSocket(Socket s) {
29 | this.socket = s;
30 | notify();
31 | }
32 |
33 |
34 | @Override
35 | public synchronized void run() {
36 | while (true) {
37 | if (socket == null) {
38 | // nothing to do
39 | try {
40 | wait();
41 | } catch (InterruptedException e) {
42 | // should not happen
43 | continue;
44 | }
45 | }
46 | try {
47 | handleClient();
48 | } catch (Exception e) {
49 | e.printStackTrace();
50 | }
51 | // go back in wait queue if there's fewer than numHandler connections.
52 | socket = null;
53 | if (!server.addWorker(this)) {
54 | return;
55 | }
56 | }
57 | }
58 |
59 |
60 | void handleClient() throws IOException {
61 | InputStream input = socket.getInputStream();
62 | BufferedReader reader = PApplet.createReader(input);
63 | OutputStream output = socket.getOutputStream();
64 | CarlOrff ps = new CarlOrff(output);
65 |
66 | // we will only block in read for this many milliseconds
67 | // before we fail with java.io.InterruptedIOException,
68 | // at which point we will abandon the connection.
69 | socket.setSoTimeout(HttpServer.TIMEOUT);
70 | socket.setTcpNoDelay(true);
71 |
72 | try {
73 | String line = reader.readLine();
74 | if (line != null) {
75 | String[] pieces = line.split(" ");
76 | if ("GET".equals(pieces[0])) {
77 | String path = pieces[1];
78 | if (path.length() < 1 || path.charAt(0) != '/') {
79 | ps.status(HttpServer.HTTP_BAD_METHOD);
80 | ps.println("Content-type: text/plain");
81 | ps.println();
82 | ps.println("500 So Misunderstood");
83 | ps.println();
84 |
85 | } else {
86 | path = path.substring(1);
87 | int slash = path.indexOf('/');
88 | String params = null;
89 | if (slash != -1) {
90 | params = path.substring(slash + 1);
91 | } else {
92 | slash = path.length();
93 | }
94 | String command = path.substring(0, slash);
95 | Handler handler = server.getHandler(command);
96 | if (handler != null) {
97 | handler.handle(params, ps);
98 | } else {
99 | server.getGenericHandler().handle(path, ps);
100 | }
101 | }
102 |
103 | } else if ("HEAD".equals(pieces[0])) {
104 | System.err.println("HEAD request ignored");
105 |
106 | } else {
107 | ps.status(HttpServer.HTTP_BAD_METHOD);
108 | ps.println("Content-type: text/plain");
109 | ps.println();
110 | ps.println("405 Unsupported Method: " + pieces[0]);
111 | ps.println();
112 | }
113 | ps.flush();
114 | }
115 | } catch (SocketTimeoutException ste) {
116 | // Ignoring these for now... Seems to be extra sockets opened
117 | // by the browser, but not quite clear yet.
118 | // https://github.com/fathominfo/processing-p5js-mode/issues/5
119 | System.out.println("Socket timed out.");
120 |
121 | } catch (IOException e) {
122 | e.printStackTrace();
123 | }
124 |
125 | // clean up, but ignore any errors along the way
126 | try {
127 | reader.close();
128 | } catch (Exception ignored) { }
129 | try {
130 | input.close();
131 | } catch (Exception ignored) { }
132 | try {
133 | output.close();
134 | } catch (Exception ignored) { }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/template/sketch.js:
--------------------------------------------------------------------------------
1 | function setup() {
2 |
3 | }
4 |
5 |
6 | function draw() {
7 |
8 | }
--------------------------------------------------------------------------------
/todo.txt:
--------------------------------------------------------------------------------
1 | 0017 (1.6.1)
2 |
3 |
4 | _ warn if files are especially large
5 |
6 | _ lots of keywords (foreach, async, await) not in keywords.txt
7 |
8 | _ creates own sketch.properties, check on possible conflicts w/ Base.java
9 |
10 | _ add menu option to hold the port fixed (necessary?)
11 |
12 | _ make it possible for the main class to be something besides the default name
13 | _ want to use sketch.js (this is a fine restriction)
14 | _ how difficult to have modes determine whether a sketch is a sketch?
15 | _ so that the main sketch class doesn't have to follow a naming convention
16 | _ Mode.rebuildSketchbookTree() handles creation of the sketchbook menu
17 | _ which calls Base.addSketches() to do the actual adding
18 | _ would need some means of specifying what the 'main' file is
19 | _ at least with p5js we could provisionally just use sketch.js?
20 |
21 | _ or need better solution when opening sketch.js files
22 | _ just rename the sketch.js file itself
23 | _ https://github.com/fathominfo/processing-p5js-mode/issues/14
24 | _ This needs to be handled by an “Import” tool that pulls
25 | _ from a local folder or from the online editor.
26 | _ in particular, because the index.html might have just about anything
27 |
28 | _ ability to run sketches that require SSL
29 | o using SSL from inside p5jsMode?
30 | o https://stackoverflow.com/questions/2308479/simple-java-https-server
31 | X just not a good option: ip addresses don't work well
32 | _ better option would be sftp or github pages integration
33 |
34 | _ if starting a new sketch, must reload the browser first?
35 | _ what's happening here? concerned about code mods not updating
36 | _ set no cache on returned objects?
37 |
38 | _ when sketch is moved while the editor is open
39 | _ it can't re-create any of the supporting files
40 | _ leaving you with a broken sketch, which is messy b/c it looks ok
41 |
42 | _ how to enable the cdn version of p5.min.js
43 | _ would be nice to use by default, but want to avoid net connection as requirement
44 | _ (i.e. if you're using a network connection, why not use an online editor)
45 | _ or is there a way to handle it inside the p5js server?
46 | _ just have a preference/checkbox in the menu
47 | _ enabling the cdn will remove p5.min.js from the sketch
48 | _ new sketches inherit the last setting
49 |
50 | _ other import library changes
51 | _ it's actually 'add library' not 'import' in this case
52 | _ probably need a way to *remove* the libraries too
53 | _ get contributed libraries working?
54 |
55 | _ refactor library imports in p5
56 | _ properly refactor rebuildLibraryList() and the isCompatible() code
57 | _ which is currently only found in ExampleContribution, but should be
58 | _ implemented in a more general way for the other contribs
59 | _ clean up the static stuff since it's not just Examples
60 | _ Library.discover(File) is static and expects .jar files
61 | _ even once that's fixed, need to make sure js libs don't show up w/ Java
62 | _ meaning that there needs to be other ironing in there
63 |
64 | _ better means of understanding actual use of p5jsMode?
65 | _ shows as installed on 30% of machines using the manager
66 |
67 | _ mode option for showing html and css or not
68 | _ might be nice to hide these for beginners who will never modify them
69 | _ is it time to add Mode preferences?
70 | _ other supported types from old/offline p5js editor
71 | _ txt, html, css, js, json, scss, xml, csv, less
72 | _ include: html, css, js, scss, less
73 | _ exclude: xml, txt, csv, json (like to be in data folder)
74 | _ add basic syntax highlighting for css
75 |
76 |
77 | . . .
78 |
79 | roll the version/revision numbers in mode.properties
80 |
81 | # update the release in mode.properties (it'll match the rev number here)
82 | git tag -a release-1.0 -m '1.0 final'
83 | git tag -a v1.0.1 -m 'version 1.0.1'
84 | git tag -a v1.0.2 -m 'version 1.0.2'
85 | git tag -a v1.0.3 -m 'version 1.0.3'
86 | git tag -a v1.0.4 -m 'version 1.0.4'
87 | git tag -a v1.1 -m 'version 1.1'
88 | git tag -a v1.1.1 -m 'version 1.1.1'
89 | git tag -a v1.2 -m 'version 1.2'
90 | git tag -a v1.2.1 -m 'version 1.2.1'
91 | git tag -a v1.2.2 -m 'version 1.2.2'
92 | git tag -a v1.3 -m 'version 1.3'
93 | git tag -a v1.3.1 -m 'version 1.3.1'
94 | git tag -a v1.4 -m 'version 1.4'
95 | git tag -a v1.4.1 -m 'version 1.4.1'
96 | git tag -a v1.4.2 -m 'version 1.4.2'
97 | git tag -a v1.5 -m 'version 1.5'
98 | git tag -a v1.6 -m 'version 1.6'
99 | git push origin --tags
100 |
101 | # delete the previous 'latest'
102 | git tag -d latest
103 | git push origin :refs/tags/latest
104 |
105 | # create new 'latest' tag with the current state of the repo
106 | git tag -f -a latest -m 'version 1.6'
107 | # actually update things
108 | git push -f --tags
109 |
110 | ant dist
111 | then upload dist/p5jsMode.zip and dist/p5jsMode.txt to the 'latest' tag
112 | the *old version* will already be there; need to manually delete and then upload the new one
113 | (can also upload them to the most recent tag for anyone installing manually)
114 | and name/put some info about the release into the tag
115 | https://github.com/fathominfo/processing-p5js-mode/releases
116 |
--------------------------------------------------------------------------------