diff --git a/src/app.c b/src/app.c

index 5d27618b..82628c9c 100644

--- a/src/app.c

+++ b/src/app.c

@@ -194,6 +194,7 @@ static iString *serializePrefs_App_(const iApp *d) {

 appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent);

 appendFormat_String(str, "smoothscroll arg:%d\n", d->prefs.smoothScrolling);

 appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling);

+ appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize);

 appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs);

 appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth);

 appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph);

@@ -321,6 +322,7 @@ iObjectList *listDocuments_App(void) {



static void saveState_App_(const iApp *d) {

 iUnused(d);

+ trimCache_App();

 iFile *f = newCStr_File(concatPath_CStr(dataDir_App_, stateFileName_App_));

 if (open_File(f, writeOnly_FileMode)) {

     writeData_File(f, magicState_App_, 4);

@@ -503,7 +505,7 @@ const iString *debugInfo_App(void) {

 iString *msg = collectNew_String();

 format_String(msg, "# Debug information\n");

 appendFormat_String(msg, "## Documents\n");

- iForEach(ObjectList, k, listDocuments_App()) {

+ iForEach(ObjectList, k, iClob(listDocuments_App())) {

     iDocumentWidget *doc = k.object;

     appendFormat_String(msg, "### Tab %zu: %s\n",

                         childIndex_Widget(constAs_Widget(doc)->parent, k.object),

@@ -857,6 +859,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {

                      isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));

     postCommandf_App("decodeurls arg:%d",

                      isSelected_Widget(findChild_Widget(d, "prefs.decodeurls")));

+ postCommandf_App("cachesize.set arg:%d",

+ toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize"))));

     postCommandf_App("proxy.gemini address:%s",

                      cstr_String(text_InputWidget(findChild_Widget(d, "prefs.proxy.gemini"))));

     postCommandf_App("proxy.gopher address:%s",

@@ -940,6 +944,33 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe

 return doc;

}



+void trimCache_App(void) {

+ iApp *d = &app_;

+ size_t cacheSize = 0;

+ const size_t limit = d->prefs.maxCacheSize * 1000000;

+ iObjectList *docs = listDocuments_App();

+ iForEach(ObjectList, i, docs) {

+ cacheSize += cacheSize_History(history_DocumentWidget(i.object));

+ }

+ init_ObjectListIterator(&i, docs);

+ iBool wasPruned = iFalse;

+ while (cacheSize > limit) {

+ iDocumentWidget *doc = i.object;

+ const size_t pruned = pruneLeastImportant_History(history_DocumentWidget(doc));

+ if (pruned) {

+ cacheSize -= pruned;

+ wasPruned = iTrue;

+ }

+ next_ObjectListIterator(&i);

+ if (!i.value) {

+ if (!wasPruned) break;

+ wasPruned = iFalse;

+ init_ObjectListIterator(&i, docs);

+ }

+ }

+ iRelease(docs);

+}

+

static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {

 iApp *d = &app_;

 if (equal_Command(cmd, "ident.temp.changed")) {

@@ -1154,6 +1185,13 @@ iBool handleCommand_App(const char *cmd) {

     postCommandf_App("theme.changed auto:1");

     return iTrue;

 }

+ else if (equal_Command(cmd, "cachesize.set")) {

+ d->prefs.maxCacheSize = arg_Command(cmd);

+ if (d->prefs.maxCacheSize <= 0) {

+ d->prefs.maxCacheSize = 0;

+ }

+ return iTrue;

+ }

 else if (equal_Command(cmd, "proxy.gemini")) {

     setCStr_String(&d->prefs.geminiProxy, suffixPtr_Command(cmd, "address"));

     return iTrue;

@@ -1331,6 +1369,8 @@ iBool handleCommand_App(const char *cmd) {

             dlg, format_CStr("prefs.saturation.%d", (int) (d->prefs.saturation * 3.99f))),

         selected_WidgetFlag,

         iTrue);

+ setText_InputWidget(findChild_Widget(dlg, "prefs.cachesize"),

+ collectNewFormat_String("%d", d->prefs.maxCacheSize));

     setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs);

     setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy);

     setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy);

diff --git a/src/app.h b/src/app.h

index 0e8351aa..efaf0a3e 100644

--- a/src/app.h

+++ b/src/app.h

@@ -61,20 +61,20 @@ void refresh_App (void);

iBool isRefreshPending_App (void);

uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */



-const iPrefs * prefs_App (void);

-iBool forceSoftwareRender_App(void);

-enum iColorTheme colorTheme_App (void);

-const iString * schemeProxy_App (iRangecc scheme);

-iBool willUseProxy_App (const iRangecc scheme);

-

-iMimeHooks * mimeHooks_App (void);

iGmCerts * certs_App (void);

iVisited * visited_App (void);

iBookmarks * bookmarks_App (void);

+iMimeHooks * mimeHooks_App (void);

iDocumentWidget * document_App (void);

iObjectList * listDocuments_App (void);

-iDocumentWidget * document_Command (const char *cmd);

iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew);

+void trimCache_App (void);

+

+const iPrefs * prefs_App (void);

+iBool forceSoftwareRender_App(void);

+enum iColorTheme colorTheme_App (void);

+const iString * schemeProxy_App (iRangecc scheme);

+iBool willUseProxy_App (const iRangecc scheme);



typedef void (*iTickerFunc)(iAny *);



@@ -91,5 +91,7 @@ iLocalDef void postCommandString_App(const iString *command) {

 }

}



+iDocumentWidget * document_Command (const char *cmd);

+

void openInDefaultBrowser_App (const iString *url);

void revealPath_App (const iString *path);

diff --git a/src/history.c b/src/history.c

index 202549a9..59d515dc 100644

--- a/src/history.c

+++ b/src/history.c

@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

#include <the_Foundation/mutex.h>

#include <the_Foundation/path.h>

#include <the_Foundation/stringset.h>

+#include <math.h>



static const size_t maxStack_History_ = 50; /* back/forward navigable items */



@@ -285,6 +286,48 @@ void setCachedResponse_History(iHistory *d, const iGmResponse *response) {

 unlock_Mutex(d->mtx);

}



+size_t cacheSize_History(const iHistory *d) {

+ size_t cached = 0;

+ lock_Mutex(d->mtx);

+ iConstForEach(Array, i, &d->recent) {

+ const iRecentUrl *url = i.value;

+ if (url->cachedResponse) {

+ cached += size_Block(&url->cachedResponse->body);

+ }

+ }

+ unlock_Mutex(d->mtx);

+ return cached;

+}

+

+size_t pruneLeastImportant_History(iHistory *d) {

+ size_t delta = 0;

+ size_t chosen = iInvalidPos;

+ double score = 0.0f;

+ iTime now;

+ initCurrent_Time(&now);

+ lock_Mutex(d->mtx);

+ iConstForEach(Array, i, &d->recent) {

+ const iRecentUrl *url = i.value;

+ if (url->cachedResponse) {

+ const double urlScore =

+ size_Block(&url->cachedResponse->body) *

+ pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25);

+ if (urlScore > score) {

+ chosen = index_ArrayConstIterator(&i);

+ score = urlScore;

+ }

+ }

+ }

+ if (chosen != iInvalidPos) {

+ iRecentUrl *url = at_Array(&d->recent, chosen);

+ delta = size_Block(&url->cachedResponse->body);

+ delete_GmResponse(url->cachedResponse);

+ url->cachedResponse = NULL;

+ }

+ unlock_Mutex(d->mtx);

+ return delta;

+}

+

const iStringArray *searchContents_History(const iHistory *d, const iRegExp *pattern) {

 iStringArray *urls = iClob(new_StringArray());

 lock_Mutex(d->mtx);

diff --git a/src/history.h b/src/history.h

index 4c6507e3..7c2684f1 100644

--- a/src/history.h

+++ b/src/history.h

@@ -56,6 +56,7 @@ iBool goForward_History (iHistory *);

iRecentUrl *recentUrl_History (iHistory *, size_t pos);

iRecentUrl *mostRecentUrl_History (iHistory *);

iRecentUrl *findUrl_History (iHistory *, const iString *url);

+size_t pruneLeastImportant_History (iHistory *);



const iStringArray * searchContents_History (const iHistory *, const iRegExp pattern); / chronologically ascending */



@@ -67,6 +68,7 @@ const iRecentUrl *

         constMostRecentUrl_History  (const iHistory *);

const iGmResponse *

         cachedResponse_History      (const iHistory *);

+size_t cacheSize_History (const iHistory *);



iString * debugInfo_History (const iHistory *);



diff --git a/src/prefs.c b/src/prefs.c

index 188938a2..ce32962b 100644

--- a/src/prefs.c

+++ b/src/prefs.c

@@ -34,6 +34,7 @@ void init_Prefs(iPrefs *d) {

 d->smoothScrolling   = iTrue;

 d->loadImageInsteadOfScrolling = iFalse;

 d->decodeUserVisibleURLs = iTrue;

+ d->maxCacheSize = 10;

 d->font              = nunito_TextFont;

 d->headingFont       = nunito_TextFont;

 d->monospaceGemini   = iFalse;

diff --git a/src/prefs.h b/src/prefs.h

index 07298eac..1c3274d9 100644

--- a/src/prefs.h

+++ b/src/prefs.h

@@ -49,6 +49,7 @@ struct Impl_Prefs {

 iBool            loadImageInsteadOfScrolling;

 /* Network */

 iBool            decodeUserVisibleURLs;

+ int maxCacheSize; /* MB */

 iString          geminiProxy;

 iString          gopherProxy;

 iString          httpProxy;

diff --git a/src/ui/util.c b/src/ui/util.c

index 7fc27130..91945db8 100644

--- a/src/ui/util.c

+++ b/src/ui/util.c

@@ -1090,7 +1090,7 @@ iWidget *makePreferences_Widget(void) {

 }

 /* Colors. */ {

     appendTwoColumnPage_(tabs, "Colors", '3', &headings, &values);

- makeTwoColumnHeading_("PAGE CONTENTS", headings, values);

+ makeTwoColumnHeading_("PAGE CONTENT", headings, values);

     for (int i = 0; i < 2; ++i) {

         const iBool isDark = (i == 0);

         const char *mode = isDark ? "dark" : "light";

@@ -1121,6 +1121,7 @@ iWidget *makePreferences_Widget(void) {

 }

 /* Layout. */ {

     appendTwoColumnPage_(tabs, "Style", '4', &headings, &values);

+ makeTwoColumnHeading_("FONTS", headings, values);

     /* Fonts. */ {

         iWidget *fonts;

         addChild_Widget(headings, iClob(makeHeading_Widget("Heading font:")));

@@ -1140,8 +1141,7 @@ iWidget *makePreferences_Widget(void) {

             addChild_Widget(mono, iClob(makeToggle_Widget("prefs.mono.gopher"))), "Gopher");

         addChildFlags_Widget(values, iClob(mono), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);

     }

- addChild_Widget(headings, iClob(makePadding_Widget(2 * gap_UI)));

- addChild_Widget(values, iClob(makePadding_Widget(2 * gap_UI)));

+ makeTwoColumnHeading_("PARAGRAPH", headings, values);

     addChild_Widget(headings, iClob(makeHeading_Widget("Line width:")));

     iWidget *widths = new_Widget();

     /* Line widths. */ {

@@ -1162,10 +1162,19 @@ iWidget *makePreferences_Widget(void) {

     addChild_Widget(headings, iClob(makeHeading_Widget("Big 1st paragaph:")));

     addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede")));

 }

- /* Proxies. */ {

+ /* Network. */ {

     appendTwoColumnPage_(tabs, "Network", '5', &headings, &values);

- addChild_Widget(headings, iClob(makeHeading_Widget("Decode paths:")));

+ addChild_Widget(headings, iClob(makeHeading_Widget("Cache size:")));

+ iWidget *cacheGroup = new_Widget(); {

+ iInputWidget *cache = new_InputWidget(4);

+ setSelectAllOnFocus_InputWidget(cache, iTrue);

+ setId_Widget(addChild_Widget(cacheGroup, iClob(cache)), "prefs.cachesize");

+ addChildFlags_Widget(cacheGroup, iClob(new_LabelWidget("MB", NULL)), frameless_WidgetFlag);

+ }

+ addChildFlags_Widget(values, iClob(cacheGroup), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);

+ addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:")));

     addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));

+ makeTwoColumnHeading_("PROXIES", headings, values);

     addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:")));

     setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini");

     addChild_Widget(headings, iClob(makeHeading_Widget("Gopher proxy:")));

diff --git a/src/ui/window.c b/src/ui/window.c

index 2e38512b..66994e79 100644

--- a/src/ui/window.c

+++ b/src/ui/window.c

@@ -397,6 +397,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {

         if (equal_Command(cmd, "document.changed")) {

             iInputWidget *url = findWidget_App("url");

             const iString *urlStr = collect_String(suffix_Command(cmd, "url"));

+ trimCache_App();

             visitUrl_Visited(visited_App(), urlStr, 0);

             postCommand_App("visited.changed"); /* sidebar will update */

             setText_InputWidget(url, urlStr);

Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.17/pcdiff/eda45fcd34189e6844babde1ebc60c083b1b09da
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
937.215569 milliseconds
Gemini-to-HTML Time
4.566457 milliseconds

This content has been proxied by September (ba2dc).