/* 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 : ImgHistory.c */ /* */ /* Purpose: Remembering image sizes, in case the */ /* HTML doesn't specify it. */ /* */ /* Author : A.D.Hodgkinson */ /* */ /* History: 22-Nov-97: Created. */ /***************************************************/ #include <stdlib.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 "svcprint.h" #include "Global.h" #include "Utils.h" #include "ChoiceDefs.h" #include "Images.h" #include "Save.h" /* For Save_ScrapFile definition */ #include "URLutils.h" /* For FileMethod definition */ #include "ImgHistory.h" /* Image history structure. It's so small that these */ /* are kept in an array (one malloc block). */ typedef struct imghistory_entry { char * url; unsigned int hash; int os_x; int os_y; unsigned int last_accessed; } imghistory_entry; /* Static variables */ static imghistory_entry * imghistory_base = NULL; int imghistory_entries = 0; /* Local statics */ static int imghistory_find_entry (const char ** url_base, int url_offset); static void imghistory_remove_entry (int entry); static int imghistory_count (void); /*************************************************/ /* imghistory_find_entry() */ /* */ /* Searches for a given image in the image */ /* history, and returns an array index for that */ /* image if it is present, else -1. */ /* */ /* If the parameters seem odd, it may help to */ /* realise that this allows a flex block to hold */ /* the URL without any worries about flex */ /* shifting. */ /* */ /* Parameters: Pointer to a pointer to a block */ /* containing the image's full URL; */ /* */ /* Offset into the block at which */ /* the image lies. */ /* */ /* Returns: Index in the image history array */ /* of the entry representing the */ /* image if found, else -1. */ /*************************************************/ static int imghistory_find_entry(const char ** url_base, int url_offset) { int entry; unsigned int hash; if (!imghistory_entries || !url_base) return -1; hash = utils_return_hash(*url_base + url_offset); /* Go through the array */ for (entry = 0; entry < imghistory_entries; entry++) { if (imghistory_base[entry].hash == hash) { /* Use strcmp. We're going to be very strict in this; strcmp is not */ /* a technically valid way to compare URLs, but it's fast and you */ /* don't need much storage space for the URLs themselves. The image */ /* history is not sufficiently important to warrant anything more */ /* robust than this. */ if (!strcmp(imghistory_base[entry].url, *url_base + url_offset)) return entry; } } /* Nothing found */ return -1; } /*************************************************/ /* imghistory_record() */ /* */ /* Records a given image in the image history. */ /* If an entry is already present for the image, */ /* it just updates the entry's last_accessed */ /* field and ensures the size is set to the */ /* given values. */ /* */ /* Parameters: Pointer to a pointer to a block */ /* containing the image's full URL; */ /* */ /* Offset into the block at which */ /* the image lies; */ /* */ /* x size of the image, in OS units; */ /* */ /* y size of the image, in OS units. */ /*************************************************/ _kernel_oserror * imghistory_record(const char ** url_base, int url_offset, int os_x, int os_y) { imghistory_entry * new_base; char * new_url; int entry; if (!url_base) return NULL; /* Don't store scrap file transfer images */ if (!strncmp(*url_base + url_offset, FileMethod, sizeof(FileMethod) - 1)) { char test_url[Limits_URL]; /* Build the URL we'd be on for a scrap file fetch */ StrNCpy0(test_url, Save_ScrapFile); urlutils_pathname_to_url(test_url, sizeof(test_url)); /* If it's the same as the URL we've been given, don't store */ /* this image in the image history. */ if (!strcmp(*url_base + url_offset, test_url)) return NULL; } /* Try to find the item */ entry = imghistory_find_entry(url_base, url_offset); /* Found it */ if (entry >= 0) { imghistory_base[entry].last_accessed = time(NULL); /* Ensure the recorded size matches that given! */ imghistory_base[entry].os_x = os_x; imghistory_base[entry].os_y = os_y; /* That's all we need to do in this case */ return NULL; } /* Oh well, it wasn't there. So have to make a new entry */ new_base = realloc(imghistory_base, sizeof(imghistory_entry) * (imghistory_entries + 1)); /* Claim failed, so exit with error */ if (!new_base) return make_no_memory_error(28); /* OK, managed that; set imghistory_base to whatever was returned */ /* by realloc but don't increment the items counter yet */ imghistory_base = new_base; /* Try to allocate space for the URL too */ new_url = malloc(strlen(*url_base + url_offset) + 1); /* If this claim fails, don't bother shrinking the array back; */ /* we haven't increased the item count, so things will take */ /* care of themselves on the next addition (assuming that the */ /* lack of memory doesn't prove catastrophic elsewhere!). */ if (!new_url) return make_no_memory_error(28); /* If we reach here, both claims were successful */ imghistory_base[imghistory_entries].url = new_url; imghistory_base[imghistory_entries].os_x = os_x; imghistory_base[imghistory_entries].os_y = os_y; imghistory_base[imghistory_entries].last_accessed = time(NULL); strcpy(new_url, *url_base + url_offset); imghistory_base[imghistory_entries].hash = utils_return_hash(new_url); imghistory_entries ++; /* Call expiry functions */ if (choices.image_expiry_age) imghistory_expire(time(NULL) - choices.image_expiry_age); if (choices.image_max_size) imghistory_limit(choices.image_max_size); #ifdef SINGLE_USER /* Finished - exit by saving, if required */ if (choices.save_image_history == Choices_SaveImageHistory_Always) { return imghistory_save(lookup_choice("ImageHistorySave:Browse:User.Images",0,0)); } #endif return NULL; } /*************************************************/ /* imghistory_return_size() */ /* */ /* Returns the size of an image based on the */ /* given URL. If the image has no entry in the */ /* image history, -1 is returned for the x and */ /* y sizes. */ /* */ /* Parameters: Pointer to a pointer to a block */ /* containing the image's full URL; */ /* */ /* Offset into the block at which */ /* the image lies; */ /* */ /* Pointer to an int, in which the x */ /* size of the image is returned in */ /* OS units, or -1 if the image has */ /* no entry in the image history; */ /* */ /* Similarly, pointer to an int for */ /* the y size. */ /* */ /* Assumes: The int pointers may be NULL (but */ /* this would of course be pretty */ /* pointless!). */ /*************************************************/ void imghistory_return_size(const char ** url_base, int url_offset, int * os_x, int * os_y) { int entry; if (os_x) *os_x = -1; if (os_y) *os_y = -1; if (!url_base) return; /* Try and find the entry for this image */ entry = imghistory_find_entry(url_base, url_offset); /* Not found */ if (entry < 0) return; /* Found, so return the sizes and exit */ if (os_x) *os_x = imghistory_base[entry].os_x; if (os_y) *os_y = imghistory_base[entry].os_y; return; } /*************************************************/ /* imghistory_remove_entry() */ /* */ /* Removes an entry from the image history */ /* array by copying the last item over it and */ /* shrinking the array size. */ /* */ /* Parameters: Entry number. */ /*************************************************/ static void imghistory_remove_entry(int entry) { if (entry < 0 || entry >= imghistory_entries) return; /* If this is the last item, only need to shrink */ /* the array and decrement the item counter. Else, */ /* copy the top item over the one being removed */ /* and then shrink the array. */ if (entry < imghistory_entries - 1) { memcpy(&imghistory_base[entry], &imghistory_base[imghistory_entries - 1], sizeof(imghistory_entry)); } imghistory_entries --; /* Shouldn't fail, but if it does... Well, we ditch the history. */ imghistory_base = realloc(imghistory_base, imghistory_entries * sizeof(imghistory_entry)); /* Finished */ return; } /*************************************************/ /* imghistory_expire() */ /* */ /* Remove all entiries for images which have */ /* last been seen before a given time (i.e. are */ /* greater than a certain age). */ /* */ /* Parameters: Time (in time() function format) */ /* which an image must not have been */ /* visited on or since for expiry to */ /* take place - so to expire 1 day */ /* old images, say, you'd pass */ /* time() - 60*60*24. */ /*************************************************/ void imghistory_expire(unsigned int time) { int entry = 0; /* Go through removing items. The removal routine copies the last */ /* array item over the one being removed, so we only imcrement */ /* the entry number if nothing has been removed. */ while (entry < imghistory_entries) { if (imghistory_base[entry].last_accessed <= time) imghistory_remove_entry(entry); else entry++; } return; } /*************************************************/ /* imghistory_count() */ /* */ /* Count the total space used by the image */ /* history. */ /* */ /* Returns: Space occupied by the image */ /* history array and strings */ /* attached to this, including their */ /* terminators, in bytes. */ /*************************************************/ static int imghistory_count(void) { int count = 0; int entry; for (entry = 0; entry < imghistory_entries; entry ++) { count += sizeof(imghistory_entry); count += imghistory_base[entry].url ? strlen(imghistory_base[entry].url) + 1 : 0; } return count; } /*************************************************/ /* imghistory_limit() */ /* */ /* Limit the space occupied by the image history */ /* to a given amount. */ /* */ /* Parameters: Maximum size the history may be, */ /* in bytes. */ /*************************************************/ void imghistory_limit(unsigned int size) { int entry; int oldest; int found; /* Keep removing items as long as the actual size is */ /* greater than the maximum size specified. */ while (imghistory_count() > size) { /* Find the oldest entry */ oldest = 0; found = 0; for (entry = 0; entry < imghistory_entries; entry++) { if ( imghistory_base[entry].last_accessed < oldest || !oldest ) oldest = imghistory_base[entry].last_accessed, found = entry; } /* Remove the oldest entry, if found */ if (oldest) imghistory_remove_entry(found); } /* Finished */ return; } /*************************************************/ /* imghistory_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 * imghistory_load(const char * pathname) { FILE * file; char * url = NULL; static char * local_path = NULL; int result; int last_accessed, os_x, os_y, 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); 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. */ result = fscanf(file, "%d\n", &items); if (result == EOF) { fclose(file); free(local_path); return NULL; } /* Allocate space for the array */ imghistory_base = malloc(sizeof(imghistory_entry) * items); if (!imghistory_base) { fclose(file); free(local_path); return make_no_memory_error(29); } imghistory_entries = 0; /* We'll increment this as each gets loaded successfully */ /* Now load each entry's details */ _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,%d\n", &last_accessed, &os_x, &os_y, &url_len); if (result == EOF) goto imghistory_load_exit; /* Allocate buffers for the strings */ url = malloc(url_len + 1); if (!url) { imghistory_entries = item; /* E.g. if item 0 fails to load, we say there are zero entries */ fclose(file); _swix(Hourglass_Off,0); free(local_path); return make_no_memory_error(29); } /* Read the URL */ for (cc = 0; cc < url_len; cc++) { int ch = fgetc(file); if (result == ch) goto imghistory_load_exit; url[cc] = (char) ch; } url[url_len] = 0; /* Skip the '\n' */ ch = fgetc(file); if (result == ch) goto imghistory_load_exit; /* Now make the entry */ imghistory_base[item].url = url; imghistory_base[item].hash = utils_return_hash(url); url = NULL; /* Make sure we don't go and free it or something! */ imghistory_base[item].os_x = os_x; imghistory_base[item].os_y = os_y; imghistory_base[item].last_accessed = last_accessed; /* Successful addition */ imghistory_entries ++; /* (Closure of 'for' loop) */ } fclose(file); _swix(Hourglass_Off,0); free(local_path); if (choices.image_expiry_age) imghistory_expire(time(NULL) - choices.image_expiry_age); if (choices.image_max_size) imghistory_limit(choices.image_max_size); return NULL; /* Error condition exit */ imghistory_load_exit: fclose(file); _swix(Hourglass_Off,0); free(url); free(local_path); RetLastE; } /*************************************************/ /* imghistory_save() */ /* */ /* Saves the global history to the given path. */ /* */ /* Parameters: Pointer to the full pathname for */ /* the history file. */ /*************************************************/ _kernel_oserror * imghistory_save(const char * pathname) { static char * local_path = NULL; FILE * file; int entry; int wrote; 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; } } /* Create the file */ file = fopen(local_path, "wb"); if (!file) { free(local_path); RetLastE; } /* Write the number of items */ wrote = fprintf(file, "%d\n", imghistory_entries); if (wrote <= 0) { fclose(file); free(local_path); RetLastE; } /* Write the item contents */ if (imghistory_entries) { for (entry = 0; entry < imghistory_entries; entry++) { wrote = fprintf(file, "%d,%d,%d,%d\n%s\n", imghistory_base[entry].last_accessed, imghistory_base[entry].os_x, imghistory_base[entry].os_y, imghistory_base[entry].url ? strlen(imghistory_base[entry].url) : 0, imghistory_base[entry].url ? imghistory_base[entry].url : ""); if (wrote <= 0) { fclose(file); free(local_path); RetLastE; } } } /* Close the file and exit */ fclose(file); free(local_path); return NULL; } /*************************************************/ /* imghistory_empty() */ /* */ /* Find out whether or not the image history has */ /* any entries in it. */ /* */ /* Returns: 1 if there are no entries (the */ /* image history is empty), else 0. */ /*************************************************/ int imghistory_empty(void) { return !imghistory_entries; }