1535 lines
45 KiB
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
|