diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e459db..5886eb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,20 +37,21 @@ option(DEBUG_MODE "Enable debugging output and memory tracing?" ON) set(CMAKE_C_STANDARD 99) set(SOURCE_FILES - thirdparty-installed/memwatch/memwatch.c - ui/generated/resources.c - src/main.c - src/utils.c - src/joeydev.c - src/vector.c - src/array.c - src/draw.c - src/image.c - src/vecparse.c - src/color.c - src/palette.c - src/project.c - src/ssh.c + thirdparty-installed/memwatch/memwatch.c + ui/generated/resources.c + src/main.c + src/utils.c + src/joeydev.c + src/vector.c + src/array.c + src/draw.c + src/image.c + src/vecparse.c + src/color.c + src/palette.c + src/project.c + src/ssh.c + src/http.c ) configure_file(include/config.h.in config.h) @@ -110,9 +111,9 @@ target_link_libraries(${CMAKE_PROJECT_NAME} ${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libssh2.a ${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libgcrypt.a ${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libgpg-error.a + ${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libcurl.a ${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libz.a ${GTK3_LIBRARIES} -# -lssl -ldl -pthread -lm diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..e7e28e7 --- /dev/null +++ b/src/http.c @@ -0,0 +1,173 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#include "http.h" + + +static CURLM *_curlMulti = NULL; +static HTTPT **_activeHTTPs = NULL; + + +gboolean httpUpdate(gpointer userData); // Not static + +static size_t httpWrite(char *data, size_t num, size_t length, void *userp); + + +HTTPT *httpDownload(HTTPCallback callback, char *filename, char *url) { + HTTPT *download = NULL; + CURL *eh = NULL; + + download = NEW(HTTPT); + if (download) { + download->file = fopen(filename, "wb"); + if (download->file) { + download->filename = strdup(filename); + download->length = 0; + download->progress = 0; + download->finished = FALSE; + download->success = FALSE; + download->callback = callback; + eh = curl_easy_init(); +#ifdef DEBUG_MODE + //curl_easy_setopt(eh, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0L); +#endif + curl_easy_setopt(eh, CURLOPT_BUFFERSIZE, CURL_MAX_READ_SIZE); + curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, httpWrite); + curl_easy_setopt(eh, CURLOPT_WRITEDATA, download); + curl_easy_setopt(eh, CURLOPT_URL, url); + curl_multi_add_handle(_curlMulti, eh); + arrput(_activeHTTPs, download); + } else { + DEL(download); + } + } + + return download; +} + + +void httpShutdown(void) { + //***TODO*** Any active sessions? + + g_idle_remove_by_data(httpUpdate); + + curl_multi_cleanup(_curlMulti); + curl_global_cleanup(); +} + + +void httpStartup(void) { + curl_global_init(CURL_GLOBAL_ALL); + _curlMulti = curl_multi_init(); + g_idle_add(httpUpdate, httpUpdate); +} + + +gboolean httpUpdate(gpointer userData) { + CURLcode code; + CURL *eh = NULL; + CURLMsg *msg = NULL; + HTTPT *dl = NULL; + int msgsLeft = -1; + int stillAlive = 1; + long httpCode = 0; + int i; + + (void)userData; + + for (i=0; idata.result; + eh = msg->easy_handle; + // Try to get remote file size. + if (dl->length == 0) { + curl_easy_getinfo(eh, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &dl->length); +#ifdef DEBUG_MODE + debug("CURL Length %" CURL_FORMAT_CURL_OFF_T "\n", dl->length); +#endif + } + if (msg->msg == CURLMSG_DONE) { + // HTTP Error? + curl_easy_getinfo (eh, CURLINFO_RESPONSE_CODE, &httpCode); + // Finished. Tell CURL. + curl_multi_remove_handle(_curlMulti, eh); + curl_easy_cleanup(eh); + // Close local file. + fclose(dl->file); + // Transfer okay? + dl->success = (code == CURLE_OK && httpCode == 200); + dl->finished = TRUE; + // Nuke anything we may have written if transfer was bad. + if (dl->success == FALSE) { + unlink(dl->filename); + } + // Callback whoever issued this download. + if (dl->callback) { + dl->callback(dl); + } + // Remove from download list. + arrdel(_activeHTTPs, i); + // Free resources. + DEL(dl->filename); + DEL(dl); + break; +#ifdef DEBUG_MODE + } else { + debug("CURL: %d\n", code); +#endif + } + } + if (stillAlive) { + curl_multi_wait(_curlMulti, NULL, 0, 100, NULL); + } + } + + return G_SOURCE_CONTINUE; +} + + +static size_t httpWrite(char *data, size_t num, size_t length, void *userp) { + HTTPT *download = (HTTPT *)userp; + + // Write it to disk. + fwrite(data, num, length, download->file); + + // Update UI. + download->progress += num * length; +#ifdef DEBUG_MODE + debug("CURL Progress %" CURL_FORMAT_CURL_OFF_T " Length %" CURL_FORMAT_CURL_OFF_T "\n", download->progress, download->length); +#endif + + // Did the user request to cancel the transfer? + /* + if (download->finished) { + return CURL_READFUNC_ABORT; + } + */ + + return num * length; +} diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..b0e48a9 --- /dev/null +++ b/src/http.h @@ -0,0 +1,56 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef HTTP_H +#define HTTP_H + + +#include "common.h" + +#include "curl/curl.h" + + +struct HTTPS; + + +typedef void (*HTTPCallback)(struct HTTPS*); + + +typedef struct HTTPS { + CURLM *cm; + FILE *file; + char *filename; + gboolean finished; + gboolean success; + curl_off_t length; + curl_off_t progress; + HTTPCallback callback; + GtkWidget *status; +} HTTPT; + + +HTTPT *httpDownload(HTTPCallback callback, char *filename, char *url); +void httpShutdown(void); +void httpStartup(void); + + +#endif // HTTP_H diff --git a/src/main.c b/src/main.c index ed9341f..e011f9c 100644 --- a/src/main.c +++ b/src/main.c @@ -24,12 +24,14 @@ #include "common.h" #include "scintillaHeaders.h" #include "joeydev.h" +#include "http.h" #include "ssh.h" int main(int argc, char **argv) { gtk_init(&argc, &argv); + httpStartup(); sshStartup(); winJoeyDevCreate(); @@ -37,6 +39,7 @@ int main(int argc, char **argv) { scintilla_release_resources(); sshShutdown(); + httpShutdown(); return 0; } diff --git a/src/project.c b/src/project.c index 6596918..4fe5da5 100644 --- a/src/project.c +++ b/src/project.c @@ -22,11 +22,13 @@ #pragma clang diagnostic push #pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" +#pragma ide diagnostic ignored "EndlessLoop" #include "common.h" #include "project.h" #include "utils.h" +#include "http.h" #include "ssh.h" @@ -56,7 +58,8 @@ typedef struct ProjectDataS { GtkWidget *treeProject; char *configName; char *buildHost; - int buildPort; + int buildHTTPPort; + int buildSSHPort; char *buildUser; char *buildPassword; } ProjectDataT; @@ -81,26 +84,31 @@ static SectionDataT _sectionData[] = { }; +static gboolean _httpTestComplete = FALSE; +static gboolean _httpTestSuccess = FALSE; + + #define BUILD_SETTINGS_RESPONSE_TEST 1 -static void addToTree(ProjectDataT *self, char *filename); -static void loadConfig(ProjectDataT *self); -static void loadProject(ProjectDataT *self); -EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData); -EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData); -EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData); -EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData); -EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData); -EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData); -EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData); -EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData); -EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData); -EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData); -EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData); -static void saveConfig(ProjectDataT *self); -EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData); -static void winProjectDelete(gpointer userData); +static void addToTree(ProjectDataT *self, char *filename); +static void httpTest(HTTPT *download); +static void loadConfig(ProjectDataT *self); +static void loadProject(ProjectDataT *self); +EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData); +EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData); +EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData); +EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData); +EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData); +EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData); +EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData); +EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData); +EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData); +EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData); +EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData); +static void saveConfig(ProjectDataT *self); +EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData); +static void winProjectDelete(gpointer userData); static void addToTree(ProjectDataT *self, char *filename) { @@ -134,6 +142,12 @@ static void addToTree(ProjectDataT *self, char *filename) { } +static void httpTest(HTTPT *download) { + _httpTestComplete = TRUE; + _httpTestSuccess = download->success; +} + + static void loadConfig(ProjectDataT *self) { FILE *in = NULL; char *line = NULL; @@ -156,16 +170,20 @@ static void loadConfig(ProjectDataT *self) { self->buildHost = strdup(line); break; - case 3: // Port - self->buildPort = atoi(line); + case 3: // HTTP Port + self->buildHTTPPort = atoi(line); break; - case 4: // User + case 4: // SSH Port + self->buildSSHPort = atoi(line); + break; + + case 5: // User DEL(self->buildUser); self->buildUser = strdup(line); break; - case 5: // Password + case 6: // Password DEL(self->buildPassword); self->buildPassword = strdup(line); break; @@ -187,7 +205,8 @@ static void loadConfig(ProjectDataT *self) { DEL(self->buildPassword); self->buildHost = strdup("build.joeylib.com"); - self->buildPort = 816; + self->buildHTTPPort = 6502; + self->buildSSHPort = 816; self->buildUser = strdup(g_get_user_name()); self->buildPassword = strdup("SuperSecret"); @@ -396,39 +415,53 @@ EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData) { EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; + GtkWidget *dialog; GtkWidget *dialogServerSettings; GtkWidget *txtHost; - GtkWidget *txtPort; + GtkWidget *txtHTTPPort; + GtkWidget *txtSSHPort; GtkWidget *txtUser; GtkWidget *txtPassword; + GtkWidget *btnTest; + GtkWidget *btnOkay; char *widgetNames[] = { "dialogServerSettings", "txtHost", - "txtPort", + "txtHTTPPort", + "txtSSHPort", "txtUser", "txtPassword", + "btnTest", + "btnOkay", NULL }; GtkWidget **widgets[] = { &dialogServerSettings, &txtHost, - &txtPort, + &txtHTTPPort, + &txtSSHPort, &txtUser, - &txtPassword + &txtPassword, + &btnTest, + &btnOkay }; char temp[6]; + char *name = NULL; + char *url = NULL; int result = 0; SSHT *ssh = NULL; - char *output = NULL; + char *error = NULL; (void)object; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/BuildServer.glade", widgetNames, widgets, self); while (1) { - snprintf(temp, 6, "%d", self->buildPort); gtk_entry_set_text(GTK_ENTRY(txtHost), self->buildHost); - gtk_entry_set_text(GTK_ENTRY(txtPort), temp); + snprintf(temp, 6, "%d", self->buildHTTPPort); + gtk_entry_set_text(GTK_ENTRY(txtHTTPPort), temp); + snprintf(temp, 6, "%d", self->buildSSHPort); + gtk_entry_set_text(GTK_ENTRY(txtSSHPort), temp); gtk_entry_set_text(GTK_ENTRY(txtUser), self->buildUser); gtk_entry_set_text(GTK_ENTRY(txtPassword), self->buildPassword); result = gtk_dialog_run(GTK_DIALOG(dialogServerSettings)); @@ -438,28 +471,56 @@ EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) { DEL(self->buildUser); DEL(self->buildPassword); self->buildHost = strdup(gtk_entry_get_text(GTK_ENTRY(txtHost))); - self->buildPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtPort))); + self->buildHTTPPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtHTTPPort))); + self->buildSSHPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtSSHPort))); self->buildUser = strdup(gtk_entry_get_text(GTK_ENTRY(txtUser))); self->buildPassword = strdup(gtk_entry_get_text(GTK_ENTRY(txtPassword))); saveConfig(self); if (result == BUILD_SETTINGS_RESPONSE_TEST) { - // Run server connection test. - ssh = sshConnect(self->buildHost, self->buildPort, self->buildUser, self->buildPassword); - if (ssh) { - debug("Connected!\n"); - // echo ${JOEYLIB_SDKS} - result = sshExecute(ssh, "ls -la", &output); - debug("----------\n"); - if ((result == 0) && (strlen(output) > 0)) { - debug("%s", output); - } else { - debug("No output!\n"); - } - debug("----------\n"); - DEL(output); - sshDisconnect(&ssh); + gtk_widget_set_sensitive(btnTest, FALSE); + gtk_widget_set_sensitive(btnOkay, FALSE); + // Run server connection tests - HTTP. + name = utilCreateString("%s%cjoeydev%cjoeydev.info", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR); + url = utilCreateString("http://%s:%d/joeydev.info", self->buildHost, self->buildHTTPPort); + _httpTestComplete = FALSE; + httpDownload(httpTest, name, url); + // This loop seriously breaks CLion's syntax highlighting. + while (_httpTestComplete == FALSE) { + gtk_main_iteration(); } + if (_httpTestSuccess == FALSE) { + error = utilCreateString("Unable to connect to HTTP port."); + } + DEL(url); + DEL(name); + // Run server connection tests - SSH. + ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword); + if (ssh) { + sshDisconnect(&ssh); + } else { + DEL(error); + error = utilCreateString("Unable to connect to SSH port."); + } + // Did the tests pass? + if (error == NULL) { + dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, + "Successful connection to build server!"); + } else { + dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", error); + } + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + DEL(error); + gtk_widget_set_sensitive(btnTest, TRUE); + gtk_widget_set_sensitive(btnOkay, TRUE); } else { // Close dialog on OKAY but not TEST. break; @@ -500,7 +561,8 @@ static void saveConfig(ProjectDataT *self) { fprintf(out, "%s\n", BUILD_SETTINGS_VERSION); fprintf(out, "------------------------------------------------------------------------------\n"); fprintf(out, "%s\n", self->buildHost); - fprintf(out, "%d\n", self->buildPort); + fprintf(out, "%d\n", self->buildHTTPPort); + fprintf(out, "%d\n", self->buildSSHPort); fprintf(out, "%s\n", self->buildUser); fprintf(out, "%s\n", self->buildPassword); //***TODO*** This is hardly secure. fclose(out); diff --git a/src/ssh.c b/src/ssh.c index eb899a2..87aa2e7 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -105,7 +105,7 @@ SSHT *sshConnect(char *hostname, uint16_t port, char *user, char *password) { ; if (rc) { debug("Failure authenticating SSH session: %d\n", rc); - while(libssh2_session_disconnect(data->session, "Error") == LIBSSH2_ERROR_EAGAIN) + while (libssh2_session_disconnect(data->session, "Error") == LIBSSH2_ERROR_EAGAIN) ; libssh2_session_free(data->session); socketclose(data->sock); @@ -130,10 +130,10 @@ void sshDisconnect(SSHT **sshData) { int sshExecute(SSHT *sshData, char *command, char **output) { - int rc; - int exitcode = 127; - char *exitsignal = (char *)"none"; - int outputLen = 0; + int rc; + int exitcode = 127; + char *exitsignal = (char *)"none"; + int outputLen = 0; utilEnsureBufferSize((unsigned char **)output, &outputLen, 1024); *output[0] = 0; @@ -144,7 +144,7 @@ int sshExecute(SSHT *sshData, char *command, char **output) { if (sshData->channel == NULL) { return exitcode; } - while((rc = libssh2_channel_exec(sshData->channel, command)) == LIBSSH2_ERROR_EAGAIN) { + while ((rc = libssh2_channel_exec(sshData->channel, command)) == LIBSSH2_ERROR_EAGAIN) { sshWaitSocket(sshData->sock, sshData->session); } if (rc != 0) { @@ -210,8 +210,7 @@ SSHT *sshExecuteVerbose(SSHT *sshData, char *title, char *command, SSHCallback c void sshShutdown(void) { - //curl_multi_cleanup(_curlMulti); - //curl_global_cleanup(); + //***TODO*** Any active sessions? g_idle_remove_by_data(sshUpdate); @@ -225,9 +224,6 @@ void sshShutdown(void) { void sshStartup(void) { int err; - //curl_global_init(CURL_GLOBAL_ALL); - //_curlMulti = curl_multi_init(); - #ifdef WIN32 err = WSAStartup(MAKEWORD(2, 0), &_wsadata); if (err != 0) { diff --git a/thirdparty/curl-7.87.0.tar.bz2 b/thirdparty/curl-7.87.0.tar.bz2 new file mode 100644 index 0000000..a58ec62 --- /dev/null +++ b/thirdparty/curl-7.87.0.tar.bz2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d6e128761b7110946d1276aff6f0f266f2b726f5e619f7e0a057a474155f307 +size 3140918 diff --git a/tools/prebuild.sh b/tools/prebuild.sh index a84993a..fb4e8ac 100755 --- a/tools/prebuild.sh +++ b/tools/prebuild.sh @@ -108,6 +108,24 @@ pushd "${ROOT}" || exit &> /dev/null popd || true &> /dev/null fi + if [[ ! -f ${INSTALLED}/lib/libcurl.a ]]; then + echo Building Dependency: libcurl... + tar xjf ${THIRDPARTY}/curl-7.87.0.tar.bz2 + pushd curl-7.87.0 || exit &> /dev/null + ./configure \ + --enable-static \ + --disable-shared \ + --without-ssl \ + --without-libidn2 \ + --without-libpsl \ + --without-brotli \ + --without-zstd \ + --prefix=${INSTALLED} + make + make install + popd || true &> /dev/null + fi + if [[ ! -f ${INSTALLED}/lib/scintilla.a ]]; then echo Building Dependency: Scintilla... tar xzf ${THIRDPARTY}/scintilla531.tgz diff --git a/ui/BuildServer.glade b/ui/BuildServer.glade index ad51254..8fc0420 100644 --- a/ui/BuildServer.glade +++ b/ui/BuildServer.glade @@ -51,7 +51,7 @@ - + True False @@ -70,45 +70,6 @@ 0 - - - True - False - end - Port: - right - - - 0 - 1 - - - - - True - False - end - User: - right - - - 0 - 2 - - - - - True - False - end - Password: - right - - - 0 - 3 - - True @@ -122,15 +83,55 @@ - + + True + False + end + Password: + right + + + 0 + 4 + + + + True True True - digits + False + password 1 - 1 + 4 + + + + + True + False + end + User: + right + + + 0 + 3 + + + + + True + False + end + SSH Port: + right + + + 0 + 2 @@ -140,21 +141,46 @@ True name + + 1 + 3 + + + + + True + True + True + digits + 1 2 - + True True True - password + digits 1 - 3 + 1 + + + + + True + False + end + HTTP Port: + right + + + 0 + 1