/ .. / / -> download
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include "s1kd_tools.h"

#define PROG_NAME "s1kd-sns"
#define VERSION "1.8.0"

#define ERR_PREFIX PROG_NAME ": ERROR: "

#define E_ENCODING_ERROR ERR_PREFIX "Error encoding path name.\n"

#define EXIT_ENCODING_ERROR 1
#define EXIT_OS_ERROR 2
#define EXIT_NO_BREX 3

#define DEFAULT_SNS_DNAME "SNS"

static int hlink(const char *path, const char *fname)
{
	#ifdef _WIN32
		return CreateHardLink(fname, path, 0);
	#else
		return link(path, fname);
	#endif
}

static int slink(const char *path, const char *fname)
{
	#ifdef _WIN32
		return CreateSymbolicLink(fname, path, 0);
	#else
		return symlink(path, fname);
	#endif
}

/* The type of link to use, hard or soft (symbolic). */
static int (*linkfn)(const char *path, const char *fname) = hlink;

/* Title SNS directories using only the SNS code, not including the SNS title. */
static bool only_numb = false;

static void change_dir(const char *dir)
{
	if (chdir(dir) != 0) {
		fprintf(stderr, ERR_PREFIX "Cannot change directory to %s: %s\n", dir, strerror(errno));
		exit(EXIT_OS_ERROR);
	}
}

static void rename_dir(const char *old, const char *new)
{
	if (rename(old, new) != 0) {
		fprintf(stderr, ERR_PREFIX "Cannot rename directory %s to %s: %s\n", old, new, strerror(errno));
		exit(EXIT_OS_ERROR);
	}
}

static void get_current_dir(char *buf, size_t size)
{
	if (!getcwd(buf, size)) {
		fprintf(stderr, ERR_PREFIX "Cannot get current directory: %s\n", strerror(errno));
		exit(EXIT_OS_ERROR);
	}
}

/* Indent printed SNS entry to a specified level. */
static void indent(int level)
{
	int i;
	for (i = 0; i < level * 4; ++i) putchar(' ');
}

/* Print the SNS. */
static void print_sns(xmlNodePtr node, int level)
{
	xmlNodePtr cur;

	char *content;

	for (cur = node->children; cur; cur = cur->next) {
		if (xmlStrcmp(cur->name, BAD_CAST "snsCode") == 0) {
			content = (char *) xmlNodeGetContent(cur);
			indent(level);
			printf("%s", content);
			xmlFree(content);
		} else if (xmlStrcmp(cur->name, BAD_CAST "snsTitle") == 0) {
			content = (char *) xmlNodeGetContent(cur);
			printf(" - %s\n", content);
			xmlFree(content);
		} else if (cur->type == XML_ELEMENT_NODE) {
			print_sns(cur, level + 1);
		}
	}
}

/* Replace characters that cannot be used in directory names. */
static void cleanstr(char *s)
{
	int i;
	for (i = 0; s[i]; ++i) {
		switch(s[i]) {
			case '/':
			#ifdef _WIN32
			case '<':
			case '>':
			case ':':
			case '"':
			case '\\':
			case '|':
			case '?':
			case '*':
			#endif
				s[i] = ' ';
		}
	}
}

/* Create a directory if it does not exist. */
static void makedir(const char *path)
{
	if (access(path, F_OK) == -1) {
		#ifdef _WIN32
			mkdir(path);
		#else
			mkdir(path, S_IRWXU);
		#endif
	}
}

/* Create the directory structure for the SNS. */
static void setup_sns(xmlNodePtr node, const char *snsdname)
{
	xmlNodePtr cur;
	char *content;
	char code[256];

	for (cur = node->children; cur; cur = cur->next) {
		if (xmlStrcmp(cur->name, BAD_CAST "snsCode") == 0) {
			content = (char *) xmlNodeGetContent(cur);

			strcpy(code, content);

			xmlFree(content);

			makedir(code);

			change_dir(code);
		} else if (xmlStrcmp(cur->name, BAD_CAST "snsTitle") == 0) {
			char oldname[PATH_MAX], newname[PATH_MAX];

			if (only_numb) continue;

			if (snprintf(oldname, PATH_MAX, "%s", code) < 0) {
				fprintf(stderr, E_ENCODING_ERROR);
				exit(EXIT_ENCODING_ERROR);
			}

			content = (char *) xmlNodeGetContent(cur);
			cleanstr(content);

			if (snprintf(newname, PATH_MAX, "%s - %s", code, content) < 0) {
				fprintf(stderr, E_ENCODING_ERROR);
				exit(EXIT_ENCODING_ERROR);
			}

			xmlFree(content);

			change_dir("..");
			rename_dir(oldname, newname);
			change_dir(newname);
		} else if (cur->type == XML_ELEMENT_NODE) {
			setup_sns(cur, snsdname);
		}
	}

	change_dir("..");
}

/* Tests if the given file is a data module. */
static int is_dmodule(const char *fname)
{
	return (strncmp(fname, "DMC-", 4) == 0 || strncmp(fname, "DME-", 4) == 0) &&
	       strncasecmp(fname + (strlen(fname) - 4), ".XML", 4) == 0;
}

/* Convenient structure for DM code properties. */
struct dm_code {
	char model_ident_code[15];
	char system_diff_code[5];
	char system_code[4];
	char sub_system_code[2];
	char sub_sub_system_code[2];
	char assy_code[5];
	char disassy_code[3];
	char disassy_code_variant[4];
	char info_code[4];
	char info_code_variant[2];
	char item_location_code[2];
	char learn_code[4];
	char learn_event_code[2];
};

/* Read a DM code from a given string. */
static int parse_dmcode(struct dm_code *code, const char *str)
{
	int c;

	c = sscanf(str, "%*[^-]-%14[^-]-%4[^-]-%3[^-]-%1s%1s-%4[^-]-%2s%3[^-]-%3s%1s-%1s-%3s%1s",
		code->model_ident_code,
		code->system_diff_code,
		code->system_code,
		code->sub_system_code,
		code->sub_sub_system_code,
		code->assy_code,
		code->disassy_code,
		code->disassy_code_variant,
		code->info_code,
		code->info_code_variant,
		code->item_location_code,
		code->learn_code,
		code->learn_event_code);

	if (c != 11 && c != 13)
		return 1;
		
	return 0;
}

/* Test if the SNS directory exists for a specified code. */
static int sns_exists(const char *code, char *dname)
{
	DIR *dir;
	struct dirent *cur;
	int exists = 0;

	dir = opendir(".");

	while ((cur = readdir(dir))) {
		if (strncmp(code, cur->d_name, strlen(code)) == 0) {
			exists = 1;
			strcpy(dname, cur->d_name);
			break;
		}
	}

	closedir(dir);

	return exists;
}

/* Place a link to a DM file in to the proper place in the SNS directory hierarchy. */
static void placedm(const char *fname, struct dm_code *code, const char *snsdname, const char *srcdname)
{
	char path[PATH_MAX], dname[PATH_MAX], orig[PATH_MAX];
	bool link = true;

	get_current_dir(orig, PATH_MAX);

	strcpy(path, srcdname);
	strcat(path, "/");

	change_dir(snsdname);

	if (sns_exists(code->system_code, dname)) {
		change_dir(dname);
		if (sns_exists(code->sub_system_code, dname)) {
			change_dir(dname);
			if (sns_exists(code->sub_sub_system_code, dname)) {
				change_dir(dname);
				if (sns_exists(code->assy_code, dname)) {
					change_dir(dname);
				}
			}
		}
	}
	
	strcat(path, fname);

	if (access(fname, F_OK) != -1) {
		char d[PATH_MAX];

		get_current_dir(d, PATH_MAX);

		if (strcmp(d, srcdname) != 0) {
			unlink(fname);
		} else {
			link = false;
		}
	}

	if (link && linkfn(path, fname) != 0) {
		fprintf(stderr, ERR_PREFIX "%s: %s => %s\n", strerror(errno), path, fname);
		exit(EXIT_OS_ERROR);
	}

	change_dir(orig);
}

/* Resort DMs in to the SNS directory hierarchy. */
static void sort_sns(const char *snsdname, const char *srcdname)
{
	DIR *dir;
	struct dirent *cur;

	dir = opendir(srcdname);

	while ((cur = readdir(dir))) {
		if (is_dmodule(cur->d_name)) {
			struct dm_code code;

			if (parse_dmcode(&code, cur->d_name) == 0) {
				placedm(cur->d_name, &code, snsdname, srcdname);
			}
		}

	}

	closedir(dir);
}

/* Print or setup the SNS directory structure for a given BREX containing SNS rules. */
static void print_or_setup_sns(const char *brex_fname, bool printsns, const char *snsdname, const char *srcdname)
{
	xmlDocPtr brex;
	xmlXPathContextPtr ctxt;
	xmlXPathObjectPtr results;

	if (!(brex = read_xml_doc(brex_fname))) {
		fprintf(stderr, ERR_PREFIX "Could not read BREX data module: %s\n", brex_fname);
		exit(EXIT_NO_BREX);
	}

	ctxt = xmlXPathNewContext(brex);

	results = xmlXPathEvalExpression(BAD_CAST "//snsRules/snsDescr", ctxt);

	if (!xmlXPathNodeSetIsEmpty(results->nodesetval)) {
		xmlNodePtr sns_descr;

		sns_descr = results->nodesetval->nodeTab[0];

		if (printsns) {
			print_sns(sns_descr, -1);
		} else if (access(snsdname, F_OK) == -1) {
			char cwd[PATH_MAX];

			get_current_dir(cwd, PATH_MAX);

			makedir(snsdname);
			change_dir(snsdname);

			setup_sns(sns_descr, snsdname);

			change_dir(cwd);
		}

		if (!printsns) {
			sort_sns(snsdname, srcdname);
		}
	}

	xmlXPathFreeObject(results);
	xmlXPathFreeContext(ctxt);

	xmlFreeDoc(brex);
}

static void show_help(void)
{
	puts("Usage: " PROG_NAME " [-D <dir>] [-d <dir>] [-cmnpsh?] [<BREX> ...]");
	puts("");
	puts("Options:");
	puts("  -c, --copy          Copy files instead of linking.");
	puts("  -D, --srcdir <dir>  Directory where DMs are stored. Default is current directory.");
	puts("  -d, --outdir <dir>  Directory to organize DMs in to. Default is \"" DEFAULT_SNS_DNAME "\"");
	puts("  -h, -?, --help      Show usage message.");
	puts("  -m, --move          Move files instead of linking.");
	puts("  -n, --only-code     Only use the SNS code to name directories.");
	puts("  -p, --print         Print SNS instead of organizing.");
	puts("  -s, --symlink       Use symbolic links.");
	puts("  --version           Show version information.");
	puts("  <BREX>              BREX data module to read SNS structure from.");
	LIBXML2_PARSE_LONGOPT_HELP
}

static void show_version(void)
{
	printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION);
	printf("Using libxml %s\n", xmlParserVersion);
}

int main(int argc, char **argv)
{
	int i;
	bool printsns = false;
	char *snsdname = NULL;
	char *srcdname = NULL;

	const char *sopts = "cD:d:mnpsh?";
	struct option lopts[] = {
		{"version"  , no_argument      , 0, 0},
		{"help"     , no_argument      , 0, 'h'},
		{"copy"     , no_argument      , 0, 'c'},
		{"srcdir"   , required_argument, 0, 'D'},
		{"outdir"   , required_argument, 0, 'd'},
		{"move"     , no_argument      , 0, 'm'},
		{"only-code", no_argument      , 0, 'n'},
		{"print"    , no_argument      , 0, 'p'},
		{"symlink"  , no_argument      , 0, 's'},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

	srcdname = malloc(PATH_MAX + 1);
	get_current_dir(srcdname, PATH_MAX);

	while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
		switch (i) {
			case 0:
				if (strcmp(lopts[loptind].name, "version") == 0) {
					show_version();
					return 0;
				}
				LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
				break;
			case 'c': linkfn = copy; break;
			case 'D': real_path(optarg, srcdname); break;
			case 'd': snsdname = strdup(optarg); break;
			case 's': linkfn = slink; break;
			case 'm': linkfn = rename; break;
			case 'n': only_numb = true; break;
			case 'p': printsns = true; break;
			case 'h':
			case '?': show_help(); return 0;
		}
	}

	if (!snsdname) {
		snsdname = strdup(DEFAULT_SNS_DNAME);
	}

	if (optind < argc) {
		for (i = optind; i < argc; ++i) {
			print_or_setup_sns(argv[i], printsns, snsdname, srcdname);
		}
	} else {
		print_or_setup_sns("-", printsns, snsdname, srcdname);
	}

	free(snsdname);
	free(srcdname);

	xmlCleanupParser();

	return 0;
}


/ gopher://khzae.net/0/s1kd/s1kd-tools/src/tools/s1kd-sns/s1kd-sns.c
Styles: Light Dark Classic