joeydev/src/project.c

1535 lines
45 KiB
C

/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* 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.
*/
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
#pragma ide diagnostic ignored "cert-err34-c" // atoi warnings
#include "common.h"
#include "project.h"
#include "cwalk.h"
#include "utils.h"
#include "http.h"
#include "ssh.h"
#include "messages.h"
#include "compiler.h"
#include "editor.h"
#define LOCAL_BUILD_RESULTS "build.tar.bz2"
#define REMOTE_BUILD_RESULTS "build/build.tar.bz2"
enum ProjectColumnsE {
COL_FILENAME = 0,
COL_COUNT
};
typedef enum ProjectColumnsE ProjectColumnsT;
// These have to match SectionDataT below.
enum ProjectSectionTypeE {
SECTION_HEADER = 0,
SECTION_CODE,
SECTION_BITMAP,
SECTION_STENCIL,
SECTION_VECTOR,
SECTION_SOUND,
SECTION_MUSIC,
SECTION_RAW_DATA,
SECTION_COOKED_DATA,
SECTION_COUNT
};
typedef enum ProjectSectionTypeE ProjectSectionTypeT;
typedef struct ArchS {
char *name;
gboolean selected;
} ArchT;
typedef struct TargetS {
char *name;
char *longName;
ArchT **archs;
} TargetT;
typedef struct ProjectDataS {
WindowDataT windowData;
GtkWidget *treeProject;
StringHashT **recipes;
char *configName;
char *buildHost;
int buildHTTPPort;
int buildSSHPort;
char *buildUser;
char *buildPassword;
TargetT **targets;
GtkWidget *tempWidget; // Used to pass data around dialogs.
} ProjectDataT;
typedef struct SectionDataS {
char *name;
char *extension;
} SectionDataT;
// These have to match ProjectSectionTypeT above.
static SectionDataT _sectionData[] = {
{ "Header", "*.h" },
{ "Code", "*.c" },
{ "Bitmap", "*.img" },
{ "Stencil", "*.stn" },
{ "Vector", "*.vic" },
{ "Sound", "*.snd" },
{ "Music", "*.mod" },
{ "Raw Data", NULL },
{ "Cooked Data", "*.dat" },
{ NULL, NULL }
};
static ProjectDataT *_cookingProjectData = NULL;
static char **_sendList = NULL;
#define BUILD_SETTINGS_RESPONSE_TEST 1
static void addToRecipeData(ProjectDataT *self, char *key, char *value);
EVENT void buildTargetClicked(GtkButton *widget, gpointer userData);
EVENT void btnEditRawClicked(GtkButton *widget, gpointer userData);
EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData);
EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData);
static void clearRecipeData(ProjectDataT *self);
static void decompressBuild(ArchiveT *archive);
static void dialogCookOptions(char *filename, ProjectDataT *self);
static int findRecipeData(ProjectDataT *self, char *key);
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 menuProjectBuildSettings(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildTargets(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData);
EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData);
static void receiveSFTP(SFTPT *sftp);
static void saveConfig(ProjectDataT *self);
static void sendSFTP(SFTPT *sftp);
static TargetT **targetArrayCopy(TargetT **targets);
static void targetArrayDelete(TargetT ***array);
EVENT void treeProjectRowActivated(GtkTreeView *treeView, GtkTreePath *path, GtkTreeViewColumn *column, gpointer userData);
static gboolean updateBuildOptions(ProjectDataT *self);
static gboolean waitForBuild(gpointer userData);
EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData);
static void winProjectDelete(gpointer userData);
static void addToRecipeData(ProjectDataT *self, char *key, char *value) {
int i;
StringHashT *s;
i = findRecipeData(self, key);
if (i >= 0) arrdel(self->recipes, i);
s = NEW(StringHashT);
s->key = strdup(key);
s->value = strdup(value);
arrput(self->recipes, s);
}
EVENT void buildTargetClicked(GtkButton *widget, gpointer userData) {
ArchT *arch = (ArchT *)userData;
arch->selected = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
}
EVENT void btnEditRawClicked(GtkButton *widget, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
char *filename = NULL;
GtkWidget *lblRaw = utilFindChildWidget(self->tempWidget, "lblRaw");
(void)widget;
gtk_dialog_response(GTK_DIALOG(self->tempWidget), GTK_RESPONSE_CANCEL);
filename = utilCreateString("%s%s", self->windowData.path, gtk_label_get_text(GTK_LABEL(lblRaw)));
winEditorCreate(filename);
DEL(filename);
}
EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
char *file = NULL;
GtkWidget *fileRecipe = utilFindChildWidget(self->tempWidget, "fileRecipe");
(void)widget;
gtk_dialog_response(GTK_DIALOG(self->tempWidget), GTK_RESPONSE_CANCEL);
file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fileRecipe));
if (utilFileExists(file)) {
winEditorCreate(file);
}
DEL(file);
}
EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
char *newFile = NULL;
char *fromTemp = NULL;
char *toTemp = NULL;
(void)widget;
if (utilFileSaveOtherAs((WindowDataT *)userData, "*.c", "Recipe", &newFile)) {
fromTemp = utilCreateString("%s%s", __resourcePath, "recipe.c");
utilFileCopy(fromTemp, newFile);
DEL(fromTemp);
fromTemp = utilCreateString("%s%s", __resourcePath, "gitignore");
toTemp = utilCreateString("%s%s", self->windowData.path, ".gitignore");
utilFileCopy(fromTemp, toTemp);
DEL(toTemp);
DEL(fromTemp);
fromTemp = utilCreateString("%s%s", __resourcePath, "gitattributes");
toTemp = utilCreateString("%s%s", self->windowData.path, ".gitattributes");
utilFileCopy(fromTemp, toTemp);
DEL(toTemp);
DEL(fromTemp);
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->tempWidget), newFile);
}
}
static void clearRecipeData(ProjectDataT *self) {
// Clear recipe data.
while (arrlen(self->recipes) > 0) {
DEL(self->recipes[0]->key);
DEL(self->recipes[0]->value);
arrdel(self->recipes, 0);
}
ARRFREE(self->recipes);
}
static void decompressBuild(ArchiveT *archive) {
ProjectDataT *self = (ProjectDataT *)archive->userData;
char *temp;
// Delete archive.
temp = utilCreateString("%s%s", self->windowData.path, LOCAL_BUILD_RESULTS);
unlink(temp);
DEL(temp);
//***TODO*** Process build results.
}
static void dialogCookOptions(char *filename, ProjectDataT *self) {
GtkWidget *dialogCookSettings;
GtkWidget *lblRaw;
GtkWidget *fileRecipe;
GtkWidget *btnNewRecipe;
GtkWidget *btnCancel;
GtkWidget *btnOkay;
int original;
int result;
char *raw = NULL;
char *temp = NULL;
char *widgetNames[] = {
"dialogCookSettings",
"lblRaw",
"fileRecipe",
"btnNewRecipe",
"btnCancel",
"btnOkay",
NULL
};
GtkWidget **widgets[] = {
&dialogCookSettings,
&lblRaw,
&fileRecipe,
&btnNewRecipe,
&btnCancel,
&btnOkay
};
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Cook.glade", widgetNames, widgets, self);
raw = utilFileBasename(filename);
gtk_label_set_text(GTK_LABEL(lblRaw), raw);
self->tempWidget = dialogCookSettings;
original = findRecipeData(self, raw);
if (original >= 0) {
cwk_path_change_basename(self->windowData.filename, self->recipes[original]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(fileRecipe), __utilFilenameBuffer);
}
result = gtk_dialog_run(GTK_DIALOG(dialogCookSettings));
if (result == GTK_RESPONSE_OK) {
temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fileRecipe));
if (temp != NULL) {
// Convert to relative path.
cwk_path_get_relative(self->windowData.path, temp, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
if (__utilFilenameBuffer[0] != 0) {
DEL(temp);
temp = strdup(__utilFilenameBuffer);
}
// Did the recipe change?
if (!((original >= 0) && (strcmp(self->recipes[original]->value, temp) == 0))) {
addToRecipeData(self, raw, temp);
utilSetDirty((WindowDataT *)self, TRUE);
}
DEL(temp);
}
}
DEL(raw);
self->tempWidget = NULL;
gtk_widget_destroy(dialogCookSettings);
}
static int findRecipeData(ProjectDataT *self, char *key) {
int i;
int result = -1;
for (i=0; i<arrlen(self->recipes); i++) {
if (strcmp(self->recipes[i]->key, key) == 0) {
result = i;
break;
}
}
return result;
}
static void loadConfig(ProjectDataT *self) {
FILE *in = NULL;
char *line = NULL;
size_t len = 0;
size_t count = 0;
self->configName = utilCreateString("%s%cjoeydev%cbuild.conf", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR);
if (utilFileExists(self->configName)) {
in = fopen(self->configName, "rt");
if (in != NULL) {
utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker.
while (getline(&line, &len, in) != -1) {
if (strlen(line) > 0) line[strlen(line) - 1] = 0;
switch (count) {
case 0: // Version Number
case 1: // Separator line
break;
case 2: // Host
DEL(self->buildHost);
self->buildHost = strdup(line);
break;
case 3: // HTTP Port
self->buildHTTPPort = atoi(line);
break;
case 4: // SSH Port
self->buildSSHPort = atoi(line);
break;
case 5: // User
DEL(self->buildUser);
self->buildUser = strdup(line);
break;
case 6: // Password
DEL(self->buildPassword);
self->buildPassword = utilDeobfuscateASCII(line);
break;
default: // Oops
break;
}
count++;
}
fclose(in);
DEL(line);
} else {
message(MSG_SEVERE, "Unknown error attempting to load build configuration!");
}
} else {
// No config file. Set defaults.
DEL(self->buildHost);
DEL(self->buildUser);
DEL(self->buildPassword);
self->buildHost = strdup("build.joeylib.com");
self->buildHTTPPort = 6502;
self->buildSSHPort = 816;
self->buildUser = strdup(g_get_user_name());
self->buildPassword = strdup("SuperSecret");
saveConfig(self);
}
}
static void loadProject(ProjectDataT *self) {
FILE *in = NULL;
char *line = NULL;
size_t len = 0;
char *c = NULL;
TargetT *t = NULL;
int i;
int j;
char *raw = NULL;
in = fopen(self->windowData.filename, "rt");
if (in != NULL) {
// Set all our known archs to FALSE.
for (i=0; i<arrlen(self->targets); i++) {
for (j=0; j<arrlen(self->targets[i]->archs); j++) {
self->targets[i]->archs[j]->selected = FALSE;
}
}
// Load project.
utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker.
while (getline(&line, &len, in) != -1) {
if (strlen(line) > 0) line[strlen(line) - 1] = 0;
c = utilGetToken(line, " ", "\"", "\"");
utilDequote(c);
// Is this a 'source' line?
if (strcasecmp(c, "source") == 0) {
c = utilGetToken(NULL, " ", "\"", "\"");
utilDequote(c);
cwk_path_get_relative(self->windowData.path, c, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
if (__utilFilenameBuffer[0] == 0) {
projectAddToTree(self, c);
} else {
projectAddToTree(self, __utilFilenameBuffer);
}
continue;
}
// Is this a 'target' line?
if (strcasecmp(c, "target") == 0) {
c = utilGetToken(NULL, " ", "\"", "\"");
utilDequote(c);
// See if we know about this target.
for (i=0; i < arrlen(self->targets); i++) {
if (strcasecmp(self->targets[i]->name, c) == 0) {
t = self->targets[i];
// We know this target. Iterate over specified arches and turn them on if known.
while (c != NULL) {
c = utilGetToken(NULL, " ", "\"", "\"");
if (c != NULL) {
utilDequote(c);
for (j=0; j<arrlen(t->archs); j++) {
if (strcasecmp(t->archs[j]->name, c) == 0) {
t->archs[j]->selected = TRUE;
break;
}
}
}
}
break;
}
}
continue;
}
// Is this a 'recipe' line?
if (strcasecmp(c, "recipe") == 0) {
c = utilGetToken(NULL, " ", "\"", "\"");
utilDequote(c);
raw = strdup(c);
c = utilGetToken(NULL, " ", "\"", "\"");
utilDequote(c);
cwk_path_get_relative(self->windowData.path, c, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
if (__utilFilenameBuffer[0] == 0) {
addToRecipeData(self, raw, c);
} else {
addToRecipeData(self, raw, __utilFilenameBuffer);
}
DEL(raw);
}
}
fclose(in);
DEL(line);
utilSetDirty((WindowDataT *)self, FALSE); // Do again - loading text marks us dirty.
} else {
message(MSG_SEVERE, "Unknown error attempting to load %s!", self->windowData.filename);
}
}
EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkTreeIter iter;
ProjectSectionTypeT i;
GtkTreeStore *store;
int x;
int y;
(void)object;
// Is the project dirty?
if (self->windowData.isDirty) {
if (!utilQuestionDialog(self->windowData.window, "New Project", "You have unsaved changes. Create new project anyway?")) {
return;
}
}
// Reset project tree.
gtk_tree_view_set_model(GTK_TREE_VIEW(self->treeProject), NULL);
store = gtk_tree_store_new(1, G_TYPE_STRING);
for (i=0; i<SECTION_COUNT; i++) {
gtk_tree_store_append(store, &iter, NULL);
gtk_tree_store_set(store, &iter, COL_FILENAME, _sectionData[i].name, -1);
}
gtk_tree_view_set_model(GTK_TREE_VIEW(self->treeProject), GTK_TREE_MODEL(store));
g_object_unref(store);
// Select every target and architecture.
for (x=0; x<arrlen(self->targets); x++) {
for (y=0; y<arrlen(self->targets[x]->archs); y++) {
self->targets[x]->archs[y]->selected = TRUE;
}
}
clearRecipeData(self);
// Nuke filename & mark clean.
DEL(self->windowData.filename);
DEL(self->windowData.path);
utilSetDirty(&self->windowData, FALSE);
}
EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
if (utilFileOpen((WindowDataT *)userData, "*.joe", "Project")) {
menuProjectFileNew(object, userData);
loadProject(self);
}
}
EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject));
FILE *out = NULL;
GtkTreeIter iter;
GtkTreeIter child;
char *temp = NULL;
TargetT *t = NULL;
ArchT *a = NULL;
gboolean archWritten = FALSE;
int i;
int j;
// Do we need to save?
if (self->windowData.isDirty == TRUE) {
// Do we have a filename? If not, kick 'em to SaveAs.
if (self->windowData.filename == NULL) {
menuProjectFileSaveAs(object, userData);
return;
}
out = fopen(self->windowData.filename, "wt");
if (out != NULL) {
// Save! Write out header.
fprintf(out, "%s\n", PROJECT_VERSION);
fprintf(out, "------------------------------------------------------------------------------\n");
// Write out file list.
gtk_tree_model_get_iter_first(model, &iter);
do {
if (gtk_tree_model_iter_children(model, &child, &iter)) {
do {
gtk_tree_model_get(model, &child, COL_FILENAME, &temp, -1);
fprintf(out, "source \"%s\"\n", temp);
DEL(temp); // This generates an unknown pointer warning in memwatch. Pretty sure it's bogus.
} while (gtk_tree_model_iter_next(model, &child));
}
} while (gtk_tree_model_iter_next(model, &iter));
// Write out desired targets.
for (i=0; i<arrlen(self->targets); i++) {
t = self->targets[i];
archWritten = FALSE;
for (j=0; j<arrlen(t->archs); j++) {
a = t->archs[j];
if (a->selected == TRUE) {
if (archWritten == FALSE) {
fprintf(out, "target %s", t->name);
archWritten = TRUE;
}
fprintf(out, " %s", a->name);
}
}
if (archWritten == TRUE) fprintf(out, "\n");
}
// Write out any data file recipes.
for (i=0; i<arrlen(self->recipes); i++) {
fprintf(out, "recipe \"%s\" \"%s\"\n", self->recipes[i]->key, self->recipes[i]->value);
}
// Close file.
fclose(out);
// We're clean now.
utilSetDirty((WindowDataT *)self, FALSE);
} else {
message(MSG_SEVERE, "Unknown error attempting to save %s!", self->windowData.filename);
}
}
}
EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData) {
(void)object;
if (utilFileSaveAs((WindowDataT *)userData, "*.joe", "Project")) {
menuProjectFileSave(object, (ProjectDataT *)userData);
}
}
EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
(void)object;
gtk_window_close(GTK_WINDOW(self->windowData.window));
}
EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkWidget *dialog;
GtkFileFilter *filter;
ProjectSectionTypeT section;
char *temp = NULL;
(void)object;
dialog = gtk_file_chooser_dialog_new("Add File to Project",
GTK_WINDOW(self->windowData.window),
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL
);
if (self->windowData.filename != NULL) {
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),self->windowData.path);
}
for (section=0; section<SECTION_COUNT; section++) {
if (_sectionData[section].extension != NULL) {
filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, _sectionData[section].name);
gtk_file_filter_add_pattern(filter, _sectionData[section].extension);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
}
}
filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, _sectionData[SECTION_RAW_DATA].name);
gtk_file_filter_add_pattern(filter, "*");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
if (self->windowData.path != NULL) {
cwk_path_get_relative(self->windowData.path,temp, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
DEL(temp);
temp = strdup(__utilFilenameBuffer);
}
projectAddToTree(self, temp);
utilSetDirty((WindowDataT *)self, TRUE);
DEL(temp);
}
gtk_widget_destroy(dialog);
}
EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->treeProject));
gboolean found = FALSE;
GtkWidget *dialog;
GtkTreeModel *model;
GtkTreeIter iter;
char *name = NULL;
int i;
(void)object;
// Is anything selected?
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
// Are we on a child item?
if (gtk_tree_store_iter_depth(GTK_TREE_STORE(model), &iter) > 0) {
found = TRUE;
}
}
if (found) {
// Delete cook recipe if it exists.
gtk_tree_model_get(model, &iter, COL_FILENAME, &name, -1);
i = findRecipeData(self, name);
if (i >= 0) arrdel(self->recipes, i);
DEL(name);
// Remove item from tree.
gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
utilSetDirty((WindowDataT *)self, TRUE);
} else {
// Provide help.
dialog = gtk_message_dialog_new(GTK_WINDOW(self->windowData.window),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO,
GTK_BUTTONS_CLOSE,
"Please select a file to remove.");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
}
EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkWidget *dialog;
GtkWidget *dialogServerSettings;
GtkWidget *txtHost;
GtkWidget *txtHTTPPort;
GtkWidget *txtSSHPort;
GtkWidget *txtUser;
GtkWidget *txtPassword;
GtkWidget *btnTest;
GtkWidget *btnOkay;
char *widgetNames[] = {
"dialogServerSettings",
"txtHost",
"txtHTTPPort",
"txtSSHPort",
"txtUser",
"txtPassword",
"btnTest",
"btnOkay",
NULL
};
GtkWidget **widgets[] = {
&dialogServerSettings,
&txtHost,
&txtHTTPPort,
&txtSSHPort,
&txtUser,
&txtPassword,
&btnTest,
&btnOkay
};
char temp[6];
int result = 0;
SSHT *ssh = NULL;
char *error = NULL;
(void)object;
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/BuildServer.glade", widgetNames, widgets, self);
while (1) {
gtk_entry_set_text(GTK_ENTRY(txtHost), self->buildHost);
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));
if (result == BUILD_SETTINGS_RESPONSE_TEST || result == GTK_RESPONSE_OK) {
// Save settings to disk.
DEL(self->buildHost);
DEL(self->buildUser);
DEL(self->buildPassword);
self->buildHost = strdup(gtk_entry_get_text(GTK_ENTRY(txtHost)));
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) {
gtk_widget_set_sensitive(btnTest, FALSE);
gtk_widget_set_sensitive(btnOkay, FALSE);
// Run server connection tests - HTTP.
if (updateBuildOptions(self) == FALSE) {
error = utilCreateString("Unable to connect to HTTP port.");
}
// 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 {
// Try to load the build options in case they didn't click TEST.
updateBuildOptions(self);
// Close dialog on OKAY but not TEST.
break;
}
}
}
gtk_widget_destroy(dialogServerSettings);
}
EVENT void menuProjectBuildTargets(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkWidget *dialog;
GtkWidget *contentArea;
GtkWidget *grid;
GtkWidget *widget;
TargetT *t;
ArchT *a;
char *temp;
int i;
int j;
int result;
TargetT **backup;
(void)object;
backup = targetArrayCopy(self->targets);
grid = gtk_grid_new();
gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
gtk_grid_set_row_homogeneous(GTK_GRID(grid), TRUE);
gtk_grid_set_column_spacing(GTK_GRID(grid), 15);
gtk_grid_set_row_spacing(GTK_GRID(grid), 5);
for (i=0; i<arrlen(self->targets); i++) {
t = self->targets[i];
temp = utilCreateString("%s:", t->longName);
widget = gtk_label_new(temp);
DEL(temp);
gtk_label_set_line_wrap(GTK_LABEL(widget), FALSE);
gtk_label_set_xalign(GTK_LABEL(widget), 1.0);
gtk_grid_attach(GTK_GRID(grid), widget, 0, i, 1, 1);
for (j=0; j<arrlen(t->archs); j++) {
a = t->archs[j];
widget = gtk_check_button_new_with_label(a->name);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), a->selected);
g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(buildTargetClicked), a);
gtk_grid_attach(GTK_GRID(grid), widget, j + 1, i, 1, 1);
}
}
dialog = gtk_dialog_new_with_buttons(
"Targets",
GTK_WINDOW(self->windowData.window),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
"_Cancel",
GTK_RESPONSE_CANCEL,
"_OK",
GTK_RESPONSE_OK,
NULL
);
contentArea = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
gtk_container_add(GTK_CONTAINER(contentArea), grid);
gtk_widget_show_all(dialog);
result = gtk_dialog_run(GTK_DIALOG(dialog));
if (result != GTK_RESPONSE_OK) {
// Undo any changes.
targetArrayDelete(&self->targets);
self->targets = targetArrayCopy(backup);
} else {
// Did their selections change?
for (i=0; i<arrlen(self->targets); i++) {
for (j=0; j<arrlen(self->targets[i]->archs); j++) {
if (backup[i]->archs[j]->selected != self->targets[i]->archs[j]->selected) {
utilSetDirty((WindowDataT *)self, TRUE);
break; // Try to speed it up just a bit. :-)
}
}
}
}
targetArrayDelete(&backup);
gtk_widget_destroy(dialog);
}
EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
int i;
char *raw;
char *recipe;
int result;
// Only one cook at a time. Should not be able to happen.
if (_cookingProjectData) {
message(MSG_ERROR, "Cook currently running");
return;
}
// Remember who started the cook.
_cookingProjectData = self;
for (i=0; i<arrlen(self->recipes); i++) {
// Build pathnames.
cwk_path_change_basename(self->windowData.filename, self->recipes[i]->key, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
raw = strdup(__utilFilenameBuffer);
cwk_path_change_basename(self->windowData.filename, self->recipes[i]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
recipe = strdup(__utilFilenameBuffer);
// Run it!
message(MSG_INFO, "Cooking %s", self->recipes[i]->key);
result = compilerRunRecipe(recipe, raw, self->windowData.path, self);
if (result != 0) {
//***TODO*** Not all negative returns are severe.
message(result > 0 ? MSG_ERROR : MSG_SEVERE, "Recipe %s returned %d", self->recipes[i]->value, result);
}
message(MSG_INFO, "Finished Cooking");
}
_cookingProjectData = NULL;
}
EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject));
GtkTreeIter iter;
GtkTreeIter child;
SSHT *ssh;
SFTPT *sftp;
char *temp;
char *filename;
char *pathString;
char *buildStart;
int section;
int i;
int j;
gboolean archPrinted;
FILE *out;
menuProjectBuildCookRecipes(object, userData);
ssh = NEW(SSHT);
ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword);
if (!ssh) {
message(MSG_ERROR, "Unable to connect to SSH port");
return;
}
// Do we have a build package to clean up?
if (sshSFTPFileExists(ssh, REMOTE_BUILD_RESULTS)) {
message(MSG_INFO, "Removing stale build results");
}
// Generate build.start.
buildStart = utilCreateString("%sbuild.start", self->windowData.path);
out = fopen(buildStart, "wt");
if (!out) {
message(MSG_SEVERE, "Unable to write temporary build.start file");
unlink(buildStart); // Just in case.
message(MSG_INFO, "Build canceled");
sshDisconnect(&ssh);
return;
}
// Write what we're building.
fprintf(out, "application\n"); //***TODO*** We don't know how to handle other types yet.
// Write it's title.
fprintf(out, "%s\n", "CRAP - MISSING!"); //***TODO*** Collect and remember project name! Derp!
// Write desired build targets.
for (i=0; i<arrlen(self->targets); i++) {
archPrinted = FALSE;
for (j=0; j<arrlen(self->targets[i]->archs); j++) {
if (self->targets[i]->archs[j]->selected) {
if (!archPrinted) {
fprintf(out, "%s", self->targets[i]->name);
archPrinted = TRUE;
}
fprintf(out, " %s", self->targets[i]->archs[j]->name);
}
}
if (archPrinted) {
fprintf(out, "\n");
}
}
fclose(out);
// Collect file names to send.
gtk_tree_model_get_iter_first(model, &iter);
do {
if (gtk_tree_model_iter_children(model, &child, &iter)) {
do {
gtk_tree_model_get(model, &child, COL_FILENAME, &filename, -1);
pathString = gtk_tree_model_get_string_from_iter(model, &child);
section = atoi(pathString);
if (section != SECTION_RAW_DATA) {
temp = utilCreateString("%s%s", self->windowData.path, filename);
arrput(_sendList, temp);
}
DEL(filename);
} while (gtk_tree_model_iter_next(model, &child));
}
} while (gtk_tree_model_iter_next(model, &iter));
// Add build.start to the end.
arrput(_sendList, buildStart);
// Prime the pump! We only set sshData and userData so sendSFTP can determine this is the first file.
sftp = NEW(SFTPT);
sftp->sshData = ssh;
sftp->userData = self;
sendSFTP(sftp);
}
EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData) {
(void)object;
(void)userData;
gtk_show_uri_on_window(NULL, "https://skunkworks.kangaroopunch.com/skunkworks/joeydev/-/wikis/Project-Editor", GDK_CURRENT_TIME, NULL);
}
gboolean projectAddToTree(ProjectDataT *self, char *filename) {
ProjectSectionTypeT section;
char *temp = NULL;
GtkTreeIter iter;
GtkTreeIter child;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject));
ProjectSectionTypeT foundAt = SECTION_RAW_DATA; // If it isn't a file we know, put it in "Raw Data".
int fileLen;
int extLen;
// Is it long enough?
if (strlen(filename) > 2) {
// Is this already in the tree? This is a pretty brain-dead test.
gtk_tree_model_get_iter_first(model, &iter);
do {
if (gtk_tree_model_iter_children(model, &child, &iter)) {
do {
gtk_tree_model_get(model, &child, COL_FILENAME, &temp, -1);
if (strcmp(temp, filename) == 0) {
DEL(temp); // This generates an unknown pointer warning in memwatch. Pretty sure it's bogus.
return FALSE; // Item exists and was not added.
}
DEL(temp); // This generates an unknown pointer warning in memwatch. Pretty sure it's bogus.
} while (gtk_tree_model_iter_next(model, &child));
}
} while (gtk_tree_model_iter_next(model, &iter));
// Find proper section.
for (section = 0; section < SECTION_COUNT; section++) {
if (_sectionData[section].extension != NULL) {
// Compare last two bytes of filename with extension - it's enough to differentiate them and allows for ".c" and ".h".
fileLen = strlen(filename) - 1;
extLen = strlen(_sectionData[section].extension) - 1;
if (filename[fileLen - 1] == _sectionData[section].extension[extLen - 1] && filename[fileLen] == _sectionData[section].extension[extLen]) {
foundAt = section;
break;
}
}
}
DEL(temp);
fileLen = 0;
utilEnsureBufferSize((unsigned char **)&temp, &fileLen, 4); // fileLen is just a throwaway here.
snprintf(temp, 4, "%d", foundAt);
gtk_tree_model_get_iter_from_string(model, &iter, temp);
gtk_tree_store_append(GTK_TREE_STORE(model), &child, &iter);
gtk_tree_store_set(GTK_TREE_STORE(model), &child, COL_FILENAME, filename, -1);
DEL(temp);
gtk_tree_view_expand_all(GTK_TREE_VIEW(self->treeProject));
}
return TRUE; // Item was added.
}
static void receiveSFTP(SFTPT *sftp) {
ProjectDataT *self = (ProjectDataT *)sftp->userData;
char *temp;
if (sftp->finished) {
if (sftp->success) {
message(MSG_INFO, "Cleaning up remote build");
sshSFTPDelete(sftp->sshData, REMOTE_BUILD_RESULTS);
//***TODO*** Delete any local "results" folder.
message(MSG_INFO, "Unpacking build");
temp = utilCreateString("%s%s", self->windowData.path, LOCAL_BUILD_RESULTS);
utilDecompress(temp, self->windowData.path, decompressBuild, self);
DEL(temp);
} else {
message(MSG_ERROR, "Receiving %s from build server - FAILED", sftp->remoteName);
message(MSG_INFO, "Build canceled");
sshDisconnect(&sftp->sshData);
}
DEL(sftp->localName);
DEL(sftp->remoteName);
DEL(sftp);
}
}
static void saveConfig(ProjectDataT *self) {
char *temp;
FILE *out;
// Save config.
out = fopen(self->configName, "wt");
if (out != NULL) {
fprintf(out, "%s\n", BUILD_SETTINGS_VERSION);
fprintf(out, "------------------------------------------------------------------------------\n");
fprintf(out, "%s\n", self->buildHost);
fprintf(out, "%d\n", self->buildHTTPPort);
fprintf(out, "%d\n", self->buildSSHPort);
fprintf(out, "%s\n", self->buildUser);
temp = utilObfuscateASCII(self->buildPassword);
fprintf(out, "%s\n", temp);
DEL(temp);
fclose(out);
} else {
message(MSG_SEVERE, "Unknown error attempting to save build configuration!");
}
}
EVENT void treeProjectRowActivated(GtkTreeView *treeView, GtkTreePath *path, GtkTreeViewColumn *column, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject));
GtkTreeIter iter;
char *name = NULL;
char *pathString = NULL;
char *filename = NULL;
int section;
pathString = gtk_tree_path_to_string(path);
if (strstr(pathString, ":") != NULL) {
gtk_tree_model_get_iter_from_string(model, &iter, pathString);
gtk_tree_model_get(model, &iter, COL_FILENAME, &name, -1);
section = atoi(pathString);
debug("Double click! [%d] [%s] [%s]\n", section, pathString, name);
if (section == SECTION_CODE || section == SECTION_HEADER) {
filename = utilCreateString("%s%s", self->windowData.path, name);
// Launch code editor.
winEditorCreate(filename);
DEL(filename);
}
if (section == SECTION_RAW_DATA) {
// Display cook dialog.
dialogCookOptions(name, self);
}
DEL(name);
}
DEL(pathString);
}
static gboolean updateBuildOptions(ProjectDataT *self) {
char *test = NULL;
char *name = NULL;
char *url = NULL;
gboolean result = FALSE;
FILE *in = NULL;
char *line = NULL;
size_t len = 0;
char *c = NULL;
TargetT *t = NULL;
ArchT *a = NULL;
TargetT **backup = NULL;
int i;
int j;
int x;
int y;
// This updates the build options file without clobbering one if it already exists and we fail to get a new one.
test = utilCreateString("%s%cjoeydev%cjoeydev.temp", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR);
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);
httpDownload(httpWaitForDownloadCallback, test, url);
if (httpWaitForDownload() == TRUE) {
unlink(name);
rename(test, name);
result = TRUE;
}
//***TODO*** Grab the latest joey.h
// Unload current target listing.
backup = targetArrayCopy(self->targets);
targetArrayDelete(&self->targets);
// If there's a list of targets on the disk, load them.
if (utilFileExists(name) == TRUE) {
in = fopen(name, "rt");
if (in != NULL) {
utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker.
while (getline(&line, &len, in) != -1) {
if (strlen(line) > 0) line[strlen(line) - 1] = 0;
c = utilGetToken(line, " ", "\"", "\"");
utilDequote(c);
// Is this a 'target' line?
if (strcasecmp(c, "target") == 0) {
t = NEW(TargetT);
// Short name.
c = utilGetToken(NULL, " ", "\"", "\"");
utilDequote(c);
t->name = strdup(c);
// Long name.
c = utilGetToken(NULL, " ", "\"", "\"");
utilDequote(c);
t->longName = strdup(c);
// Architectures.
do {
c = utilGetToken(NULL, " ", "\"", "\"");
if (c != NULL) {
utilDequote(c);
a = NEW(ArchT);
a->name = strdup(c);
a->selected = FALSE;
arrput(t->archs, a);
}
} while (c != NULL);
arrput(self->targets, t);
}
}
fclose(in);
DEL(line);
}
}
// Merge any previously checked items into the new list.
for (i=0; i<arrlen(backup); i++) {
// Is this target in the new list?
for (j=0; j<arrlen(self->targets); j++) {
if (strcasecmp(backup[i]->name, self->targets[j]->name) == 0) {
// This is our target. Iterate over archs and enable any we had enabled before.
for (x=0; x<arrlen(backup[i]->archs); x++) {
for (y=0; x<arrlen(self->targets[j]->archs); y++) {
if (strcasecmp(backup[i]->archs[x]->name, self->targets[j]->archs[y]->name) == 0) {
self->targets[j]->archs[y]->selected = backup[i]->archs[x]->selected;
break;
}
}
}
break;
}
}
}
targetArrayDelete(&backup);
DEL(url);
DEL(name);
DEL(test);
return result;
}
static void sendSFTP(SFTPT *sftp) {
ProjectDataT *self = (ProjectDataT *)sftp->userData;
SFTPT *last;
SFTPT *next;
char *target;
char *name;
last = sftp;
// If source and target are missing, this is the first file - start sending.
// Or, did the current transfer succeed? If so, send next file.
if ((sftp->localName == NULL && sftp->remoteName == NULL) || (sftp->finished && sftp->success)) {
if (sftp->localName == NULL && sftp->remoteName == NULL) {
message(MSG_INFO, "Building %s", self->windowData.filename);
}
// Are there more files to send?
if (arrlen(_sendList) > 0) {
// Send next entry in the file list.
name = utilFileBasename(_sendList[0]);
target = utilCreateString("build/%s", name);
message(MSG_INFO, "Sending %s to build server", name);
sftp = sshSFTPSend(last->sshData, _sendList[0], target, sendSFTP);
sftp->userData = self;
DEL(target);
DEL(name);
arrdel(_sendList, 0);
} else {
// Finished!
message(MSG_INFO, "Waiting for build to complete");
// "last" will be deleted by sshSFTPUpdate so copy what we need to "next".
next = NEW(SFTPT);
next->sshData = last->sshData;
next->userData = last->userData;
g_timeout_add_seconds(5, waitForBuild, next);
}
}
// Do we have a transfer? Did the transfer fail?
if ((!sftp) || (sftp->finished && !sftp->success)) {
if (sftp) {
message(MSG_ERROR, "Sending %s to build server - FAILED", sftp->remoteName);
}
message(MSG_INFO, "Build canceled");
sshDisconnect(&last->sshData);
// Clear any remaining transfers.
while (arrlen(_sendList) > 0) {
arrdel(_sendList, 0);
}
ARRFREE(_sendList);
}
// Do we need to free this?
if (last->finished) {
// Do not delete last->sshData as we continue to need it until everything is finished.
DEL(last->localName);
DEL(last->remoteName);
DEL(last);
}
}
static TargetT **targetArrayCopy(TargetT **targets) {
int i;
int j;
TargetT *t = NULL;
ArchT *a = NULL;
TargetT **backup = NULL;
for (i=0; i<arrlen(targets); i++) {
t = NEW(TargetT);
t->name = strdup(targets[i]->name);
t->longName = strdup(targets[i]->longName);
for (j=0; j<arrlen(targets[i]->archs); j++) {
a = NEW(ArchT);
a->name = strdup(targets[i]->archs[j]->name);
a->selected = targets[i]->archs[j]->selected;
arrput(t->archs, a);
}
arrput(backup, t);
}
return backup;
}
static void targetArrayDelete(TargetT ***array) {
TargetT **targets = (TargetT **)*array;
TargetT *t = NULL;
ArchT *a = NULL;
while (arrlen(targets) > 0) {
t = targets[0];
while (arrlen(t->archs) > 0) {
a = t->archs[0];
DEL(a->name);
DEL(a);
arrdel(t->archs, 0);
}
//arrfree(t->archs);
DEL(t->name);
DEL(t->longName);
DEL(t);
arrdel(targets, 0);
}
//arrfree(targets);
}
static gboolean waitForBuild(gpointer userData) {
SFTPT *last = (SFTPT *)userData;
ProjectDataT *self = (ProjectDataT *)last->userData;
SFTPT *sftp;
char *temp;
if (sshSFTPFileExists(last->sshData, REMOTE_BUILD_RESULTS)) {
// Build complete! Next step!
//***TODO*** Delete any local build archive.
message(MSG_INFO, "Retrieving build results");
temp = utilCreateString("%s%s", self->windowData.path, LOCAL_BUILD_RESULTS);
sftp = sshSFTPReceive(last->sshData, REMOTE_BUILD_RESULTS, temp, receiveSFTP);
DEL(temp);
if (!sftp) {
message(MSG_ERROR, "Receiving build from build server - FAILED");
message(MSG_INFO, "Build canceled");
sshDisconnect(&last->sshData);
} else {
sftp->userData = last->userData;
}
// Finally finished with "last"
DEL(last->localName);
DEL(last->remoteName);
DEL(last);
return G_SOURCE_REMOVE;
}
// Keep waiting.
//***TODO*** Add a timeout of some kind here and sshDisconnect.
return G_SOURCE_CONTINUE;
}
EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData) {
// userData is not reliable due to menuVectorFileClose and util indirectly calling us.
ProjectDataT *self = (ProjectDataT *)utilGetWindowData(object);
(void)userData;
if (self->windowData.isDirty == TRUE) {
if (utilQuestionDialog(self->windowData.window, "Exit", "You have unsaved changes. Exit?")) {
winProjectDelete(self);
return FALSE;
}
return TRUE;
}
winProjectDelete(self);
return FALSE;
}
void winProjectCreate(void) {
ProjectDataT *self;
char *widgetNames[] = {
"winProject",
"treeProject",
NULL
};
GtkWidget **widgets[] = {
NULL,
NULL
};
GtkTreeViewColumn *col;
GtkCellRenderer *renderer;
// Set up instance data.
self = NEW(ProjectDataT);
self->windowData.closeWindow = winProjectClose;
// Load widgets from XML.
widgets[0] = &self->windowData.window;
widgets[1] = &self->treeProject;
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Project.glade", widgetNames, widgets, self);
// Register window.
utilWindowRegister(self);
// Build tree.
col = gtk_tree_view_column_new();
gtk_tree_view_append_column(GTK_TREE_VIEW(self->treeProject), col);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(col, renderer, TRUE);
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FILENAME);
// Reset tree.
menuProjectFileNew(self->windowData.window, self);
// Load server settings.
loadConfig(self);
// Show window.
gtk_widget_show_all(self->windowData.window);
// Attempt to load build settings from build server.
updateBuildOptions(self);
//***DEBUG***
self->windowData.filename = strdup("/home/scott/joeyapps/warehouse/Warehouse.joe");
self->windowData.path = strdup("/home/scott/joeyapps/warehouse/");
loadProject(self);
}
static void winProjectDelete(gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
utilWindowUnRegister(userData);
targetArrayDelete(&self->targets);
clearRecipeData(self);
DEL(self->buildHost);
DEL(self->buildUser);
DEL(self->buildPassword);
DEL(self->configName);
DEL(self);
}
#pragma clang diagnostic pop