/*
* Copyright (c) 2009-2015 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Interface to the library of device packages.
*/
#include "core.h"
#include
#include
#include
#include
/* XXX TODO use agar 1.6 AG_User interface. */
#if defined(HAVE_GETPWUID) && defined(HAVE_GETUID)
#include
#endif
AG_Object *esPackageLibrary = NULL; /* Package library */
char **esPackageLibraryDirs;
int esPackageLibraryDirCount;
/* Load a package from file. */
static int
LoadPackageFile(const char *path, AG_Object *objParent)
{
AG_ObjectHeader oh;
AG_DataSource *ds;
AG_ObjectClass *cl;
AG_Object *obj;
/* Fetch class information from the object file's header. */
if ((ds = AG_OpenFile(path, "rb")) == NULL) {
return (-1);
}
if (AG_ObjectReadHeader(ds, &oh) == -1) {
AG_CloseFile(ds);
return (-1);
}
AG_CloseFile(ds);
/*
* Fetch class information for the model contained. If dynamic library
* modules are required, they get linked at this stage.
*/
if ((cl = AG_LoadClass(oh.cs.hier)) == NULL)
return (-1);
/* Create the package model instance and load its contents. */
if ((obj = AG_ObjectNew(objParent, NULL, cl)) == NULL) {
return (-1);
}
if (AG_ObjectLoadFromFile(obj, path) == -1) {
AG_SetError("%s: %s", path, AG_GetError());
AG_ObjectDelete(obj);
return (-1);
}
AG_SetString(obj, "archive-path", path);
AG_ObjectSetNameS(obj, AG_ShortFilename(path));
return (0);
}
/* Load package model files in specified directory (and subdirectories). */
static int
LoadPackagesFromDisk(const char *modelDir, AG_Object *objParent)
{
char path[AG_PATHNAME_MAX];
AG_Object *objFolder;
AG_Dir *dir;
int j;
#if 0
Debug(NULL, "Scanning model directory: %s (under \"%s\")\n",
modelDir, objParent->name);
#endif
if ((dir = AG_OpenDir(modelDir)) == NULL) {
return (-1);
}
for (j = 0; j < dir->nents; j++) {
char *file = dir->ents[j];
AG_FileInfo fileInfo;
char *s;
if (file[0] == '.')
continue;
Strlcpy(path, modelDir, sizeof(path));
Strlcat(path, PATHSEP, sizeof(path));
Strlcat(path, file, sizeof(path));
if (AG_GetFileInfo(path, &fileInfo) == -1) {
AG_Verbose("Ignoring: %s (%s)\n", path, AG_GetError());
continue;
}
if (fileInfo.type == AG_FILE_DIRECTORY) {
if ((objFolder = AG_ObjectFindChild(objParent, file))
== NULL) {
/* Should not happen */
AG_Verbose("Ignoring folder: %s\n", file);
continue;
}
if (LoadPackagesFromDisk(path, objFolder) == -1) {
AG_Verbose("Ignoring model dir: %s (%s)\n",
path, AG_GetError());
}
continue;
}
if ((s = strstr(file, ".em")) == NULL || s[3] != '\0') {
continue;
}
if (LoadPackageFile(path, objParent) == -1)
AG_Verbose("Ignoring model file: %s (%s)", path,
AG_GetError());
}
AG_CloseDir(dir);
return (0);
}
/* Load package model "folder" objects from disk. */
static int
LoadFoldersFromDisk(const char *modelDir, AG_Object *objParent)
{
char path[AG_PATHNAME_MAX];
AG_Dir *dir;
AG_Object *objFolder;
int j;
#if 0
Debug(NULL, "Scanning for folders in: %s (under \"%s\")\n",
modelDir, objParent->name);
#endif
if ((dir = AG_OpenDir(modelDir)) == NULL) {
return (-1);
}
for (j = 0; j < dir->nents; j++) {
char *file = dir->ents[j];
AG_FileInfo fileInfo;
if (file[0] == '.')
continue;
Strlcpy(path, modelDir, sizeof(path));
Strlcat(path, PATHSEP, sizeof(path));
Strlcat(path, file, sizeof(path));
if (AG_GetFileInfo(path, &fileInfo) == -1) {
AG_Verbose("Ignoring: %s (%s)\n", path, AG_GetError());
continue;
}
if (fileInfo.type != AG_FILE_DIRECTORY) {
continue;
}
if ((objFolder = AG_ObjectNew(objParent, file, &agObjectClass))
== NULL) {
AG_Verbose("Ignoring folder: %s (%s)\n", path,
AG_GetError());
continue;
}
if (LoadFoldersFromDisk(path, objFolder) == -1) {
AG_Verbose("Ignoring folder: %s (%s)\n", path,
AG_GetError());
}
}
AG_CloseDir(dir);
return (0);
}
/* Load the model library from disk. */
int
ES_PackageLibraryLoad(void)
{
int i;
if (esPackageLibrary != NULL) {
if (AG_ObjectInUse(esPackageLibrary)) {
/* XXX */
AG_Verbose("Not freeing model library; model(s) are"
"currently in use");
} else {
// AG_ObjectDestroy(esPackageLibrary);
}
}
/* Create the folder structure. */
esPackageLibrary = AG_ObjectNew(NULL, "Package Library", &agObjectClass);
for (i = 0; i < esPackageLibraryDirCount; i++) {
if (LoadFoldersFromDisk(esPackageLibraryDirs[i], esPackageLibrary) == -1)
AG_Verbose("Skipping: %s\n", AG_GetError());
}
/* Load the model files. */
for (i = 0; i < esPackageLibraryDirCount; i++) {
if (LoadPackagesFromDisk(esPackageLibraryDirs[i],
esPackageLibrary) == -1)
AG_Verbose("Skipping: %s\n", AG_GetError());
}
return (0);
}
/* Register a search path for model files. */
void
ES_PackageLibraryRegisterDir(const char *path)
{
char *s, *p;
esPackageLibraryDirs = Realloc(esPackageLibraryDirs,
(esPackageLibraryDirCount+1)*sizeof(char *));
esPackageLibraryDirs[esPackageLibraryDirCount++] = s = Strdup(path);
if (*(p = &s[strlen(s)-1]) == PATHSEPCHAR)
*p = '\0';
}
/* Unregister a model directory. */
void
ES_PackageLibraryUnregisterDir(const char *path)
{
int i;
for (i = 0; i < esPackageLibraryDirCount; i++) {
if (strcmp(esPackageLibraryDirs[i], path) == 0)
break;
}
if (i == esPackageLibraryDirCount) {
return;
}
free(esPackageLibraryDirs[i]);
if (i < esPackageLibraryDirCount-1) {
memmove(&esPackageLibraryDirs[i], &esPackageLibraryDirs[i+1],
(esPackageLibraryDirCount-i-1)*sizeof(char *));
}
esPackageLibraryDirCount--;
}
/* Initialize the model library. */
void
ES_PackageLibraryInit(void)
{
char path[AG_PATHNAME_MAX];
esPackageLibrary = NULL;
esPackageLibraryDirs = NULL;
esPackageLibraryDirCount = 0;
Strlcpy(path, DATADIR, sizeof(path));
Strlcat(path, "/Packages", sizeof(path));
printf("Registering package library dir: %s\n", path);
ES_PackageLibraryRegisterDir(path);
/* XXX TODO use agar 1.6 AG_User interface. */
#if defined(HAVE_GETPWUID) && defined(HAVE_GETUID)
{
struct passwd *pwd = getpwuid(getuid());
Strlcpy(path, pwd->pw_dir, sizeof(path));
Strlcat(path, PATHSEP, sizeof(path));
Strlcat(path, ".edacious", sizeof(path));
Strlcat(path, PATHSEP, sizeof(path));
Strlcat(path, "Packages", sizeof(path));
if (!AG_FileExists(path)) {
if (AG_MkPath(path) == -1) {
AG_Verbose("Failed to create %s (%s)\n", path,
AG_GetError());
}
}
ES_PackageLibraryRegisterDir(path);
}
#endif
if (ES_PackageLibraryLoad() == -1)
AG_Verbose("Loading library: %s", AG_GetError());
}
/* Destroy the model library. */
void
ES_PackageLibraryDestroy(void)
{
AG_ObjectDestroy(esPackageLibrary);
esPackageLibrary = NULL;
Free(esPackageLibraryDirs);
}
static AG_TlistItem *
FindPackages(AG_Tlist *tl, AG_Object *pob, int depth)
{
AG_Object *chld;
AG_TlistItem *it;
it = AG_TlistAddPtr(tl,
AG_OfClass(pob, "ES_Layout:ES_Package:*") ?
esIconComponent.s : agIconDirectory.s,
pob->name, pob);
it->depth = depth;
if (!TAILQ_EMPTY(&pob->children)) {
it->flags |= AG_TLIST_HAS_CHILDREN;
}
if ((it->flags & AG_TLIST_HAS_CHILDREN) &&
AG_TlistVisibleChildren(tl, it)) {
TAILQ_FOREACH(chld, &pob->children, cobjs)
FindPackages(tl, chld, depth+1);
}
return (it);
}
/* Generate a Tlist tree for the package library. */
static void
PollLibrary(AG_Event *event)
{
AG_Tlist *tl = AG_TLIST_SELF();
AG_TlistItem *ti;
AG_TlistClear(tl);
AG_LockVFS(esPackageLibrary);
ti = FindPackages(tl, OBJECT(esPackageLibrary), 0);
ti->flags |= AG_TLIST_ITEM_EXPANDED;
AG_UnlockVFS(esPackageLibrary);
AG_TlistRestore(tl);
}
static void
RefreshLibrary(AG_Event *event)
{
AG_Tlist *tl = AG_TLIST_PTR(1);
if (ES_PackageLibraryLoad() == -1) {
AG_TextMsgFromError();
return;
}
AG_TlistRefresh(tl);
}
static void
EditPackage(AG_Event *event)
{
AG_Object *obj = AG_OBJECT_PTR(1);
(void)ES_OpenObject(obj);
}
static void
SavePackage(AG_Event *event)
{
AG_Object *obj = AG_OBJECT_PTR(1);
if (AG_ObjectSave(obj) == -1) {
AG_TextMsgFromError();
return;
}
AG_TextTmsg(AG_MSG_INFO, 1250,
_("Successfully saved package model to %s"),
AG_ShortFilename(AG_GetStringP(obj,"archive-path")));
}
static void
SavePackageAs(AG_Event *event)
{
AG_Object *obj = AG_OBJECT_PTR(1);
const char *path = AG_STRING(2);
if (AG_ObjectSaveToFile(obj, path) == -1) {
AG_TextMsgFromError();
return;
}
AG_SetString(obj, "archive-path", path);
AG_ObjectSetNameS(obj, AG_ShortFilename(path));
AG_TextTmsg(AG_MSG_INFO, 1250,
_("Successfully saved package model to %s"),
AG_ShortFilename(path));
}
static void
SavePackageAsDlg(AG_Event *event)
{
AG_Object *obj = AG_OBJECT_PTR(1);
AG_Window *win;
AG_FileDlg *fd;
if ((win = AG_WindowNew(0)) == NULL) {
return;
}
AG_WindowSetCaption(win, _("Save %s as..."), obj->name);
fd = AG_FileDlgNewMRU(win, "edacious.mru.packages",
AG_FILEDLG_SAVE | AG_FILEDLG_CLOSEWIN | AG_FILEDLG_EXPAND);
AG_FileDlgSetOptionContainer(fd, AG_BoxNewVert(win, AG_BOX_HFILL));
AG_FileDlgAddType(fd, _("Edacious device package"), "*.edp",
SavePackageAs, "%p", obj);
AG_WindowShow(win);
}
static void
PackageMenu(AG_Event *event)
{
AG_Tlist *tl = AG_TLIST_SELF();
AG_Object *obj = AG_TlistSelectedItemPtr(tl);
AG_PopupMenu *pm;
if (!AG_OfClass(obj, "ES_Layout:ES_Package:*"))
return;
if ((pm = AG_PopupNew(tl)) != NULL)
return;
AG_MenuAction(pm->root, _("Edit package model..."), esIconComponent.s,
EditPackage, "%p", obj);
AG_MenuSeparator(pm->root);
AG_MenuAction(pm->root, _("Save"), agIconSave.s, SavePackage, "%p,%s", obj, "");
AG_MenuAction(pm->root, _("Save as..."), agIconSave.s, SavePackageAsDlg, "%p", obj);
AG_PopupShow(pm);
}
ES_PackageLibraryEditor *
ES_PackageLibraryEditorNew(void *parent, VG_View *vv, ES_Layout *lo,
Uint flags, AG_EventFn insFn, const char *fmt, ...)
{
AG_Box *box;
AG_Button *btn;
AG_Tlist *tl;
AG_Event ev;
AG_Event *evSel;
box = AG_BoxNewVert(parent, AG_BOX_EXPAND);
tl = AG_TlistNewPolled(box, AG_TLIST_TREE | AG_TLIST_EXPAND,
PollLibrary, NULL);
AG_WidgetSetFocusable(tl, 0);
AG_TlistSetRefresh(tl, -1);
AG_TlistSizeHint(tl, "XXXXXXXXXXXXXXXXXXX", 10);
AG_TlistSetPopupFn(tl, PackageMenu, NULL);
evSel = AG_SetEvent(tl, "tlist-dblclick", insFn, NULL);
if (fmt) {
va_list ap;
va_start(ap, fmt);
AG_EventGetArgs(evSel, fmt, ap);
va_end(ap);
}
btn = AG_ButtonNewFn(box, AG_BUTTON_HFILL, _("Refresh list"),
RefreshLibrary, "%p", tl);
AG_WidgetSetFocusable(btn, 0);
AG_EventArgs(&ev, "%p", tl);
RefreshLibrary(&ev);
return (ES_PackageLibraryEditor *)box;
}