/* Copyright 1997 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /***************************************************/ /* File : History.c */ /* */ /* Purpose: Handles the browser History. */ /* */ /* Author : A.D.Hodgkinson */ /* */ /* History: 07-Feb-97: Created. */ /* */ /* 06-Nov-97: Major revision, largely a */ /* complete rewrite to make */ /* the system more flexible */ /* and less prone to bugs (but */ /* still do Back/Forward as in */ /* Navigator 2, rather than */ /* Navigator 3). */ /***************************************************/ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #include "kernel.h" #include "swis.h" #include "flex.h" #include "wimp.h" #include "wimplib.h" #include "event.h" #include "toolbox.h" #include "window.h" #include "gadgets.h" #include "menu.h" #include "svcprint.h" #include "Global.h" #include "FromROSLib.h" #include "Utils.h" #include "Browser.h" #include "ChoiceDefs.h" #include "FetchPage.h" #include "Filetypes.h" #include "Memory.h" #include "OpenURL.h" #include "Save.h" #include "Toolbars.h" #include "URLutils.h" #include "Windows.h" #include "History.h" /**************************************************************************************************/ /* */ /* Browser History structure */ /* ========================= */ /* */ /* prev next The basic History structure is as pictured to the left. */ /* (entry) <---- ENTRY ----> (entry) Global History takes the form of a doubly-linked list of */ /* | ^ history_entry structures, which may point to another */ /* users| |parent doubly-linked list of structures recording the usage of */ /* | | a History item by a specific browser. Within these */ /* | | history_user records, there are doubly-linked references */ /* | |(user) to other history_user structures referring to the same */ /* | |^ browser - in this way, a local visit History is held. The */ /* | ||prev history_user structures point back to the history_entry */ /* | || structures through the 'parent' field. */ /* v || */ /* (LHPU) <---- USER ----> (LHNU) LHPU and LHNU stand for 'Local History [Prev/Next] User'. */ /* history_prev | history_next */ /* | All memory allocations are through malloc, since the */ /* |next blocks in use are typically small and must not move (so a */ /* v granular allocation system or a shifting heap such as */ /* (user) that provided by flexlib would be inappropriate). */ /* */ /* Two records are kept in the browser_data structure which are, as far as anyone else is */ /* concerned, magic words. They are in fact both history_user pointers cast to void *. One is */ /* updated every time a new history_user entry is created - this is needed so that prev/next */ /* pointers within all the items relevant to the browser's local History may be updated quickly. */ /* The other is a pointer to a history_user struct which when non-NULL, indicates a position */ /* within the History itself. */ /* */ /* Note that one history_entry may have several history_user structs threading through it for the */ /* same browser, if a user keeps going back to the same page by following links. */ /* */ /**************************************************************************************************/ /* Internationalisation support */ #ifdef UNIFONT #define CHARSET_SPECIFIER "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n" #else #define CHARSET_SPECIFIER "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n" #endif /* Local definitions */ #define HistoryWrite(fn) {written = (fn); if (written < 0) goto history_save_error;} /* Main History list structures */ struct history_entry; /* Record usage by a specific browser */ typedef struct history_user { struct history_entry * parent; struct history_user * next; struct history_user * prev; struct history_user * history_next; /* (For the browser history, this is NOT a linked list) */ struct history_user * history_prev; browser_data * user; unsigned int last_accessed; } history_user; /* Global History structure, which points to */ /* an array of usage structures (see above) */ typedef struct history_entry { struct history_entry * next; struct history_entry * prev; url_description * url; /* (Defined in URLutils.h) */ unsigned int hash; char * title; history_user * users; unsigned int last_accessed; } history_entry; /* Static variables */ static history_entry * history_base = NULL; static void * history_menu = NULL; static int menu_entries = 0; static history_user ** user_array = NULL; static history_entry ** entry_array = NULL; static int nusers = 0; static int nentries = 0; /* Static function prototypes */ static history_entry * history_find_entry (const char * url); static history_user * history_find_user (browser_data * b, history_entry * entry); static _kernel_oserror * history_add_user (browser_data * b, history_entry * found, history_user ** created); static void history_remove_user (history_user * user); static void history_remove_entry (browser_data * b, history_entry * entry); static int history_compare_entries (const void * first, const void * second); /*************************************************/ /* history_find_entry() */ /* */ /* Returns a pointer to a history_entry for a */ /* given URL. */ /* */ /* Parameters: Pointer to a null-terminated URL */ /* string to find. */ /* */ /* Returns: Pointer to a history_entry struct */ /* representing the given URL, or */ /* NULL if no such entry is found */ /* in the History. */ /*************************************************/ static history_entry * history_find_entry(const char * url) { history_entry * find = history_base; url_description * url_d; int hash; if (!url || !*url) return NULL; /* Get a description of the URL - makes comparissons faster */ url_d = urlutils_return_description(url); if (!url_d) return NULL; hash = utils_return_hash(url_d->full); /* Search the list */ while (find) { if (hash == find->hash && !urlutils_urlddcmp(find->url, url_d)) break; find = find->next; } urlutils_free_description(url_d); return find; } /*************************************************/ /* history_find_user */ /* */ /* Returns a pointer to a history_user for a */ /* given browser in a given History entry. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* represented by the entry; */ /* */ /* Pointer to a history_entry struct */ /* to search in. */ /* */ /* Returns: Pointer to a history_user struct */ /* representing the given browser or */ /* NULL if no such entry is found. */ /*************************************************/ static history_user * history_find_user(browser_data * b, history_entry * entry) { history_user * user; if (!b || !entry) return NULL; /* Search the list */ user = entry->users; while (user) { if (user->user == b) return user; user = user->next; } return NULL; } /*************************************************/ /* history_add_user() */ /* */ /* Add an association of a given browser with a */ /* given History entry, updating the browser's */ /* record of where it is in the local History to */ /* the new association (user record). This last */ /* step is required if subsequent additions are */ /* to be correctly linked into the local History */ /* list (history_next and history_prev fields). */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the association; */ /* */ /* Pointer to a history_entry struct */ /* to associate the browser to; */ /* */ /* Pointer to a history_user *, */ /* in which the address of the new */ /* user record will be placed. */ /* */ /* Assumes: The history_user * pointer may */ /* be NULL. */ /*************************************************/ static _kernel_oserror * history_add_user(browser_data * b, history_entry * found, history_user ** created) { history_user * newuser; #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_user: Called for browser %p, entry %p\n",b,found); #endif if (created) *created = NULL; newuser = malloc(sizeof(history_user)); if (!newuser) { /* Oh just great, the allocation failed. Well, rather */ /* than scrap everything, leave the global entry */ /* alone - just don't create the extra reference to */ /* this browser. */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_user: Could not allocate space for user record\n"); #endif return make_no_memory_error(25); } #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_user: New user record is %p\n", newuser); #endif /* Insert the item into the linked list of history_user */ /* structures for this history_entry structure. */ newuser->parent = found; if (found->users) found->users->prev = newuser; newuser->next = found->users; newuser->prev = NULL; found->users = newuser; /* Link the item into the linked list of history_user */ /* structures forming the browser local history. */ newuser->history_prev = (history_user *) b->history_current; newuser->history_next = NULL; newuser->user = b; /* Fill in the accessed time */ newuser->last_accessed = found->last_accessed; /* If newuser->history_prev points in its history_next */ /* field to any other items, remove them, because we've */ /* just branched the history at this point by adding */ /* the new item. */ if (newuser->history_prev && newuser->history_prev->history_next) { history_user * current = newuser->history_prev->history_next; history_user * next; #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_user: Adding from within an existing history; removing later entries...\n"); #endif while (current) { next = current->history_next; #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_user: Calling history_remove_user for user record %p\n",current); #endif history_remove_user(current); current = next; } } /* Fill in the link to the new structure */ if (newuser->history_prev) newuser->history_prev->history_next = newuser; b->history_current = (void *) newuser; #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_user: Successful\n"); #endif if (created) *created = newuser; return NULL; } /*************************************************/ /* history_record() */ /* */ /* Records the visiting of a given URL in the */ /* History, optionally associating this visit */ /* with a given browser. */ /* */ /* If there is already an entry for this URL, it */ /* will simply have its latest visit time */ /* record updated. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* to associate the visit with, or */ /* NULL for none; */ /* */ /* Pointer to a null-terminated URL */ /* string to record. */ /*************************************************/ _kernel_oserror * history_record(browser_data * b, const char * url) { history_entry * found = NULL; history_user * newuser = NULL; if (!url || !*url) return NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_record: Called for %p and '%s'\n",b,url); #endif /* First, see if we have the URL present already */ found = history_find_entry(url); /* If this is for a specific browser which is fetching */ /* from its own local History, then we don't record */ /* anything (it'd corrupt the history ordering). We */ /* should, however, update the timestamps. */ if (b && b->from_history) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_record: Browser fetching from History; updating timestamps and exitting\n"); #endif if (found) { found->last_accessed = time(NULL); newuser = history_find_user(b, found); if (newuser) newuser->last_accessed = found->last_accessed; } return NULL; } /* If not fetching from the local History, proceed as normal */ if (!found) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_record: No existing entry, so creating a new one\n"); #endif /* Need to create an entry */ found = malloc(sizeof(history_entry)); if (!found) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_record: Couldn't allocate space for entry\n"); #endif return make_no_memory_error(25); } /* Need to turn the URL into a more complete description, */ /* but if we get NULL back, memory allocation must have */ /* failed somewhere. */ found->url = urlutils_return_description(url); if (!found->url) { free(found); #ifdef TRACE if (tl & (1u<<16)) Printf("history_record: Couldn't allocate space for URL description\n"); #endif return make_no_memory_error(25); } else { /* Create a hash number for the string */ found->hash = utils_return_hash(found->url->full); } /* Link in the structure */ found->prev = NULL; found->next = history_base; if (history_base) history_base->prev = found; history_base = found; /* Fill in some other fields */ found->title = NULL; found->users = NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_record: OK, have entry %p with description block %p\n",found,found->url); #endif } #ifdef TRACE else { if (tl & (1u<<16)) Printf("history_record: Already have an entry %p\n",found); } #endif /* Update the last accessed time for the global record */ found->last_accessed = time(NULL); /* Always add a usage record for the browser, unless the URL being */ /* added matches that represented by the item the browser is currently */ /* on in terms of its visit history. */ if ( b && ( !b->history_current || ( ((history_user *) b->history_current)->parent != found ) ) ) RetError(history_add_user(b, found, NULL)); #ifdef TRACE if (tl & (1u<<16)) { Printf("history_record: Successful. History size is now %d\n", history_count()); Printf(" Exitting through expiry functions.\n"); } #endif if (choices.expiry_age) RetError(history_expire(NULL, time(NULL) - choices.expiry_age)); if (choices.max_size) RetError(history_limit(choices.max_size)); #ifdef SINGLE_USER if (choices.save_history == Choices_SaveHistory_Always) { return history_save(lookup_choice("HistorySave:Browse:User.History",0,0)); } #endif return NULL; } /*************************************************/ /* history_inherit() */ /* */ /* For any association in the History of a given */ /* base (parent) browser with a URL, create a */ /* duplicate associaton with a given child */ /* browser with the same visit timestamps. The */ /* child browser thus inherits the history of */ /* the parent. */ /* */ /* The history_current field of the child is set */ /* to the child's equivalent of the field in the */ /* parent. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* representing the parent browser; */ /* */ /* Pointer to a browser_data struct */ /* representing the child browser. */ /* */ /* Assumes: The child browser has no existing */ /* history references. If it does, */ /* then this will become detached as */ /* the new references are put in */ /* place. */ /*************************************************/ _kernel_oserror * history_inherit(browser_data * parent, browser_data * child) { history_user * parent_user; history_user * parent_current; history_user * child_user; history_user * child_equivalent = NULL; if (!parent || !child) return NULL; parent_user = (history_user *) parent->history_current; parent_current = parent_user; if (!parent_user) return NULL; /* Nothing to inherit */ /* Find the first item in the local History */ while (parent_user->history_prev) parent_user = parent_user->history_prev; /* Now go through adding users for the new browser */ child->history_current = NULL; while (parent_user) { /* Add the user record for the child */ RetError(history_add_user(child, parent_user->parent, &child_user)); /* If this record is equivalent to the history_current field */ /* of the parent, remember the new user record's address in */ /* 'child_equivalent'. */ if (parent_user == parent_current) child_equivalent = child_user; /* Go on to the next user record in the parent's History */ parent_user = parent_user->history_next; } /* Set the child's history_current field to the equivalent */ /* of the same field in the parent. */ if (child_equivalent) child->history_current = (void *) child_equivalent; /* Finished */ return NULL; } /*************************************************/ /* history_remove_user() */ /* */ /* Removes a given history_user record from the */ /* various lists in which it may lie. */ /* */ /* Parameters: Pointer to the history_user */ /* structure to remove. */ /*************************************************/ static void history_remove_user(history_user * user) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_user: Called for %p\n",user); #endif if (!user) return; /* Repoint within the linked list of items attached to a history_entry structure, */ /* and within the local history list. */ if (user->prev) user->prev->next = user->next; if (user->next) user->next->prev = user->prev; if (user->history_prev) user->history_prev->history_next = user->history_next; if (user->history_next) user->history_next->history_prev = user->history_prev; /* If required, repoint the parent history_entry structure's 'users' field. */ if (user == user->parent->users) user->parent->users = user->next; /* Does the owner browser point to this item? If so, invalidate */ /* that pointer (set it to NULL). */ if ( is_known_browser(user->user) && user->user->history_current == (void *) user ) user->user->history_current = NULL; /* Finally, free the item. */ free(user); #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_user: Successful\n"); #endif return; } /*************************************************/ /* history_remove_entry() */ /* */ /* Removes a given record from the History, */ /* removes all associations with this entry, */ /* and/or removes all associations between any */ /* URLs in the History and a given browser. In */ /* the latter case, URLs left with no */ /* assocations are *not* removed; they stay as */ /* global visit History records. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* to remove all associations for, */ /* or NULL to not do this; */ /* */ /* Pointer to the history_entry */ /* struct to remove, or NULL not to */ /* do this; */ /* */ /* Assumes: Either or both pointers may be */ /* valid or NULL, though obviously */ /* there's little point calling with */ /* both set to NULL... */ /*************************************************/ static void history_remove_entry(browser_data * b, history_entry * entry) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Called for browser %p, entry %p\n",b,entry); #endif if (entry) { history_user * user = entry->users; history_user * next; /* We've found the entry. First remove all user structures */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Have found the entry - removing user records...\n"); #endif while (user) { next = user->next; #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Calling history_remove_user for user record %p\n",user); #endif history_remove_user(user); user = next; } /* Point other History entries elsewhere too, and correct */ /* the history_base pointer if required. */ if (entry->prev) entry->prev->next = entry->next; if (entry->next) entry->next->prev = entry->prev; if (entry == history_base) history_base = entry->next; /* Free the URL description */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Calling urlutils_free_description for URL description %p\n",entry->url); #endif urlutils_free_description(entry->url); /* Free the title string */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Freeing entry title string\n"); #endif free(entry->title); /* Free the entry */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Freeing entry itself\n"); #endif free(entry); } /* Deal with removing all associations for a given browser */ if (b) { history_user * user; history_user * next; #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Removing all user records for browser %p\n",b); #endif entry = history_base; /* Go through all entries... */ while (entry) { user = entry->users; /* ...and all users (associations) in each entry... */ while (user) { next = user->next; /* ...and remove any associated with the given browser. */ if (user->user == b) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Calling history_remove_user for user record %p\n",user); #endif history_remove_user(user); } user = next; } entry = entry->next; } } /* Finished */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove_entry: Successful\n"); #endif return; } /*************************************************/ /* history_remove() */ /* */ /* Removes a record in the History of a given */ /* URL and all associations with this URL, */ /* and/or removes all associations between any */ /* URLs in the History and a given browser. In */ /* the latter case, URLs left with no */ /* assocations are *not* removed; they stay as */ /* global visit History records. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* to remove all associations for, */ /* or NULL to not do this; */ /* */ /* Pointer to a null-terminated URL */ /* string to remove, or NULL to not */ /* do this. */ /* */ /* Assumes: Either or both pointers may be */ /* valid or NULL, though obviously */ /* there's little point calling with */ /* both set to NULL... */ /*************************************************/ void history_remove(browser_data * b, const char * url) { history_entry * entry = NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove: Called for browser %p, URL address %p\n",b,url); #endif /* Deal with removing a specific URL */ if (url) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove: Removing entry for URL '%s'\n",url); #endif entry = history_find_entry(url); #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove: Entry for this URL %p\n",entry); #endif } #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove: Calling history_remove_entry\n"); #endif history_remove_entry(b, entry); #ifdef TRACE if (tl & (1u<<16)) Printf("history_remove: Successful. History size is now %d\n", history_count()); #endif } /*************************************************/ /* history_add_title() */ /* */ /* Associates a given title string with a given */ /* URL and all associations with this URL, */ /* and/or removes all associations between any */ /* URLs in the History and a given browser. */ /* */ /* Parameters: Pointer to a null-terminated URL */ /* string for which the title is to */ /* be associated; */ /* */ /* Pointer to a null-terminated */ /* title string to associate with */ /* the URL. */ /*************************************************/ _kernel_oserror * history_add_title(const char * url, const char * title) { history_entry * entry; if (!url || !*url || !title || !*title) return NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_title: Called for URL '%s'\n" " and title '%s'\n",url,title); #endif /* Find the item */ entry = history_find_entry(url); #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_title: Entry returned by history_find_entry is 0x%08x\n",entry); #endif if (!entry) return NULL; /* Add the title */ free(entry->title); entry->title = malloc(strlen(title) + 1); if (!entry->title) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_title: Couldn't allocate space for title string\n"); #endif return make_no_memory_error(26); } else strcpy(entry->title, title); /* Finished */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_add_title: Successful. History size is now %d\n", history_count()); #endif return NULL; } /*************************************************/ /* history_return_title() */ /* */ /* Returns a title string (if any) associated */ /* with a given URL. */ /* */ /* Parameters: Pointer to a null-terminated URL */ /* string for which a title is to be */ /* found. */ /* */ /* Returns: Pointer to a null-terminated */ /* title string associated with the */ /* URL, or NULL if either the URL */ /* had no associated title, or the */ /* URL couldn't be found in the */ /* History at all. */ /*************************************************/ char * history_return_title(char * url) { history_entry * entry; if (!url || !*url) return NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_return_title: Called for URL '%s'\n",url); #endif entry = history_find_entry(url); #ifdef TRACE if (tl & (1u<<16)) Printf("history_return_title: Entry returned by history_find_entry is 0x%08x\n",entry); #endif if (!entry) return NULL; #ifdef TRACE if (tl & (1u<<16)) { if (entry->title) Printf("history_return_title: Successful, returning '%s'\n",entry->title); else Printf("history_return_title: Successful, but there is no title (returning NULL)\n"); } #endif return entry->title; } /*************************************************/ /* history_expire() */ /* */ /* Remove all associations of a URL which have */ /* been visited before a given time (i.e. are */ /* greater than a certain age). This may only be */ /* for associations with a specific browser. */ /* */ /* Any URL left with no associations and an */ /* overall last visit time before that given */ /* will be removed from the History completely. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* for which associations are to be */ /* expired, or NULL if the expiry is */ /* for all History items; */ /* */ /* Time (in time() function format) */ /* which a URL must have not been */ /* visited on or since for expiry to */ /* take place - so to expire 1 day */ /* old URLs, say, you'd pass */ /* time() - 60*60*24. */ /*************************************************/ _kernel_oserror * history_expire(browser_data * b, unsigned int time) { history_entry * this_entry; history_entry * next_entry; history_user * this_user; history_user * next_user; #ifdef TRACE if (tl & (1u<<16)) Printf("history_expire: Called for %p and time %d\n",b,time); #endif this_entry = history_base; /* Scan the main entry list */ while (this_entry) { next_entry = this_entry->next; this_user = this_entry->users; /* For each entry, scan the associated users */ while (this_user) { next_user = this_user->next; /* If we've not been given a browser, or we have and it matches */ /* the user for this user record, and the last accessed time is */ /* earlier than that given (the record is older), remove it. */ if ((!b || this_user->user == b) && this_user->last_accessed < time) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_expire: Calling history_remove_user for user record %p\n",this_user); #endif history_remove_user(this_user); } this_user = next_user; } /* If this item has no users [now], and the last accessed time */ /* is earlier than that given (the entry is older), remove it. */ if (!this_entry->users && this_entry->last_accessed < time) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_expire: Calling history_remove_entry for entry %p\n",this_entry); #endif history_remove_entry(NULL, this_entry); } /* Next... */ this_entry = next_entry; } openurl_update_popup(); #ifdef TRACE if (tl & (1u<<16)) Printf("history_expire: Successful, exitting through toolbars_set_all_button_states\n"); #endif return toolbars_set_all_button_states(); } /*************************************************/ /* history_count() */ /* */ /* Counts the size in memory of all strings and */ /* structures used by the History. */ /* */ /* Returns: Size of all strings and structs */ /* used by the History, in bytes. */ /*************************************************/ int history_count(void) { int count = 0; history_entry * entry = history_base; history_user * user; #ifdef TRACE if (tl & (1u<<16)) Printf("history_count: Called\n"); #endif while (entry) { /* Work out how much the entry takes up. First */ /* the structure itself. */ count += sizeof(entry); /* The title string, if present */ if (entry->title) count += strlen(entry->title) + 1; /* The URL description, if present (should always be!) */ if (entry->url) { /* This has a structure itself */ count += sizeof(url_description); /* Then add in all the strings it can carry */ if (entry->url->full) count += strlen(entry->url->full) + 1; if (entry->url->protocol) count += strlen(entry->url->protocol) + 1; if (entry->url->host) count += strlen(entry->url->host) + 1; if (entry->url->port) count += strlen(entry->url->port) + 1; if (entry->url->user) count += strlen(entry->url->user) + 1; if (entry->url->password) count += strlen(entry->url->password) + 1; if (entry->url->account) count += strlen(entry->url->account) + 1; if (entry->url->path) count += strlen(entry->url->path) + 1; if (entry->url->query) count += strlen(entry->url->query) + 1; if (entry->url->fragment) count += strlen(entry->url->fragment) + 1; } /* Now deal with any user records in this entry */ user = entry->users; while (user) { /* Only need to count the structure size itself here */ count += sizeof(history_user); user = user->next; } /* Next entry */ entry = entry->next; } #ifdef TRACE if (tl & (1u<<16)) Printf("history_count: Successful, returning %d\n", count); #endif return count; } /*************************************************/ /* history_limit() */ /* */ /* Counts the history size, and will expire the */ /* oldest item if it is over the given size. */ /* This continues until the history falls at or */ /* below the given size limit. */ /* */ /* This is quite a slow process. */ /* */ /* Parameters: Size in bytes at or below which */ /* the history is to fall on exit. */ /*************************************************/ _kernel_oserror * history_limit(unsigned int size) { int size_now; #ifdef TRACE if (tl & (1u<<16)) Printf("history_limit: Called with limit size %d\n",size); #endif do { size_now = history_count(); #ifdef TRACE if (tl & (1u<<16)) Printf("history_limit: Current size is %d\n",size_now); #endif if (size_now > size) { history_entry * entry = history_base; history_entry * old = NULL; int oldest = 0; /* If we're oversize, find the oldest item */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_limit: Need to remove entries\n"); #endif while (entry) { if (!oldest || entry->last_accessed < oldest) oldest = entry->last_accessed, old = entry; entry = entry->next; } /* Now expire it */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_limit: Expiring entry %p\n", old); #endif if (old) history_remove_entry(NULL, old); } /* Continue until we fall within the required size */ } while (size_now > size); /* Exit through a toolbar button update, as some local histories */ /* may well have been disturbed by the loss of History entries. */ openurl_update_popup(); #ifdef TRACE if (tl & (1u<<16)) Printf("history_limit: Successful, exitting through toolbars_set_all_button_states\n"); #endif return toolbars_set_all_button_states(); } /*************************************************/ /* history_visited() */ /* */ /* Returns 1 if a given URL is in the History, */ /* else 0. */ /* */ /* Parameters: Pointer to a null-terminated URL */ /* string to look for. */ /* */ /* Returns: 1 if a record of this can be */ /* found in the History, else 0. */ /*************************************************/ int history_visited(const char * url) { if (history_find_entry(url)) return 1; return 0; } /*************************************************/ /* history_empty() */ /* */ /* Inquire if there are any History items */ /* present for a given browser or the global */ /* History. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the enquiry, or NULL */ /* for the global History. */ /* */ /* Returns: 1 if there are no items, else 0. */ /*************************************************/ int history_empty(browser_data * b) { if (!b) return !history_base; return !b->history_current; } /*************************************************/ /* history_can_go_backwards() */ /* */ /* Reports whether or not there are more pages */ /* to go back to in the local History of a */ /* given browser. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the History. */ /* */ /* Returns: 1 if there are more items in the */ /* local history to go back to, else */ /* 0. */ /*************************************************/ int history_can_go_backwards(browser_data * b) { history_user * user; if (!b) return 0; user = (history_user *) b->history_current; if (!user || !user->history_prev) return 0; return 1; } /*************************************************/ /* history_can_go_forwards() */ /* */ /* Reports whether or not there are more pages */ /* to go forward to in the local History of a */ /* given browser. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the History. */ /* */ /* Returns: 1 if there are more items in the */ /* local history to go forwards to, */ /* else 0. */ /*************************************************/ int history_can_go_forwards(browser_data * b) { history_user * user; if (!b) return 0; user = (history_user *) b->history_current; if (!user || !user->history_next) return 0; return 1; } /*************************************************/ /* history_fetch_backwards() */ /* */ /* When called, will fetch the previous page in */ /* a given browser's local History. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the History; */ /* */ /* 1 to open the URL in a new window */ /* or 0 to open it in the window to */ /* which the browser_data struct is */ /* relevant. */ /*************************************************/ _kernel_oserror * history_fetch_backwards(browser_data * b, int new_view) { history_user * user; const char * url; #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_backwards: Called for %p\n", b); #endif if (!is_known_browser(b)) return NULL; /* Only proceed if we're not right at the start of the history */ user = (history_user *) b->history_current; if (!user || !user->history_prev) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_backwards: Can't go back any further, exitting\n"); #endif return NULL; } #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_backwards: Proceeding\n"); #endif /* Step backwards - even if we're going to open a new view, */ /* when the child inherits the parent's history this needs */ /* to have been set up to make sure the child gains the */ /* right position in the inherited history list. We will */ /* correct the parent's history position later. */ b->history_current = (void *) (user->history_prev); /* Get a pointer to the URL */ url = user->history_prev->parent->url->full; /* Flag that this will be a History based fetch */ b->from_history = 1; /* Get the URL in either the same window or a new window. */ if (!new_view) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting through fetchpage_new\n"); #endif /* Fetch the URL, flagging not to record this URL in the history */ return (fetchpage_new(b, (char *) url, 0, 1)); } else { #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting through windows_create_browser and browser_inherit\n"); #endif RetError(windows_create_browser((char *) url, NULL, NULL, NULL, Windows_CreateBrowser_Normal)); RetError(browser_inherit(b, last_browser)); /* We didn't end up moving anywhere in b, and only stepped backwards */ /* so that the indirectly called history_inherit could work properly */ /* - so now make sure that b's current history position has not been */ /* altered. */ b->history_current = (void *) (user); } return NULL; } /*************************************************/ /* history_fetch_forwards() */ /* */ /* When called, will fetch the next page in a */ /* given browser's local History. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the History; */ /* */ /* 1 to open the URL in a new window */ /* or 0 to open it in the window to */ /* which the browser_data struct is */ /* relevant. */ /*************************************************/ _kernel_oserror * history_fetch_forwards(browser_data * b, int new_view) { history_user * user; const char * url; #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_forwards: Called for %p\n", b); #endif if (!is_known_browser(b)) return NULL; /* Only proceed if we're not right at the start of the history */ user = (history_user *) b->history_current; if (!user || !user->history_next) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_forwards: Can't go forwards any further, exitting\n"); #endif return NULL; } #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_forwards: Proceeding\n"); #endif /* Step forwards - even if we're going to open a new view, */ /* when the child inherits the parent's history this needs */ /* to have been set up to make sure the child gains the */ /* right position in the inherited history list. We will */ /* correct the parent's history position later. */ b->history_current = (void *) (user->history_next); /* Get a pointer to the URL */ url = user->history_next->parent->url->full; /* Flag that this will be a History based fetch */ b->from_history = 1; /* Get the URL in either the same window or a new window. */ if (!new_view) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_forwards: Exitting through fetchpage_new\n"); #endif /* Fetch the URL, flagging not to record this URL in the history */ return (fetchpage_new(b, (char *) url, 0, 1)); } else { #ifdef TRACE if (tl & (1u<<16)) Printf("history_fetch_forwards: Exitting through windows_create_browser and browser_inherit\n"); #endif RetError(windows_create_browser((char *) url, NULL, NULL, NULL, Windows_CreateBrowser_Normal)); RetError(browser_inherit(b, last_browser)); /* We didn't end up moving anywhere in b, and only stepped forwards */ /* so that the indirectly called history_inherit could work properly */ /* - so now make sure that b's current history position has not been */ /* altered. */ b->history_current = (void *) (user); } return NULL; } /*************************************************/ /* history_menu_popup() */ /* */ /* Handles clicks on a history menu popup item. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the history to show, */ /* or NULL if this is for a global */ /* history from an OpenURL dialogue */ /* box; */ /* */ /* Object ID of the item holding the */ /* menu gadget; */ /* */ /* Component ID of the item that was */ /* clicked on; */ /* */ /* 1 to show the global history even */ /* if the first parameter is not */ /* NULL, else 0; */ /* */ /* 1 to show the URLs, 0 to show */ /* page titles where available. */ /*************************************************/ _kernel_oserror * history_menu_popup(browser_data * b, ObjectId object, ComponentId component, int global, int show_urls) { _kernel_oserror * e; WimpGetWindowStateBlock state; BBox menu; /* If there's already a menu open, close it */ /* (so the action is to toggle the menu). */ if ((menusrc == Menu_LocalHist || menusrc == Menu_GlobalHist) && menuhdl == b) { menusrc = Menu_None; menuhdl = NULL; return wimp_create_menu((void *) -1, 0, 0); } /* Get the Wimp handle for the tool bar and get the window state */ e = window_get_wimp_handle(0, object, &state.window_handle); if (e) return e; e = wimp_get_window_state(&state); if (e) return e; /* Get the bounding box of the popup icon that was used */ e = gadget_get_bbox(0, object, component, &menu); if (e) return e; /* Convert that to screen coords ready for opening the menu */ /* next to it. */ coords_box_toscreen(&menu, (WimpRedrawWindowBlock *) &state); if (component == URLBarHistoryMenuR || component == OpenHistory) { /* Build and show menu to right of menu icon for URLBarHistoryMenuR object */ e = (history_build_menu(b, menu.xmax - 2, menu.ymax, global, show_urls, 0)); if (e) return e; } else { /* Otherwise, show it to the left of the icon */ e = history_build_menu(b, menu.xmin - 4, menu.ymin + 4, global, show_urls, 1); if (e) return e; } return NULL; } /*************************************************/ /* history_compare_entries() */ /* */ /* A comparisson function for qsort(); compares */ /* the last_accessed fields of two given */ /* history_entry structures so that an array */ /* being sorted will have highest last_accessed */ /* field (newest) items first. */ /* */ /* Parameters: Pointer to the first */ /* history_entry struct as a void *. */ /* */ /* Pointer to the second */ /* history_entry struct as a void *. */ /*************************************************/ static int history_compare_entries(const void * first, const void * second) { history_entry * f = *((history_entry **) first); history_entry * s = *((history_entry **) second); if (f->last_accessed < s->last_accessed) return 1; /* We want the greatest last_accessed field to come first */ if (f->last_accessed > s->last_accessed) return -1; return 0; } /*************************************************/ /* history_build_menu() */ /* */ /* Builds a history menu for the given browser, */ /* showing it at the specified coordinates. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the menu, or NULL to */ /* build from the global history; */ /* */ /* x coordinate to show at; */ /* */ /* y coordinate to show at; */ /* */ /* 1 to show the global history even */ /* if the first parameter is not */ /* NULL; */ /* */ /* Non-0 to only show URLs, 0 to */ /* allow titles in the menu; */ /* */ /* Non-0 to subtract the width of */ /* the menu from the given show x */ /* coordinate (e.g. to show to the */ /* left of a given position) else 0. */ /*************************************************/ _kernel_oserror * history_build_menu(browser_data * b, int x, int y, int global, int show_urls, int subtract) { _kernel_oserror * e; wimp_menuhdr * mhp; wimp_menuitem * mip; char * menudata; int i; int size; int width, awidth, thiswidth; int len; /* Clear out any existing menu-related data */ free(history_menu); history_menu = NULL; free(entry_array); entry_array = NULL, nentries = 0; free(user_array); user_array = NULL, nusers = 0; #ifdef TRACE if (tl & (1u<<16)) Printf("history_build_menu: Called with %p\n", (void *) b); #endif /* If there are no History items, flag an error. */ if (!history_base) goto history_build_menu_empty_history; /* Work out the data size required for the menu structure. */ /* Can't just point the menu to the history list as it's a */ /* flex block, which may shift whilst the menu is open. */ size = sizeof(wimp_menuhdr); width = 8; awidth = 0; /* Loop round finding the string length of the longest entry */ /* in 'width' and the OS unit width of it in 'awidth'. */ _swix(Hourglass_On, 0); #ifdef TRACE if (tl & (1u<<16)) { Printf("\nhistory_build_menu: Widthing menu\n" "=================================\n\n"); } #endif if (b && !global) { history_user * current; /* Want a local (visit history). */ /* */ /* For this, we want to build a menu which shows where the forwards */ /* / backwards buttons will go, with the current position in the */ /* local History ticked. So we need to find the browser's current */ /* position, move right to the end of it, and build the menu from */ /* there. The build order will follow the history path by definition */ /* so no sorting is needed afterwards. */ current = (history_user *) b->history_current; if (!current) goto history_build_menu_empty_history; /* Track to the end of the local History */ while (current->history_next) current = current->history_next; /* Now add in each entry */ menu_entries = 0; while (current) { history_user ** new_array; const char * used; const char * url; const char * title = current->parent->title; url_description * url_d = current->parent->url; /* Rremember the item in the array */ new_array = realloc(user_array, (++nusers) * sizeof(history_user *)); if (!new_array) /* Zoiks! The allocation failed */ { goto history_build_menu_out_of_memory; } user_array = new_array; user_array[nusers - 1] = current; menu_entries ++; if (url_d) url = url_d->full; else url = ""; /* (So from the above, 'url' is never NULL) */ if (show_urls || !title || !*title) len = strlen(url), used = url; else len = strlen(title), used = title; #ifdef TRACE if (tl & (1u<<16)) { Printf("Local - URL : '%s'\n", url); Printf("Local - Title: '%s'\n\n", title ? title : ""); } #endif /* Account for an appended space to stop e.g. 'Home' being taken */ /* as a keyboard shortcut by the Wimp */ len ++; /* Work out the space requirement for this entry and record the */ /* widest entry in characters in 'width'. */ size += len + 1 + sizeof(wimp_menuitem); /* (+1 = string terminator) */ if (len > width) width = len; /* Find the width of the entry in OS units */ RetError(utils_text_width((char *) used, &thiswidth, 0)); /* Record the widest entry in OS units in 'awidth' */ if (thiswidth > awidth) awidth = thiswidth; /* Go on to the next (or rather, given the order we want the */ /* menu items in, previous!) item. */ current = current->history_prev; } } else { history_entry * entry = history_base; history_entry ** new_array; const char * used; const char * url; const char * title; url_description * url_d; /* Want a global history. */ /* */ /* We need to go through all history_entry structures, recording */ /* each on in the entry_array array so menu entry numbers can be */ /* associated with history_entry structures. */ menu_entries = 0; while (entry) { new_array = realloc(entry_array, (++nentries) * sizeof(history_user *)); if (!new_array) { goto history_build_menu_out_of_memory; } entry_array = new_array; entry_array[nentries - 1] = entry; /* Find the maximum width in chars and OS units of the URL or title strings */ title = entry_array[nentries - 1]->title; url_d = entry_array[nentries - 1]->url; /* From here on it's more or less identical to the local history code above */ if (url_d) url = url_d->full; else url = NULL; if (show_urls || !title || !*title) len = url ? strlen(url) : 0, used = url; else len = strlen(title), used = title; #ifdef TRACE if (tl & (1u<<16)) { Printf("Global - URL : '%s'\n", url ? url : ""); Printf("Global - Title: '%s'\n\n", title ? title : ""); } #endif len ++; size += len + 1 + sizeof(wimp_menuitem); if (len > width) width = len; RetError(utils_text_width((char *) used, &thiswidth, 0)); if (thiswidth > awidth) awidth = thiswidth; menu_entries ++; /* Next item */ entry = entry->next; } /* Sort the entries array based on the entry datestamps - */ /* newest (highest datestamp number) first. */ qsort(entry_array, nentries, sizeof(history_entry *), history_compare_entries); #ifdef TRACE if (tl & (1u<<16)) { history_entry * entry; Printf("Post-sorting, entry array looks like:\n\n"); for (i = 0; i < nentries; i++) { entry = entry_array[i]; Printf("Entry %04d - URL '%s'\n", i, entry->url ? entry->url->full : ""); } Printf("\n"); } #endif } size += 4; /* Deallocate any existing menu data and allocate the new required size. */ #ifdef TRACE if (tl & (1u<<16)) { if (history_menu) Printf("history_build_menu: Freeing existing store\n"); else Printf("history_build_menu: There is no existing store\n"); } #endif free(history_menu); history_menu = NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_build_menu: Attempting to malloc %d bytes\n",size); #endif history_menu = calloc(1, size); if (!history_menu) goto history_build_menu_out_of_memory; /* Point mhp to the start of the menu header, and mip */ /* to the first menu item (straight after the header). */ mhp = (wimp_menuhdr *) history_menu; mip = (wimp_menuitem *) (((int) mhp) + sizeof(wimp_menuhdr)); /* Fill in the header. */ strncpy(mhp->title, lookup_token("HistMenT:History",0,0), 12); mhp->tit_fcol = 7; mhp->tit_bcol = 2; mhp->work_fcol = 7; mhp->work_bcol = 0; mhp->width = awidth; mhp->height = 44; mhp->gap = 0; /* Pointer arithmetic - mip + menu_entries adds menu_entries */ /* lots of sizeof(mip) to menudata, since the cast to char * */ /* is the last thing that happens. So menudata points past */ /* all the menu structure stuff to the data area. */ menudata = (char *) (mip + menu_entries); /* Fill in each menu item. */ #ifdef TRACE if (tl & (1u<<16)) { Printf("\nhistory_build_menu: Building menu\n" "=================================\n\n"); } #endif for (i = 0; i < menu_entries; i++) { url_description * url_d; char * url; char * title; /* Find the URL or title string for this entry */ if (b && !global) { history_user * user = user_array[i]; url_d = user->parent->url; title = user->parent->title; url = url_d ? url_d->full : ""; #ifdef TRACE if (tl & (1u<<16)) { Printf("Local - URL : '%s'\n", url); Printf("Local - Title: '%s'\n\n", title ? title : ""); } #endif } else { history_entry * entry = entry_array[i]; url_d = entry->url; title = entry->title; url = url_d ? url_d->full : ""; #ifdef TRACE if (tl & (1u<<16)) { Printf("Global - URL : '%s'\n", url); Printf("Global - Title: '%s'\n\n", title ? title : ""); } #endif } /* Fill in the entry header */ mip->flags = ( (i == menu_entries - 1) ? wimp_MLAST : 0 ) | ( ( b && !global && user_array[i] == (history_user *) b->history_current ) ? wimp_MTICK : 0 ); mip->submenu = (wimp_menuptr) -1; mip->iconflags = wimp_ITEXT | wimp_IFILLED | wimp_INDIRECT | (7<<24); mip->data.indirecttext.validstring = NULL; mip->data.indirecttext.bufflen = 0; mip->data.indirecttext.buffer = menudata; /* Use the URL if we're told to, or the title is NULL / a null string */ if (show_urls || !title || !*title) { /* Use the URL */ strcpy(menudata, url); strcat(menudata, " "); /* To stop e.g. 'Home' at the end of an item being taken as a keyboard shortcut by the Wimp */ len = strlen(menudata); /* Advance the data pointer, possibly removing any */ /* CGI information if HIDE_CGI is defined inside */ /* the compiler. */ #ifdef HIDE_CGI toolbars_hide_cgi(menudata); #else /* If not hiding all CGI information, still don't want to */ /* put all the CGI stuff in the menu or it can get far too */ /* wide. So leave an indicator to show there was CGI info. */ toolbars_hide_cgi(menudata); if (len > strlen(menudata) + 7) strcat(menudata, " (+CGI)"); else if (len > strlen(menudata) + 4) strcat(menudata, "?..."); #endif } else { /* Use the title */ strcpy(menudata, title); strcat(menudata, " "); /* To stop e.g. 'Home' at the end of an item being taken as a keyboard shortcut by the Wimp */ len = strlen(menudata); } /* If we've gone over the width limit on this entry, */ /* show the right hand portion of the item with '...' */ /* before it. */ if (len + 4 > Limits_HistoryMenuItemSize) /* '+4' to account for the '...' we'll put in front plus terminator */ { memmove(menudata + 3, menudata + len - (Limits_HistoryMenuItemSize - 3), Limits_HistoryMenuItemSize - 3); strncpy(menudata, "...", 3); menudata[Limits_HistoryMenuItemSize - 1] = '\0'; } menudata += strlen(menudata) + 1; /* Next item... */ mip ++; } #ifdef TRACE if (menudata > ((char *) history_menu) + size) { erb.errnum = 0; sprintf(erb.errmess,"Fatal error inside history_build_menu: Overran menu buffer! Allocated %d bytes, then used %d.",size,(int) menudata - (int) history_menu); show_error(&erb); } #endif /* Finally, open the menu */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_build_menu: Exitting through wimp_create_menu\n"); #endif menuhdl = (void *) b; if (global) menusrc = Menu_GlobalHist; else menusrc = Menu_LocalHist; e = wimp_create_menu(history_menu, x - (subtract ? (mhp->width + 64) : 0), y); _swix(Hourglass_Off, 0); return e; /* Forced exits... */ history_build_menu_out_of_memory: _swix(Hourglass_Off, 0); free(history_menu); history_menu = NULL; free(entry_array); entry_array = NULL, nentries = 0; free(user_array); user_array = NULL, nusers = 0; /* Out of memory */ erb.errnum = Utils_Error_Custom_Normal; StrNCpy0(erb.errmess, lookup_token("NoMemLHi:There is not enough free memory to open the history menu.", 0, 0)); #ifdef TRACE if (tl & (1u<<16)) Printf("history_build_menu: Exitting (out of memory)\n"); #endif return &erb; history_build_menu_empty_history: _swix(Hourglass_Off, 0); free(history_menu); history_menu = NULL; free(entry_array); entry_array = NULL, nentries = 0; free(user_array); user_array = NULL, nusers = 0; /* Empty history (nothing at all, nothing for a given */ /* browser, or whatever) */ erb.errnum = Utils_Error_Custom_Message; StrNCpy0(erb.errmess, lookup_token("EmptyHistE:The history list is empty.", 0, 0)); #ifdef TRACE if (tl & (1u<<16)) Printf("history_build_menu: Exitting (history is empty)\n"); #endif return &erb; } /*************************************************/ /* history_menu_selection() */ /* */ /* Jumps to a URL according to the item selected */ /* in a history menu. */ /* */ /* If Adjust is used the menu is not reopened as */ /* the fetch will occur in a new window (as with */ /* following page links) - it doesn't make sense */ /* to reopen the menu in this case. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the history, or NULL */ /* for the global history - in this */ /* case it is assumed that the menu */ /* opened from an Open URL dialogue */ /* and appropriate alternative */ /* functions will be called; */ /* */ /* Pointer to a WimpPollBlock struct */ /* from which the menu item that was */ /* selected may be determined. */ /*************************************************/ _kernel_oserror * history_menu_selection(browser_data * b, WimpPollBlock * block) { int item, adj; int global; const char * url = NULL; /* What type of menu was it? */ if (menusrc == Menu_GlobalHist || !b) global = 1; else global = 0; /* Flag that there is no known menu source anymore */ /* and fetch the new URL. */ menusrc = Menu_None; /* What item was clicked on? */ item = block->menu_selection[0]; /* If the menu selection appears to be out of range, */ /* exit quietly. */ if ( item < 0 || item >= menu_entries || ( global && item >= nentries ) || ( !global && item >= nusers ) ) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_menu_selection: Warning, menu selection out of range\n"); #endif return NULL; } /* Otherwise find out the button that was used. */ adj = controls.ignore_adjust ? 0 : adjust(); /* If a new window isn't going to be opened, */ /* need to remember that we've just dived */ /* into the History list so that forwards / */ /* backwards work correctly. */ if (!adj && b && !global) { history_user * user = user_array[item]; b->history_current = (void *) user; } /* Point to the required URL */ if (!global) { history_user * user = user_array[item]; if (user->parent && user->parent->url) url = user->parent->url->full; } else { history_entry * entry = entry_array[item]; if (entry->url) url = entry->url->full; } /* If b is set, this is from a browser toolbar */ if (b) { /* Somewhat non-standard behaviour to have an adjust-click */ /* open a new window instead of leaving the menu up, but */ /* this is more consistent with the rest of the browser UI. */ if (!adj) return fetchpage_new(b, url, 0, 1); else return windows_create_browser((char *) url, NULL, NULL, NULL, Windows_CreateBrowser_Normal); } /* Otherwise, if b is NULL, it's from an Open URL dialogue */ else { return openurl_fill_in_url((char *) url); } return NULL; } /*************************************************/ /* history_load() */ /* */ /* Loads the global history from the given path. */ /* Will raise errors (usually, due to RISC OS */ /* C's file I/O, the wrong ones...) if there is */ /* a failure during loading, but not if the file */ /* refuses to open or the number of items cannot */ /* be read from it (i.e. a missing or zero */ /* length History file). */ /* */ /* Parameters: Pointer to the full pathname for */ /* the history file. */ /*************************************************/ _kernel_oserror * history_load(char * pathname) { FILE * file; char * title = NULL; char * url = NULL; static char * local_path = NULL; int result; int last_accessed, title_len, url_len; int items, item; int cc, ch; if (!pathname || !*pathname) return NULL; local_path = malloc(strlen(pathname) + 1); if (!local_path) return NULL; strcpy(local_path, pathname); #ifdef TRACE if (tl & (1u<<16)) Printf("history_load: Called\n"); #endif file = fopen(local_path, "rb"); if (!file) { free(local_path); return NULL; /* Fail silently - there may be no History file; this is OK */ } /* Read how many items there are - again file silently, the */ /* file may just be zero bytes long. */ #ifdef TRACE if (tl & (1u<<16)) Printf("history_load: File opened OK\n"); #endif result = fscanf(file, "%d\n", &items); if (result == EOF) { fclose(file); free(local_path); return NULL; } #ifdef TRACE if (tl & (1u<<16)) { if (items > 1) Printf("history_load: There are %d items\n",items); else Printf("history_load: There is 1 item\n"); } #endif _swix(Hourglass_On, 0); for (item = 0; item < items; item++) { _swix(Hourglass_Percentage, _IN(0), (100 * item) / items); /* Read the last accessed time and required string lengths */ result = fscanf(file, "%d,%d,%d\n", &last_accessed, &title_len, &url_len); if (result == EOF) goto history_load_exit; /* Allocate buffers for the strings */ free(title); title = malloc(title_len + 1); if (!title) { fclose(file); _swix(Hourglass_Off,0); free(local_path); return make_no_memory_error(27); } free(url); url = malloc(url_len + 1); if (!url) { fclose(file); _swix(Hourglass_Off,0); free(title); free(local_path); return make_no_memory_error(27); } /* Read the title */ for (cc = 0; cc < title_len; cc++) { int ch = fgetc(file); if (ch == EOF) goto history_load_exit; title[cc] = (char) ch; } title[title_len] = 0; /* Hmm, should not be needed? But it seems fscanf reads one \n extra */ /* when there is a NULL title string. It probably thinks the line */ /* ending type is '\n\n' or something. */ if (title_len) { /* Skip the '\n' */ ch = fgetc(file); if (ch == EOF) goto history_load_exit; } /* Read the URL */ for (cc = 0; cc < url_len; cc++) { int ch = fgetc(file); if (ch == EOF) goto history_load_exit; url[cc] = (char) ch; } url[url_len] = 0; /* Skip the '\n' */ ch = fgetc(file); if (ch == EOF) goto history_load_exit; #ifdef TRACE if (tl & (1u<<16)) { Printf("history_load: %04d Title = \0213'%s'\0217\n", item, title); Printf(" URL = \0216'%s'\0217\n", url); } #endif /* Now make the entry */ if (url_len) { history_entry * found = malloc(sizeof(history_entry)); if (!found) { #ifdef TRACE if (tl & (1u<<16)) Printf("history_load: Couldn't allocate space for entry\n"); #endif fclose(file); _swix(Hourglass_Off,0); free(url); free(title); free(local_path); return make_no_memory_error(25); } found->url = urlutils_return_description(url); /* We've now finished with the temporary URL string */ free(url); url = NULL; if (!found->url) { free(found); #ifdef TRACE if (tl & (1u<<16)) Printf("history_load: Couldn't allocate space for URL description\n"); #endif fclose(file); _swix(Hourglass_Off,0); free(url); free(title); free(local_path); history_remove_entry(NULL, found); return make_no_memory_error(25); } else { /* Create a hash number for the string */ found->hash = utils_return_hash(found->url->full); } /* Link in the structure */ found->prev = NULL; found->next = history_base; if (history_base) history_base->prev = found; history_base = found; /* Fill in some other fields */ found->title = title; /* Use the title string directly */ found->users = NULL; found->last_accessed = last_accessed; title = NULL; #ifdef TRACE if (tl & (1u<<16)) Printf("history_load: OK, have entry %p with description block %p\n",found,found->url); #endif } /* (Closure of 'for' loop) */ } fclose(file); #ifdef TRACE if (tl & (1u<<16)) Printf("history_load: Successful, exitting via. expiry functions\n"); #endif _swix(Hourglass_Off,0); free(url); free(title); free(local_path); if (choices.expiry_age) RetError(history_expire(NULL, time(NULL) - choices.expiry_age)); if (choices.max_size) return history_limit(choices.max_size); else return NULL; /* Error condition exit routine */ history_load_exit: if (file) fclose(file); _swix(Hourglass_Off, 0); free(url); free(title); free(local_path); RetLastE; } /*************************************************/ /* history_save() */ /* */ /* Saves the global history to the given path. */ /* */ /* Parameters: Pointer to the full pathname for */ /* the history file. */ /*************************************************/ _kernel_oserror * history_save(char * pathname) { history_entry * entry = history_base; FILE * file; static char * local_path = NULL; int wrote; int items = 0; if (!pathname || !*pathname) return NULL; /* Canonicalise the path */ RetError(utils_canonicalise_path(pathname, &local_path)); /* Ensure it is present */ { _kernel_oserror * e = utils_build_tree(local_path); if (e) { free(local_path); return e; } } /* How many items are there? */ while (entry) { items ++; entry = entry->next; } entry = history_base; /* Create the file */ file = fopen(local_path, "wb"); if (!file) { free(local_path); RetLastE; } /* Write the number of items */ wrote = fprintf(file, "%d\n", items); if (wrote <= 0) { fclose(file); free(local_path); RetLastE; } /* Write the item contents */ while (entry) { if (entry->url) { wrote = fprintf(file, "%d,%d,%d\n%s\n%s\n", entry->last_accessed, entry->title ? strlen(entry->title) : 0, entry->url->full ? strlen(entry->url->full) : 0, entry->title ? entry->title : "", entry->url->full ? entry->url->full : ""); if (wrote <= 0) { fclose(file); free(local_path); RetLastE; } } entry = entry->next; } /* Close the file and exit */ fclose(file); free(local_path); return NULL; } /*************************************************/ /* history_save_as_html() */ /* */ /* This function saves the History as an HTML */ /* file. */ /* */ /* Parameters: Pointer to the filename to save */ /* to (null terminated); */ /* */ /* Pointer to a browser_data struct */ /* to save the History for if you */ /* want a local history, else NULL */ /* for the global history. */ /*************************************************/ _kernel_oserror * history_save_as_html(char * pathname, browser_data * b) { _kernel_oserror * e; static char * local_path = NULL; history_entry * entry = history_base; history_user * user; FILE * fileptr; int written; if (!pathname || !*pathname) return NULL; local_path = malloc(strlen(pathname) + 1); if (!local_path) return NULL; strcpy(local_path, pathname); /* Could take a while... */ _swix(Hourglass_On, 0); /* Open the file for writng */ fileptr = fopen(local_path, "wb"); /* Complain if it fails */ if (fileptr == NULL) { free(local_path); RetLastE; } /* Write the file header */ HistoryWrite(fprintf(fileptr, "<html>\n" "<head>\n" CHARSET_SPECIFIER "<title>")); HistoryWrite(fprintf(fileptr, lookup_token("HistoryHTMLTitle:History",0,0))); HistoryWrite(fprintf(fileptr, "</title>\n" "</head>\n" "<body>\n" "<ul>\n")); /* Fill in the body for global histories */ if (!b) { while (entry) { if (entry->url && entry->url->full) { HistoryWrite(fprintf(fileptr, "<li><a href=\"%s\">%s</a>\n", entry->url->full, (entry->title && *entry->title) ? entry->title : entry->url->full)); } entry = entry->next; } } /* Fill in the body for local histories */ else { user = (history_user *) b->history_current; while (user && user->history_next) user = user->history_next; while (user) { entry = user->parent; if (entry && entry->url && entry->url->full) { HistoryWrite(fprintf(fileptr, "<li><a href=\"%s\">%s</a>\n", entry->url->full, (entry->title && *entry->title) ? entry->title : "")); } user = user->history_prev; } } /* Write the footer and close the file */ HistoryWrite(fprintf(fileptr, "</ul>\n")); HistoryWrite(fprintf(fileptr, "</body>\n")); HistoryWrite(fprintf(fileptr, "</html>\n")); fclose(fileptr); _swix(Hourglass_Off, 0); /* Set the filetype to HTML (0xfaf) */ e = _swix(OS_File, _INR(0,2), 18, local_path, FileType_HTML); free(local_path); return e; /* Error condition exit */ history_save_error: if (fileptr) { fclose(fileptr); _swix(Hourglass_Off, 0); } free(local_path); RetLastE; } /*************************************************/ /* history_find_match() */ /* */ /* Takes a string from the given buffer and sees */ /* if there's something in the global History */ /* that matches it in some way. */ /* */ /* If it finds something, it writes it back to */ /* the buffer and returns 1. */ /* */ /* Parameters: Pointer to the buffer holding the */ /* string to try and match; */ /* */ /* Size of the buffer in bytes. */ /* */ /* Returns: 1 if the buffer is updated with a */ /* match string, else 0 (buffer */ /* contents will be unaltered). */ /*************************************************/ int history_find_match(char * buffer, int buffer_size) { history_entry * entry = history_base; history_entry * lowest_entry = NULL; const char * found = NULL; int lowest_offset = -1; int lowest_diff = 0; int this_offset = 0; int got_one; if (!buffer || !*buffer) return 0; while (entry) { if (entry->url && entry->url->full) { got_one = 0; /* Match in the host, if present */ if (entry->url->host) { /* See if we can find the string */ found = strstr(entry->url->host, buffer); if (found) { /* If so, record the offset into the string where */ /* the match was found and mark we've got a match. */ this_offset = found - entry->url->host; got_one = 1; } } /* Match in the path, if nothing found in the host */ if (!got_one && entry->url->path) { found = strstr(entry->url->path, buffer); if (found) { this_offset = found - entry->url->path; got_one = 1; } } /* Match in the full URL, if all else fails */ if (!got_one) { found = strstr(entry->url->full, buffer); if (found) { this_offset = found - entry->url->full; got_one = 1; } } /* Match in the title, out of desperation! */ if (!got_one && entry->title) { found = strstr(entry->title, buffer); if (found) { this_offset = found - entry->title; got_one = 1; } } /* If found, have we not recorded an offset before, or is this offset */ /* lower than any previously recorded? If so, remember the entry. This */ /* way, 'digital' will match 'www.digital.com' rather than */ /* 'www.altavista.digital.com' regardless of where the entry is in the */ /* History. Matching the first one we come to would obviously rely on */ /* the order of appearance in the list. */ if (got_one) { if (lowest_offset < 0 || this_offset <= lowest_offset) { int this_diff = strlen(entry->url->full) - strlen(buffer); lowest_offset = this_offset; /* Having found an entry that matches in the lowest part of the string */ /* so far, we also want to choose one which has the smallest string */ /* length difference between itself and the given match string, which */ /* is non-zero. This ensures we would go 'www.acorn.com/acorn/', */ /* 'www.acorn.com/acorn/news/', 'www.acorn.com/acorn/news/releases/' */ /* again regardless of the relative position of the entries in the */ /* History list. */ /* */ /* Of course, the whole thing is still fairly undetermined - whatever */ /* the next directory is after you've found a match on the root of a */ /* site (say) which happens to be the shortest will be the tree you */ /* always end up fetching. But it's a reasonable start on this anyway, */ /* and the mists of time may well reveal that further enhancements are */ /* not necessary in practice. */ if (this_offset < lowest_offset) { /* First thing, we've found a new lowest offset; so forget any previous */ /* string length differences. */ lowest_diff = 0; } /* If we've not got a lowest difference yet, or we have a current difference */ /* and it's lower than the recorded lowest, then remember the difference and */ /* the entry that generated it. */ if (!lowest_diff || (this_diff && this_diff < lowest_diff)) { lowest_diff = this_diff; lowest_entry = entry; } } } } /* Try the next item */ entry = entry->next; } /* Did we end up finding anything? */ if (lowest_entry && lowest_offset >= 0) { /* Yes - copy the full URL into the buffer and return, saying */ /* we updated the buffer contents. */ strncpy(buffer, lowest_entry->url->full, buffer_size); buffer[buffer_size - 1] = 0; return 1; } /* No - exit, saying we didn't touch the buffer contents. */ return 0; }