summaryrefslogtreecommitdiff
path: root/app/bin/partcatalog.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/bin/partcatalog.c')
-rw-r--r--app/bin/partcatalog.c1371
1 files changed, 815 insertions, 556 deletions
diff --git a/app/bin/partcatalog.c b/app/bin/partcatalog.c
index a1db09c..205ae50 100644
--- a/app/bin/partcatalog.c
+++ b/app/bin/partcatalog.c
@@ -1,7 +1,7 @@
/** \file partcatalog.c
* Manage the catalog of track parameter files
*/
-/* XTrkCad - Model Railroad CAD
+/* XTrackCAD - Model Railroad CAD
* Copyright (C) 2019 Martin Fischer
*
* This program is free software; you can redistribute it and/or modify
@@ -16,44 +16,29 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
-* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <assert.h>
-#include <ctype.h>
-#ifdef HAVE_MALLOC_H
-#include <malloc.h>
-#endif
-#include <search.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-
-#ifdef WINDOWS
- #include "include/dirent.h"
-#else
- #include <dirent.h>
-#endif
+#include "dynstring.h"
#include "fileio.h"
+#include "include/levenshtein.h"
#include "misc.h"
#include "include/paramfile.h"
#include "include/partcatalog.h"
#include "paths.h"
#include "include/stringxtc.h"
#include "include/utf8convert.h"
-
-#if _MSC_VER > 1300
- #define strnicmp _strnicmp
- #define strdup _strdup
-#endif
+#include "include/utlist.h"
#define PUNCTUATION "+-*/.,&%=#"
+#define SEARCHDELIMITER " \t\n\r/"
+#define LDISTANCELIMIT (2)
+static char *stopwords = {
+ "scale",
+};
+static int log_params;
/**
* Create and initialize the linked list for the catalog entries
@@ -61,24 +46,49 @@
* \return pointer to first element
*/
-CatalogEntry *
+Catalog *
InitCatalog(void)
{
- CatalogEntry *head;
- CatalogEntry *tail;
-
- /* allocate two pseudo nodes for beginning and end of list */
- head = (CatalogEntry *)malloc(sizeof(CatalogEntry));
- tail = (CatalogEntry *)malloc(sizeof(CatalogEntry));
-
- head->next = tail;
- tail->next = tail;
+ Catalog *newCatalog = MyMalloc(sizeof(Catalog));
+ if (newCatalog) {
+ newCatalog->head = NULL;
+ }
+ return (newCatalog);
+}
+/**
+ * Destroys the catalog
+ *
+ * \param [in] catalog
+ */
+void
+DestroyCatalog(Catalog *catalog)
+{
+ CatalogEntry *current = catalog->head;
+ CatalogEntry *entry = NULL;
+ CatalogEntry *tmp = NULL;
+// CatalogEntry *old = NULL;
+ DL_FOREACH_SAFE(current, entry, tmp) {
+ //if (old) MyFree(old);
+// old = NULL;
+ for (unsigned int i = 0; i < entry->files; i++) {
+ MyFree(entry->fullFileName[i]);
+ entry->fullFileName[i] = NULL;
+ }
+ entry->files = 0;
+ MyFree(entry->contents);
+ entry->contents = NULL;
+ MyFree(entry->tag);
+ entry->tag = NULL;
+// old = entry;
+ DL_DELETE(catalog->head,entry);
+ }
- return (head);
+ catalog->head = NULL;
}
+#if 0
/**
* Create a new CatalogEntry and add it to the linked list. The newly
* created entry is inserted into the list after the given position
@@ -90,33 +100,32 @@ InitCatalog(void)
static CatalogEntry *
InsertIntoCatalogAfter(CatalogEntry *entry)
{
- CatalogEntry *newEntry = (CatalogEntry *)malloc(sizeof(CatalogEntry));
- newEntry->next = entry->next;
- entry->next = newEntry;
- newEntry->files = 0;
- newEntry->contents = NULL;
-
- return (newEntry);
+ CatalogEntry *newEntry = (CatalogEntry *)MyMalloc(sizeof(CatalogEntry));
+ newEntry->next = entry->next;
+ newEntry->prev = entry;
+ entry->next = newEntry;
+ newEntry->files = 0;
+ newEntry->contents = NULL;
+ newEntry->tag = NULL;
+
+ return (newEntry);
}
+#endif
/**
- * Count the elements in the linked list ignoring dummy elements
+ * Count the elements in the linked list
*
- * \param listHeader IN the linked list
- * \return the numberof elements
+ * \param catalog IN
+ * \return the number of elements
*/
unsigned
-CountCatalogEntries(CatalogEntry *listHeader)
+CountCatalogEntries(Catalog *catalog)
{
- CatalogEntry *currentEntry = listHeader->next;
- unsigned count = 0;
-
- while (currentEntry != currentEntry->next) {
- count++;
- currentEntry = currentEntry->next;
- }
- return (count);
+ CatalogEntry * entry;
+ unsigned count = 0;
+ DL_COUNT(catalog->head, entry, count);
+ return (count);
}
/**
@@ -125,119 +134,134 @@ CountCatalogEntries(CatalogEntry *listHeader)
* \param listHeader IN the list
*/
-void
-EmptyCatalog(CatalogEntry *listHeader)
+EXPORT void
+CatalogDiscard(Catalog *catalog)
{
- CatalogEntry *current = listHeader;
-
- while (current->next != current->next->next) {
- CatalogEntry *removedElement;
- removedElement = current->next;
- current->next = current->next->next;
- if (removedElement->contents) {
- free(removedElement->contents);
- }
- for (unsigned int i = 0; i < removedElement->files; i++) {
- free(removedElement->fullFileName[i]);
- }
- free(removedElement);
- }
+ CatalogEntry *current = catalog->head;
+ CatalogEntry *element;
+ CatalogEntry *tmp;
+// CatalogEntry *old = NULL;
+
+ DL_FOREACH_SAFE(current, element, tmp) {
+ //if (old) MyFree(old);
+// old = NULL;
+ MyFree(element->contents);
+ element->contents = NULL;
+ MyFree(element->tag);
+ element->tag = NULL;
+ for (unsigned int i = 0; i < element->files; i++) {
+ MyFree(element->fullFileName[i]);
+ element->fullFileName[i] = NULL;
+ }
+ element->files = 0;
+// old = element;
+ DL_DELETE(catalog->head,element);
+ }
+
+ catalog->head = NULL;
}
/**
- * Find the position in the list and add
+ * Compare entries
*
- * \param listHeader IN start of list
- * \param contents IN contents to include
+ * \param [in] a If non-null, a CatalogEntry to compare.
+ * \param [in] b If non-null, a CatalogEntry to compare.
*
- * \return CatalogEntry if found, NULL otherwise
+ * \returns An int.
*/
-static CatalogEntry *
-InsertInOrder(CatalogEntry *listHeader, const char *contents)
+static int
+CompareEntries(CatalogEntry *a, CatalogEntry *b)
{
- CatalogEntry *currentEntry = listHeader;
-
- while (currentEntry->next != currentEntry->next->next) {
- CatalogEntry *nextEntry = currentEntry->next;
- if (XtcStricmp(nextEntry->contents, contents)>0) {
- return InsertIntoCatalogAfter(currentEntry);
- }
- currentEntry = nextEntry;
- }
- return InsertIntoCatalogAfter(currentEntry);
+ return XtcStricmp(a->contents, b->contents);
}
+
/**
- * Get the existing list element for a content
+ * Create a new CatalogEntry and insert it keeping the list sorted
*
- * \param listHeader IN start of list
- * \param contents IN contents to search
- * \param Do we log error messages or not
+ * \param [in] catalog
+ * \param [in] contents to include.
+ * \param [in] tag
*
- * \return CatalogEntry if found, NULL otherwise
+ * \returns CatalogEntry
*/
-static CatalogEntry *
-IsExistingContents(CatalogEntry *listHeader, const char *contents, BOOL_T silent)
+EXPORT CatalogEntry *
+InsertInOrder(Catalog *catalog, const char *contents, const char *tag)
{
- CatalogEntry *currentEntry = listHeader->next;
-
- while (currentEntry != currentEntry->next) {
- if (!XtcStricmp(currentEntry->contents, contents)) {
- if (!silent)
- printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]);
- return (currentEntry);
- }
- currentEntry = currentEntry->next;
- }
- return (NULL);
-}
+ CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry));
+ newEntry->files = 0;
-/**
- * Store information about a parameter file. The filename is added to the array
- * of files with identical content. If not already present, in addition
- * the content is stored
- *
- * \param entry existing entry to be updated
- * \param path filename to add
- * \param contents contents description
- */
+ if (contents) {
+ newEntry->contents = MyStrdup(contents);
+ }
+ if (tag) {
+ newEntry->tag = MyStrdup(tag);
+ }
-static void
-UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents)
-{
- if (!entry->contents) {
- entry->contents = strdup(contents);
- }
-
- if (entry->files < MAXFILESPERCONTENT) {
- entry->fullFileName[entry->files++] = strdup(path);
- } else {
- AbortProg("Number of file with same content too large!", NULL);
- }
+ DL_INSERT_INORDER(catalog->head, newEntry, CompareEntries);
+
+ return newEntry;
}
/**
- * Create the list for the catalog entries
+ * Find an existing list element for a given content
+ *
+ * \param [in] catalog
+ * \param [in] contents contents to search.
+ * \param [in] silent we log error messages or not.
*
- * \return
+ * \returns CatalogEntry if found, NULL otherwise.
*/
static CatalogEntry *
-CreateCatalog()
+IsExistingContents(Catalog *catalog, const char *contents, BOOL_T silent)
{
- CatalogEntry *catalog = InitCatalog();
+ CatalogEntry *head = catalog->head;
+ CatalogEntry *currentEntry;
- return (catalog);
+ DL_FOREACH(head, currentEntry) {
+ if (!XtcStricmp(currentEntry->contents, contents)) {
+ if (!silent) {
+ printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]);
+ }
+ return (currentEntry);
+ }
+ }
+ return (NULL);
}
+/**
+ * Store information about a parameter file. The filename is added to the array
+ * of files with identical content. If not already present, in addition
+ * the content is stored
+ *
+ * \param entry existing entry to be updated
+ * \param path filename to add
+ * \param contents contents description
+ */
-static IndexEntry *
-CreateIndexTable(unsigned int capacity)
+EXPORT void
+UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents, char *tag)
{
- IndexEntry *index = (IndexEntry *)malloc(capacity * sizeof(IndexEntry));
+ if (!entry->contents) {
+ MyFree(entry->contents);
+ entry->contents = NULL;
+ }
+ if (contents) {
+ entry->contents = MyStrdup(contents);
+ }
+
+ if (!entry->tag) {
+ MyFree(entry->tag);
+ entry->tag = NULL;
+ }
+ if (tag) {
+ entry->tag = MyStrdup(tag);
+ }
- return (index);
+ CHECK( entry->files < MAXFILESPERCONTENT );
+ entry->fullFileName[entry->files++] = MyStrdup(path);
}
/**
@@ -245,7 +269,7 @@ CreateIndexTable(unsigned int capacity)
*
* \param dir IN opened directory handle
* \param dirName IN name of directory
- * \param fileName OUT fully qualified filename
+ * \param fileName OUT fully qualified filename, must be free()'d by caller
*
* \return TRUE if file found, FALSE if not
*/
@@ -253,97 +277,40 @@ CreateIndexTable(unsigned int capacity)
static bool
GetNextParameterFile(DIR *dir, const char *dirName, char **fileName)
{
- bool done = false;
- bool res = false;
-
- /*
- * get all files from the directory
- */
- while (!done) {
- struct stat fileState;
- struct dirent *ent;
-
- ent = readdir(dir);
-
- if (ent) {
- if (!XtcStricmp(FindFileExtension(ent->d_name), "xtp")) {
- /* create full file name and get the state for that file */
- MakeFullpath(fileName, dirName, ent->d_name, NULL);
-
- if (stat(*fileName, &fileState) == -1) {
- fprintf(stderr, "Error getting file state for %s\n", *fileName);
- continue;
- }
-
- /* ignore any directories */
- if (!(fileState.st_mode & S_IFDIR)) {
- done = true;
- res = true;
- }
- }
- } else {
- done = true;
- res = false;
- }
- }
- return (res);
-}
-
-/**
- * Scan a directory for parameter files. For each file found the CONTENTS is
- * read and added to the list *
- *
- * \param insertAfter IN starting point for the list of files
- * \param dirName IN directory to be scanned
- *
- * \return pointer to the last element(?)
- */
-
-static CatalogEntry *
-ScanDirectory(CatalogEntry *catalog, const char *dirName)
-{
- DIR *d;
- CatalogEntry *newEntry = catalog;
-
- d = opendir(dirName);
- if (d) {
- char *fileName = NULL;
-
- while (GetNextParameterFile(d, dirName, &fileName)) {
- CatalogEntry *existingEntry;
- char *contents = GetParameterFileContent(fileName);
- if ((existingEntry = IsExistingContents(catalog, contents,FALSE))) {
- printf("Duplicate CONTENTS record in parameter file %s\n", fileName);
- if (strcmp(existingEntry->fullFileName[existingEntry->files-1],fileName))
- UpdateCatalogEntry(existingEntry, fileName, contents);
- } else {
- newEntry = InsertInOrder(catalog,contents);
- UpdateCatalogEntry(newEntry, fileName, contents);
- }
- free(contents);
- free(fileName);
- fileName = NULL;
- }
- closedir(d);
- }
-
- return (newEntry);
-}
-
-/**
- * Comparison function for IndexEntries used by qsort()
- *
- * \param entry1 IN
- * \param entry2 IN
- * \return per C runtime conventions
- */
+ bool done = false;
+ bool res = false;
+
+ /*
+ * get all files from the directory
+ */
+ while (!done) {
+ struct stat fileState;
+ struct dirent *ent;
+
+ ent = readdir(dir);
+
+ if (ent) {
+ if (!XtcStricmp(FindFileExtension(ent->d_name), "xtp")) {
+ /* create full file name and get the state for that file */
+ MakeFullpath(fileName, dirName, ent->d_name, NULL);
+
+ if (stat(*fileName, &fileState) == -1) {
+ fprintf(stderr, "Error getting file state for %s\n", *fileName);
+ continue;
+ }
-static int
-CompareIndex(const void *entry1, const void *entry2)
-{
- IndexEntry index1 = *(IndexEntry *)entry1;
- IndexEntry index2 = *(IndexEntry *)entry2;
- return (strcoll(index1.keyWord, index2.keyWord));
+ /* ignore any directories */
+ if (!(fileState.st_mode & S_IFDIR)) {
+ done = true;
+ res = true;
+ }
+ }
+ } else {
+ done = true;
+ res = false;
+ }
+ }
+ return (res);
}
/*!
@@ -357,218 +324,185 @@ CompareIndex(const void *entry1, const void *entry2)
bool
FilterKeyword(char *word)
{
- if (strlen(word) == 1 && strpbrk(word, PUNCTUATION )) {
- return(true);
+ if (strlen(word) == 1 && strpbrk(word, PUNCTUATION)) {
+ return (true);
}
- return(false);
-}
-
-/**
- * Create the keyword index from a list of parameter files
- *
- * \param catalog IN list of parameter files
- * \param index IN index table to be filled
- * \param pointer IN/OUT array of words that are indexed
- * \param capacityOfIndex IN total maximum of keywords
- * \return number of indexed keywords
- */
-static unsigned
-CreateContentsIndex(CatalogEntry *catalog, IndexEntry *index, void** words_array,
- unsigned capacityOfIndex)
-{
- CatalogEntry *currentEntry = catalog->next;
- unsigned totalMemory = 0;
- size_t wordCount = 0;
- char *wordList;
- char *wordListPtr;
-
- while (currentEntry != currentEntry->next) {
- totalMemory += strlen(currentEntry->contents) + 1;
- currentEntry = currentEntry->next;
- }
-
- wordList = malloc((totalMemory + 1) * sizeof(char));
- *words_array = (void*)wordList;
-
- wordListPtr = wordList;
- currentEntry = catalog->next;
-
- while (currentEntry != currentEntry->next) {
- char *word;
- char *content = strdup(currentEntry->contents);
-
- word = strtok(content, " \t\n\r");
- while (word && wordCount < capacityOfIndex) {
- strcpy(wordListPtr, word);
-
- char *p = wordListPtr;
- for (; *p; ++p) {
- *p = tolower(*p);
- }
- if (!FilterKeyword(wordListPtr)) {
- index[wordCount].value = currentEntry;
- index[wordCount].keyWord = wordListPtr;
- wordListPtr += strlen(word) + 1;
- wordCount++;
- if (wordCount >= capacityOfIndex) {
- AbortProg("Too many keywords were used!", NULL);
- }
- }
- word = strtok(NULL, " \t\n\r");
- }
- free(content);
- currentEntry = currentEntry->next;
- }
- *wordListPtr = '\0';
- qsort((void*)index, wordCount, sizeof(IndexEntry), CompareIndex);
-
- return (wordCount);
+ for (int i = 0; i < sizeof(stopwords) / sizeof(char *); i++) {
+ if (!XtcStricmp(word, stopwords+i)) {
+ return (true);
+ }
+ }
+ return (false);
}
-/**
-* A recursive binary search function. It returns location of x in
-* given array arr[l..r] is present, otherwise -1
-* Taken from http://www.geeksforgeeks.org/binary-search/ and modified
-*
-* \param arr IN array to search
-* \param l IN starting index
-* \param r IN highest index in array
-* \param key IN key to search
-* \return index if found, -1 otherwise
-*/
-
-static int SearchInIndex(IndexEntry arr[], int l, int r, char *key)
+int KeyWordCmp(void *a, void *b)
{
- if (r >= l) {
- int mid = l + (r - l) / 2;
- int res = XtcStricmp(key, arr[mid].keyWord);
-
- // If the element is present at the middle itself
- if (!res) {
- return mid;
- }
-
- // If the array size is 1
- if (r == 0) {
- return -1;
- }
-
- // If element is smaller than mid, then it can only be present
- // in left subarray
- if (res < 0) {
- return SearchInIndex(arr, l, mid - 1, key);
- }
-
- // Else the element can only be present in right subarray
- return SearchInIndex(arr, mid + 1, r, key);
- }
-
- // We reach here when element is not present in array
- return -1;
+ return XtcStricmp(((IndexEntry *)a)->keyWord,((IndexEntry *)b)->keyWord);
}
+
/**
- * Inserts a key in arr[] of given capacity. n is current
- * size of arr[]. This function returns n+1 if insertion
- * is successful, else n.
- * Taken from http ://www.geeksforgeeks.org/search-insert-and-delete-in-a-sorted-array/ and modified
+ * Standardize spelling: remove some typical spelling problems. It is assumed that the word
+ * has already been converted to lower case
+ *
+ * \param [in,out] word If non-null, the word.
*/
-int InsertSorted(CatalogEntry *arr[], int n, CatalogEntry *key, int capacity)
+void
+StandardizeSpelling(char *word)
{
- // Cannot insert more elements if n is already
- // more than or equal to capcity
- if (n >= capacity) {
- return n;
- }
+ char *p = strchr(word, '-');
+ // remove the word 'scale' from combinations like N-scale
+ if (p) {
+ if (!XtcStricmp(p+1, "scale")) {
+ *p = '\0';
+ }
+ }
- int i;
- for (i = n - 1; (i >= 0 && arr[i] > key); i--) {
- arr[i + 1] = arr[i];
- }
+ if (!strncasecmp(word, "h0", 2)) {
+ strcpy(word, "ho");
+ }
- arr[i + 1] = key;
+ if (!strncasecmp(word, "00", 2)) {
+ strcpy(word, "oo");
+ }
- return (n + 1);
+ if (word[0] == '0') {
+ word[0] = 'o';
+ }
}
/**
- * Comparison function for CatalogEntries used by qsort()
+ * Create the keyword index from a list of parameter files
*
- * \param entry1 IN
- * \param entry2 IN
- * \return per C runtime conventions
+ * \param [in] library initialized library
+ *
+ * \returns number of indexed keywords.
*/
-static int
-CompareResults(const void *entry1, const void *entry2)
+static unsigned
+CreateKeywordIndex(ParameterLib *library)
{
- CatalogEntry * index1 = *(CatalogEntry **)entry1;
- CatalogEntry * index2 = *(CatalogEntry **)entry2;
- return (strcoll(index1->contents, index2->contents));
-}
+ CatalogEntry *listOfEntries = library->catalog->head;
+ CatalogEntry *curParamFile;
+ size_t totalMemory = 0;
+ size_t wordCount = 0;
+ char *wordList;
+ char *wordListPtr;
+ IndexEntry *index = library->index;
+
+ // allocate a buffer for the complete set of keywords
+ DL_FOREACH(listOfEntries, curParamFile) {
+ totalMemory += strlen(curParamFile->contents) + 1;
+ }
+ wordList = MyMalloc((totalMemory + 1) * sizeof(char));
-/**
- * Search the index for a keyword. The index is assumed to be sorted. So after one entry
- * is found, neighboring entries up and down are checked as well. The total result set
- * is placed into an array and returned. This array has to be free'd by the caller.
- *
- * \param index IN index list
- * \param length IN number of entries index
- * \param search IN search string
- * \param resultCount OUT count of found entries
- * \return array of found catalog entries, NULL if none found
- */
+ wordListPtr = wordList;
-static int findAll = 1;
+ DL_FOREACH(listOfEntries, curParamFile) {
+ char *word;
+ char *content = strdup(curParamFile->contents);
-unsigned int
-FindWord(IndexEntry *index, int length, char *search, CatalogEntry ***entries)
-{
- CatalogEntry **result; //Array of pointers to Catalog Entries
- int found;
- int foundElements = 0;
- *entries = NULL;
-
- //Get all the entries back for generic search or if "generic find"
- if (findAll || !search || (search[0] == '*') || (search[0] == '\0')) {
- result = malloc((length) * sizeof(CatalogEntry *));
- for (int i = 0; i < length; i++) {
- result[i] = index[i].value;
- }
- *entries = result;
- return length;
- }
+ word = strtok(content, SEARCHDELIMITER);
+ while (word) {
+ strcpy(wordListPtr, word);
- found = SearchInIndex(index, 0, length, search);
+ XtcStrlwr(wordListPtr);
+ if (!FilterKeyword(wordListPtr)) {
+ IndexEntry *searchEntry = MyMalloc(sizeof(IndexEntry));
+ IndexEntry *existingEntry = NULL;
+ searchEntry->keyWord = wordListPtr;
+ StandardizeSpelling(wordListPtr);
- if (found >= 0) {
- int lower = found;
- int upper = found;
- int i;
+ if (index) {
+ DL_SEARCH(index, existingEntry, searchEntry, KeyWordCmp);
+ }
+ if (existingEntry) {
+ DYNARR_APPEND(CatalogEntry *, *(existingEntry->references), 5);
+ DYNARR_LAST(CatalogEntry *, *(existingEntry->references)) = curParamFile;
+ MyFree(searchEntry);
+ } else {
+ searchEntry->references = calloc(1, sizeof(dynArr_t));
+ DYNARR_APPEND(CatalogEntry *, *(searchEntry->references), 5);
+ DYNARR_LAST(CatalogEntry *, *(searchEntry->references)) = curParamFile;
+ DL_APPEND(index, searchEntry);
+ LOG1(log_params, ("Index Entry: <%s>\n", searchEntry->keyWord))
+ }
- while (lower > 0 && !XtcStricmp(index[lower-1].keyWord, search)) {
- lower--;
- }
+ wordListPtr += strlen(word) + 1;
+ wordCount++;
+ }
+ word = strtok(NULL, SEARCHDELIMITER);
+ }
+ free(content);
+ }
+ *wordListPtr = '\0';
- while (upper < length - 1 && !XtcStricmp(index[upper + 1].keyWord, search)) {
- upper++;
- }
+ DL_SORT(index, KeyWordCmp);
+ library->index = index;
+ library->words = wordList;
- foundElements = 1 + upper - lower;
+ IndexEntry *existingEntry;
+ DL_FOREACH(index, existingEntry) {
+ LOG1(log_params, ("Index Entry: <%s> Count: %d\n", existingEntry->keyWord,
+ existingEntry->references->cnt));
+ }
+ return (unsigned)(wordCount);
+}
- result = malloc((foundElements) * sizeof(CatalogEntry *));
+/**
+ * Search the index for a keyword. The index is assumed to be sorted. Each
+ * keyword has one entry in the index list.
+ *
+ * \param [in] index index list.
+ * \param length number of entries index.
+ * \param [in] search search string.
+ * \param [out] entries array of found entry.
+ *
+ * \returns TRUE if found, FALSE otherwise.
+ */
- for (i = 0; i < foundElements; i++) {
- result[i] = index[i+lower].value;
- }
+unsigned int
+FindWord(IndexEntry *index, int length, char *search, IndexEntry **entries)
+{
+ IndexEntry *result = NULL;
+
+ IndexEntry searchWord;
+ searchWord.keyWord = search;
+
+ *entries = NULL;
+
+ DL_SEARCH(index, result, &searchWord, KeyWordCmp);
+ if (!result) {
+ int maxdistance = 1;
+ while (maxdistance <= LDISTANCELIMIT && !result ) {
+ IndexEntry *current;
+// size_t minDistance = LDISTANCELIMIT + 1;
+ int maxProbability = 0;
+ LOG1(log_params, ("Close match for: <%s> maxdistance: %d\n", search,
+ maxdistance));
+
+ DL_FOREACH(index, current) {
+ size_t ldist = levenshtein(search, current->keyWord);
+ LOG1(log_params, ("Distance of: <%s> is %d\n", current->keyWord, ldist));
+ if (ldist == maxdistance) {
+ if (current->references->cnt > maxProbability) {
+ if (!result) {
+ result = MyMalloc(sizeof(IndexEntry));
+ }
+ memcpy(result, current, sizeof(IndexEntry));
+ maxProbability = current->references->cnt;
+ }
+ }
+ }
- qsort((void*)result, foundElements, sizeof(void *), CompareResults);
+ maxdistance++;
+ }
+ }
- *entries = result;
- }
- return (foundElements);
+ *entries = result;
+ return (result != NULL);
}
/**
@@ -578,23 +512,38 @@ FindWord(IndexEntry *index, int length, char *search, CatalogEntry ***entries)
* \return TRUE on success
*/
-TrackLibrary *
+ParameterLib *
InitLibrary(void)
{
- TrackLibrary *trackLib = malloc(sizeof(TrackLibrary));
+ ParameterLib *trackLib = MyMalloc(sizeof(ParameterLib));
+
+ if (trackLib) {
+ trackLib->catalog = InitCatalog();
+ trackLib->index = NULL;
+ trackLib->wordCount = 0;
+ trackLib->parameterFileCount = 0;
+ }
+
+ return (trackLib);
+}
- if (trackLib) {
- trackLib->catalog = CreateCatalog();
- trackLib->index = NULL;
- trackLib->wordCount = 0;
- trackLib->trackTypeCount = 0;
- }
+/**
+ * Destroys the library freeing all associated memory
+ *
+ * \param [in] library If non-null, the library.
+ */
- return (trackLib);
+void
+DestroyLibrary(ParameterLib *library)
+{
+ if (library) {
+ DestroyCatalog(library->catalog);
+ MyFree(library);
+ }
}
/**
- * Scan directory and all parameter files found to the catalog
+ * Scan directory and add all parameter files found to the catalog
*
* \param trackLib IN the catalog
* \param directory IN directory to scan
@@ -602,78 +551,109 @@ InitLibrary(void)
*/
bool
-GetTrackFiles(TrackLibrary *trackLib, char *directory)
+CreateCatalogFromDir(ParameterLib *paramLib, char *directory)
{
- ScanDirectory(trackLib->catalog, directory);
- trackLib->trackTypeCount = CountCatalogEntries(trackLib->catalog);
+ DIR *d;
+ Catalog *catalog = paramLib->catalog;
+
+ d = opendir(directory);
+ if (d) {
+ char *fileName = NULL;
+
+ while (GetNextParameterFile(d, directory, &fileName)) {
+ CatalogEntry *existingEntry;
+
+ char *contents = GetParameterFileContent(fileName);
+
+ char *scale = GetParameterFileScale(fileName);
- return (trackLib->trackTypeCount);
+
+ if ((existingEntry = IsExistingContents(catalog, contents, FALSE))) {
+ UpdateCatalogEntry(existingEntry, fileName, contents, scale);
+ } else {
+ CatalogEntry *newEntry;
+ newEntry = InsertInOrder(catalog, contents, scale);
+ UpdateCatalogEntry(newEntry, fileName, contents, scale);
+ }
+ MyFree(contents);
+ MyFree(scale);
+ free(fileName);
+ fileName = NULL;
+ }
+ closedir(d);
+ }
+ paramLib->parameterFileCount = CountCatalogEntries(paramLib->catalog);
+ return (paramLib->parameterFileCount);
}
+
/**
- * Add a list of parameters files to a catalog. This function is
- * called when the user selects files in the file selector.
+ * Discard the complete catalog from a library
*
- * \param files IN count of files
- * \param fileName IN array of filenames
- * \param data IN pointer to the catalog
- * \return alwqys TRUE
+ * \param [in] library
*/
-int GetParameterFileInfo(
- int files,
- char ** fileName,
- void * data)
+void
+DiscardCatalog(ParameterLib *library)
{
- CatalogEntry *catalog = (CatalogEntry *)data;
-
- assert(fileName != NULL);
- assert(files > 0);
- assert(data != NULL);
-
- for (int i = 0; i < files; i++) {
- CatalogEntry *newEntry;
- char *contents = GetParameterFileContent(fileName[i]);
-
- if (!(newEntry = IsExistingContents(catalog, contents,TRUE))) {
- newEntry = InsertIntoCatalogAfter(catalog);
- }
- UpdateCatalogEntry(newEntry, fileName[i], contents);
- free(contents);
- }
- return (TRUE);
+ CatalogEntry *entry;
+ CatalogEntry *temp;
+
+ DL_FOREACH_SAFE(library->catalog->head, entry, temp) {
+ MyFree(entry->contents);
+ MyFree(entry->tag);
+ for (unsigned int i = 0; i < entry->files; i++) {
+ MyFree(entry->fullFileName[i]);
+ }
+ DL_DELETE(library->catalog->head, entry);
+ MyFree(entry);
+ }
+
}
+
/**
- * Create the search index from the contents description for the whole catalog.
- * A fixed number of words are added to the index. See ESTIMATED_CONTENTS_WORDS
+ * Create the search index from the contents description for the whole
+ * catalog.
*
- * \param trackLib IN the catalog
- * \return the number of words indexed
+ * \param [in] parameterLib IN the catalog.
+ *
+ * \returns the number of words indexed.
*/
unsigned
-CreateLibraryIndex(TrackLibrary *trackLib)
+CreateLibraryIndex(ParameterLib *parameterLib)
{
- trackLib->index = CreateIndexTable(trackLib->trackTypeCount *
- ESTIMATED_CONTENTS_WORDS);
+ parameterLib->index = NULL;
- trackLib->wordCount = CreateContentsIndex(trackLib->catalog, trackLib->index,
- &trackLib->words_array,
- ESTIMATED_CONTENTS_WORDS * trackLib->trackTypeCount);
+ parameterLib->wordCount = CreateKeywordIndex(parameterLib);
- return (trackLib->wordCount);
+ return (parameterLib->wordCount);
}
+/**
+ * Discard library index freeing all memory used
+ * references were created using MakeFullPath. These were allocated using malloc and
+ * not MyMalloc
+ *
+ * \param [in] trackLib the track library.
+ */
+
void
-DeleteLibraryIndex(TrackLibrary *trackLib)
+DiscardLibraryIndex(ParameterLib *trackLib)
{
- free(trackLib->index);
- trackLib->index = NULL;
-
- free(trackLib->words_array);
+ IndexEntry *indexEntry;
+ IndexEntry *tmp;
+
+ DL_FOREACH_SAFE(trackLib->index, indexEntry, tmp) {
+ DYNARR_FREE(CatalogEntry *, *(indexEntry->references));
+ free(indexEntry->references);
+ DL_DELETE(trackLib->index, indexEntry);
+ MyFree(indexEntry);
+ }
+ MyFree(trackLib->words);
+ trackLib->index = NULL;
trackLib->wordCount = 0;
-
}
@@ -684,128 +664,307 @@ DeleteLibraryIndex(TrackLibrary *trackLib)
* \return NULL if error or empty directory, else library handle
*/
-TrackLibrary *
+ParameterLib *
CreateLibrary(char *directory)
{
- TrackLibrary *library;
+ ParameterLib *library;
- library = InitLibrary();
- if (library) {
- if (!GetTrackFiles(library, directory)) {
- return (NULL);
- }
+ log_params = LogFindIndex("params");
- CreateLibraryIndex(library);
- }
- return (library);
+ library = InitLibrary();
+ if (library) {
+ if (!CreateCatalogFromDir(library, directory)) {
+ return (NULL);
+ }
+
+ CreateLibraryIndex(library);
+ }
+ return (library);
}
+/**
+ * Discard library freeing all memory used
+ *
+ * \param [in,out] library If non-null, the library.
+ */
+
void
-DeleteLibrary(TrackLibrary* library)
+DiscardLibrary(ParameterLib* library)
{
- DeleteLibraryIndex(library);
+ CatalogEntry *entry = library->catalog->head;
+ CatalogEntry *element;
+ CatalogEntry *tmp;
+ DiscardLibraryIndex(library);
- free(library);
+ DL_FOREACH_SAFE(entry, element, tmp) {
+ MyFree(element->contents);
+ MyFree(element->tag);
+ for (unsigned int i = 0; i < element->files; i++) {
+ MyFree(element->fullFileName[i]);
+ }
+ DL_DELETE(entry, element);
+ MyFree(element);
+ }
+ MyFree(library->words);
+ MyFree(library);
}
-// Case insensitive comparison
-char* stristr( const char* haystack, const char* needle )
+/**
+ * Create a statistic for a finished search. The returned string has to be MyFreed() after usage
+ *
+ * \param [in] result the finished search
+ *
+ * \returns Null if it fails, else the found statistics.
+ */
+
+char *
+SearchStatistics(SearchResult *result)
{
- int c = tolower((unsigned char)*needle);
- if (c == '\0')
- return (char *)haystack;
- for (; *haystack; haystack++) {
- if (tolower((unsigned char)*haystack) == c) {
- for (size_t i = 0;;) {
- if (needle[++i] == '\0')
- return (char *)haystack;
- if (tolower((unsigned char)haystack[i]) != tolower((unsigned char)needle[i]))
- break;
- }
- }
- }
- return NULL;
+ DynString buffer;
+ DynString subStats[STATE_COUNT];
+
+ unsigned searched = 0;
+ unsigned discarded = 0;
+ unsigned notfound = 0;
+ unsigned close = 0;
+
+ char *resStat;
+ DynStringMalloc(&buffer, 16);
+
+ for (int i = SEARCHED; i < STATE_COUNT; i++) {
+ DynStringMalloc(subStats + i, 16);
+ }
+
+ DynStringCatCStr(subStats + SEARCHED, _("Found: "));
+ DynStringCatCStr(subStats + CLOSE, _("Similar: "));
+ DynStringCatCStr(subStats + DISCARDED, _("Ignored: "));
+ DynStringCatCStr(subStats + NOTFOUND, _("Not found: "));
+
+ for (unsigned int i = 0; i < result->words; i++) {
+ switch (result->kw[i].state) {
+ case SEARCHED:
+ DynStringPrintf(&buffer, "%s (%d) ", result->kw[i].keyWord,
+ result->kw[i].count);
+ searched++;
+ break;
+ case DISCARDED:
+ DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord);
+ discarded++;
+ break;
+ case NOTFOUND:
+ DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord);
+ notfound++;
+ break;
+ case CLOSE:
+ DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord);
+ close++;
+ break;
+ default:
+ break;
+ }
+ DynStringCatStr(subStats + result->kw[i].state, &buffer);
+ }
+
+ DynStringReset(&buffer);
+ if (searched) {
+ DynStringCatStr(&buffer, subStats + SEARCHED);
+ }
+ if (close) {
+ DynStringCatStr(&buffer, subStats + CLOSE);
+ }
+ if (notfound) {
+ DynStringCatStr(&buffer, subStats + NOTFOUND);
+ }
+ if (discarded) {
+ DynStringCatStr(&buffer, subStats + DISCARDED);
+ }
+
+ resStat = MyStrdup(DynStringToCStr(&buffer));
+ DynStringFree(&buffer);
+ for (int i = SEARCHED; i < STATE_COUNT; i++) {
+ DynStringFree(subStats + i);
+ }
+ return (resStat);
}
/**
- * Search the library for a keyword string and return the result list
+ * returns number of words in str.
*
- * First the index is searched for the first word and then each "hit" is matched
- * to the entire search string
+ * \param [in] str the string.
*
- * Null, Blank and "*" match all entries
+ * \returns The total number of words.
+ */
+
+unsigned countWords(char *str)
+{
+ int state = FALSE;
+ unsigned wc = 0; // word count
+
+ // Scan all characters one by one
+ while (*str) {
+ // If next character is a separator, set the
+ // state as FALSE
+ if (*str == ' ' || *str == '\n' || *str == '\t' || *str == '\r'
+ || *str == '/') {
+ state = FALSE;
+ }
+
+ // If next character is not a word separator and
+ // state is OUT, then set the state as IN and
+ // increment word count
+ else if (state == FALSE) {
+ state = TRUE;
+ ++wc;
+ }
+
+ // Move to next character
+ ++str;
+ }
+
+ return wc;
+}
+
+/**
+ * Search the library for a keyword string and return the result list
*
- * The list is de-duped of repeat of filenames as the same file might appear in
- * more than once
+ * Each key word exists only once in the index.
*
* \param library IN the library
* \param searchExpression IN keyword to search for
* \param resultEntries IN list header for result list
* \return number of found entries
*/
+
unsigned
-SearchLibrary(TrackLibrary *library, char *searchExpression,
- CatalogEntry *resultEntries)
+SearchLibrary(ParameterLib *library, char *searchExpression,
+ SearchResult *results)
{
- CatalogEntry **entries;
- CatalogEntry * newEntry = resultEntries;
- unsigned entryCount;
-
- char * word;
-
- word = strdup(searchExpression);
-
- //word = strtok(word," \t");
-
- if (library->index == NULL || library->wordCount == 0) {
- return (0);
- }
- entryCount = FindWord(library->index, library->wordCount, word,
- &entries);
- int count= 0;
- if (entryCount) {
- unsigned int i = 0;
- while (i < entryCount) {
- char * match;
- //Check if entire String Matches
- if (!searchExpression || !word || (word[0] == '*') || (word[0] == '\0') ||
- (match = stristr(entries[i]->contents,searchExpression))) {
- CatalogEntry * existingEntry;
- existingEntry = IsExistingContents(resultEntries, entries[i]->contents, TRUE);
- //Same FileName already in one of the entries?
- BOOL_T found = FALSE;
- if (existingEntry) {
- for (unsigned int j=0;j<existingEntry->files;j++) {
- if (!strcmp(existingEntry->fullFileName[j],entries[i]->fullFileName[entries[i]->files-1])) {
- found=TRUE;
+ CatalogEntry *element;
+ IndexEntry *entries;
+// unsigned entryCount = 0;
+ char *searchWord;
+ unsigned words = countWords(searchExpression);
+ char *searchExp = MyStrdup(searchExpression);
+ unsigned i = 0;
+
+ if (library->index == NULL || library->wordCount == 0) {
+ return (0);
+ }
+
+ results->kw = MyMalloc(words * sizeof(struct sSingleResult));
+ results->subCatalog.head = NULL;
+
+ searchWord = strtok(searchExp, SEARCHDELIMITER);
+ while (searchWord) {
+ XtcStrlwr(searchWord);
+ if (!FilterKeyword(searchWord)) {
+ StandardizeSpelling(searchWord);
+ results->kw[i].state = SEARCHED;
+ } else {
+ results->kw[i].state = DISCARDED;
+ }
+ results->kw[i++].keyWord = MyStrdup(searchWord);
+ searchWord = strtok(NULL, SEARCHDELIMITER);
+ }
+ results->words = words;
+
+ i = 0;
+ while (i < words) {
+ if (results->kw[i].state == DISCARDED) {
+ i++;
+ continue;
+ }
+ FindWord(library->index, library->wordCount, results->kw[i].keyWord, &entries);
+ if (entries) {
+ results->kw[i].count = entries->references->cnt;
+ if (XtcStricmp(results->kw[i].keyWord, entries->keyWord)) {
+ results->kw[i].state = CLOSE;
+ MyFree(results->kw[i].keyWord);
+ results->kw[i].keyWord = MyStrdup(entries->keyWord);
+ }
+
+ if (results->subCatalog.head == NULL) {
+ // if first keyword -> initialize result set
+ for (int j = 0; j < entries->references->cnt; j++) {
+ CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry));
+ CatalogEntry *foundEntry = DYNARR_N(CatalogEntry *, *(entries->references), j);
+ newEntry->contents = MyStrdup(foundEntry->contents);
+ newEntry->tag = MyStrdup(foundEntry->tag);
+ newEntry->files = foundEntry->files;
+ for (unsigned int i=0; i<newEntry->files; i++) {
+ newEntry->fullFileName[i] = MyStrdup(foundEntry->fullFileName[i]);
+ }
+
+ DL_APPEND(results->subCatalog.head, newEntry);
+ }
+ } else {
+ // follow up keyword, create intersection with current result set
+ CatalogEntry *current;
+ CatalogEntry *temp;
+
+ DL_FOREACH_SAFE(results->subCatalog.head, current, temp) {
+ int found = 0;
+ for (int j = 0; j < entries->references->cnt; j++) {
+ CatalogEntry *foundEntry = DYNARR_N(CatalogEntry *, *(entries->references), j);
+
+ if (strcmp(foundEntry->contents,current->contents)==0) {
+ found = TRUE;
break;
}
}
- if (found == TRUE ) {
- i++;
- continue;
+ if (!found) {
+ DL_DELETE(results->subCatalog.head, current);
+ MyFree(current->contents);
+ MyFree(current->tag);
+ for (unsigned int i=0; i<current->files; i++) {
+ MyFree(current->fullFileName[i]);
+ }
+ MyFree(current);
}
- UpdateCatalogEntry(existingEntry, entries[i]->fullFileName[(entries[i]->files- 1)],
- entries[i]->contents);
- } else {
- newEntry = InsertInOrder(resultEntries,entries[i]->contents);
- UpdateCatalogEntry(newEntry, entries[i]->fullFileName[(entries[i]->files- 1)],
- entries[i]->contents);
}
- count++;
- }
- i++;
- }
- }
- free(word);
- if (entries)
- free(entries); //Clean-up after search
- return (count);
+ }
+ } else {
+ // Searches that don't yield a result are ignored
+ results->kw[i].state = NOTFOUND;
+ results->kw[i].count = 0;
+ }
+ i++;
+ }
+
+ DL_COUNT(results->subCatalog.head, element, results->totalFound);
+ MyFree(searchExp);
+ return (results->totalFound);
+}
+
+/**
+ * Discard results. The memory allocated with the search is freed
+ *
+ * \param [in] res If non-null, the results.
+ */
+
+void
+SearchDiscardResult(SearchResult *res)
+{
+ if (res) {
+ CatalogEntry *current = res->subCatalog.head;
+ CatalogEntry *element;
+ CatalogEntry *tmp;
+
+ DL_FOREACH_SAFE(current, element, tmp) {
+ DL_DELETE(current, element);
+ MyFree(element);
+ }
+
+ for (unsigned int i = 0; i < res->words; i++) {
+ MyFree(res->kw[i].keyWord);
+ }
+ MyFree(res->kw);
+ }
}
/**
- * Get the contents description from a parameter file. Returned string has to be freed after use.
+ * Get the contents description from a parameter file. Returned string has to be MyFree'd after use.
*
* \param file IN xtpfile
* \return pointer to found contents or NULL if not present
@@ -829,10 +988,10 @@ GetParameterFileContent(char *file)
/* if found, store the rest of the line and the filename */
ptr = ptr+strlen(CONTENTSCOMMAND)+1;
ptr = strtok(ptr, "\r\n");
- result = strdup(ptr);
-#ifdef WINDOWS
+ result = MyStrdup(ptr);
+#ifdef UTFCONVERT
ConvertUTF8ToSystem(result);
-#endif // WINDOWS
+#endif // UTFCONVERT
found = true;
}
} else {
@@ -842,5 +1001,105 @@ GetParameterFileContent(char *file)
}
fclose(fh);
}
- return(result);
+ return (result);
+}
+
+/**
+ * Get the first scale values from a parameter file. Returned strings have to be MyFreed after use
+ *
+ * \param file IN xtpfile
+ * \param array of one of three char results (Track, Structure and Car)
+ */
+
+char *
+GetParameterFileScale(char *file)
+{
+ FILE *fh;
+ char *scale = NULL;
+
+
+ fh = fopen(file, "rt");
+ if (fh) {
+ bool found = FALSE, found_Turnout = FALSE, found_Structure = FALSE,
+ found_Car = FALSE;
+
+ while (!found) {
+ char buffer[512];
+ if (fgets(buffer, sizeof(buffer), fh)) {
+ char *ptr = strtok(buffer, " \t");
+ if (!found_Turnout && !XtcStricmp(ptr, TURNOUTCOMMAND)) {
+ /* if found, store the rest of the line and the filename */
+ ptr = ptr+strlen(TURNOUTCOMMAND)+1;
+ ptr = strtok(ptr, " \t");
+ scale = MyMalloc(strlen(TURNOUTCOMMAND)+2+strlen(ptr));
+ strcpy(scale,TURNOUTCOMMAND);
+ char * cp = scale + strlen(TURNOUTCOMMAND);
+ cp[0] = ' ';
+ cp++;
+ strcpy(cp,ptr);
+ found_Turnout = true;
+ } else if (!found_Structure && !XtcStricmp(ptr, STRUCTURECOMMAND)) {
+ /* if found, store the rest of the line and the filename */
+ ptr = ptr+strlen(STRUCTURECOMMAND)+1;
+ ptr = strtok(ptr, " \t");
+ scale = MyMalloc(strlen(STRUCTURECOMMAND)+2+strlen(ptr));
+ strcpy(scale,STRUCTURECOMMAND);
+ char * cp = scale + strlen(STRUCTURECOMMAND)+1;
+ cp[-1] = ' ';
+ strcpy(cp,ptr);
+ found_Structure = true;
+ } else if (!found_Car && !XtcStricmp(ptr, CARCOMMAND)) {
+ /* if found, store the rest of the line and the filename */
+ ptr = ptr+strlen(CARCOMMAND)+1;
+ ptr = strtok(ptr, " \t");
+ scale = MyMalloc(strlen(CARCOMMAND)+2+strlen(ptr));
+ strcpy(scale,CARCOMMAND);
+ char * cp = scale + strlen(CARCOMMAND)+1;
+ cp[-1] = ' ';
+ strcpy(cp,ptr);
+ found_Car = true;
+ } else if (!found_Car && !XtcStricmp(ptr, CARPROTOCOMMAND)) {
+ /* if found, store the rest of the line and the filename */
+ scale = MyMalloc(strlen(CARPROTOCOMMAND)+3);
+ strcpy(scale,CARPROTOCOMMAND);
+ char * cp = scale + strlen(CARPROTOCOMMAND);
+ strcpy(cp," *");
+ found_Car = true;
+ }
+ } else {
+ if (!found_Turnout && !found_Structure && !found_Car) {
+ fprintf(stderr, "Nothing found in %s\n", file);
+ found = true;
+ }
+ }
+ if (found_Turnout || found_Structure || found_Car) { found = TRUE; }
+ }
+ fclose(fh);
+ }
+ return scale;
+
+}
+
+#ifdef MEMWATCH
+/** this is used to test for memory leaks. It should show no leaks from functions in this source file */
+RunMemoryTest(char *directory)
+{
+ ParameterLib *library;
+ SearchResult *results;
+
+ mwInit();
+ library = InitLibrary();
+ if (library) {
+ CreateCatalogFromDir(library, directory);
+ CreateLibraryIndex(library);
+ results = MyMalloc(sizeof(SearchResult));
+ SearchLibrary(library, "peco", results);
+ SearchDiscardResult(results);
+ MyFree(results);
+ DiscardLibraryIndex(library);
+ DiscardCatalog(library);
+ }
+ DestroyLibrary(library);
+ mwTerm();
}
+#endif //MEMWATCH