summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-09-17 16:52:35 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-09-17 16:52:35 +0300
commitd7bd8ee9a9b057d3cc1b75881668e1a6362b646a (patch)
tree97cb320194dbd63154dc825861a97de7a776a289 /src
parent65778911126ed14d2c194c167f7a899fd4dafb14 (diff)
DocumentWidget: Saving the source to a file
"Save Page" now writes the current page's source to the Downloads folder as a file.
Diffstat (limited to 'src')
-rw-r--r--src/gmutil.c3
-rw-r--r--src/ui/documentwidget.c94
2 files changed, 59 insertions, 38 deletions
diff --git a/src/gmutil.c b/src/gmutil.c
index b2c7e93f..f55729d1 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -215,8 +215,7 @@ static const struct {
215 { unsupportedMimeType_GmStatusCode, 215 { unsupportedMimeType_GmStatusCode,
216 { 0x1f47d, /* alien */ 216 { 0x1f47d, /* alien */
217 "Unsupported MIME Type", 217 "Unsupported MIME Type",
218 "The received content is in an unsupported format and cannot be viewed with " 218 "The received content cannot be viewed with this application." } },
219 "this application." } },
220 { invalidHeader_GmStatusCode, 219 { invalidHeader_GmStatusCode,
221 { 0x1f4a9, /* pile of poo */ 220 { 0x1f4a9, /* pile of poo */
222 "Invalid Header", 221 "Invalid Header",
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 73d2676b..23f34577 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
36#include "../gmutil.h" 36#include "../gmutil.h"
37 37
38#include <the_Foundation/file.h> 38#include <the_Foundation/file.h>
39#include <the_Foundation/fileinfo.h>
39#include <the_Foundation/objectlist.h> 40#include <the_Foundation/objectlist.h>
40#include <the_Foundation/path.h> 41#include <the_Foundation/path.h>
41#include <the_Foundation/ptrarray.h> 42#include <the_Foundation/ptrarray.h>
@@ -106,8 +107,8 @@ struct Impl_Model {
106}; 107};
107 108
108void init_Model(iModel *d) { 109void init_Model(iModel *d) {
109 d->history = new_History(); 110 d->history = new_History();
110 d->url = new_String(); 111 d->url = new_String();
111} 112}
112 113
113void deinit_Model(iModel *d) { 114void deinit_Model(iModel *d) {
@@ -148,6 +149,7 @@ struct Impl_DocumentWidget {
148 iGmRequest * request; 149 iGmRequest * request;
149 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ 150 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
150 iObjectList * media; 151 iObjectList * media;
152 iString sourceMime;
151 iBlock sourceContent; /* original content as received, for saving */ 153 iBlock sourceContent; /* original content as received, for saving */
152 iGmDocument * doc; 154 iGmDocument * doc;
153 int certFlags; 155 int certFlags;
@@ -210,6 +212,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
210 d->showLinkNumbers = iFalse; 212 d->showLinkNumbers = iFalse;
211 d->visBuf = new_VisBuf(); 213 d->visBuf = new_VisBuf();
212 d->invalidRuns = new_PtrSet(); 214 d->invalidRuns = new_PtrSet();
215 init_String(&d->sourceMime);
213 init_Block(&d->sourceContent, 0); 216 init_Block(&d->sourceContent, 0);
214 init_PtrArray(&d->visibleLinks); 217 init_PtrArray(&d->visibleLinks);
215 init_Click(&d->click, d, SDL_BUTTON_LEFT); 218 init_Click(&d->click, d, SDL_BUTTON_LEFT);
@@ -229,6 +232,7 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
229 iRelease(d->media); 232 iRelease(d->media);
230 iRelease(d->request); 233 iRelease(d->request);
231 deinit_Block(&d->sourceContent); 234 deinit_Block(&d->sourceContent);
235 deinit_String(&d->sourceMime);
232 iRelease(d->doc); 236 iRelease(d->doc);
233 deinit_PtrArray(&d->visibleLinks); 237 deinit_PtrArray(&d->visibleLinks);
234 delete_String(d->certSubject); 238 delete_String(d->certSubject);
@@ -518,9 +522,11 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
518 case unsupportedMimeType_GmStatusCode: { 522 case unsupportedMimeType_GmStatusCode: {
519 iString *key = collectNew_String(); 523 iString *key = collectNew_String();
520 toString_Sym(SDLK_s, KMOD_PRIMARY, key); 524 toString_Sym(SDLK_s, KMOD_PRIMARY, key);
521 appendFormat_String(src, "\n```\n%s\n```\n" 525 appendFormat_String(src,
522 "You can save the content to your Downloads folder, though. " 526 "\n```\n%s\n```\n"
523 "Press %s or select Save Page from the menu.", cstr_String(meta), 527 "You can save it as a file to your Downloads folder, though. "
528 "Press %s or select Save Page from the menu.",
529 cstr_String(meta),
524 cstr_String(key)); 530 cstr_String(key));
525 break; 531 break;
526 } 532 }
@@ -559,6 +565,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
559 iString str; 565 iString str;
560 invalidate_DocumentWidget_(d); 566 invalidate_DocumentWidget_(d);
561 updateTheme_DocumentWidget_(d); 567 updateTheme_DocumentWidget_(d);
568 clear_String(&d->sourceMime);
562 set_Block(&d->sourceContent, &response->body); 569 set_Block(&d->sourceContent, &response->body);
563 initBlock_String(&str, &response->body); 570 initBlock_String(&str, &response->body);
564 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { 571 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) {
@@ -566,6 +573,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
566 iRangecc charset = range_CStr("utf-8"); 573 iRangecc charset = range_CStr("utf-8");
567 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; 574 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat;
568 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ 575 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */
576 set_String(&d->sourceMime, mimeStr);
569 iRangecc mime = range_String(mimeStr); 577 iRangecc mime = range_String(mimeStr);
570 iRangecc seg = iNullRange; 578 iRangecc seg = iNullRange;
571 while (nextSplit_Rangecc(mime, ";", &seg)) { 579 while (nextSplit_Rangecc(mime, ";", &seg)) {
@@ -573,12 +581,15 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
573 trim_Rangecc(&param); 581 trim_Rangecc(&param);
574 if (equal_Rangecc(param, "text/plain")) { 582 if (equal_Rangecc(param, "text/plain")) {
575 docFormat = plainText_GmDocumentFormat; 583 docFormat = plainText_GmDocumentFormat;
584 setRange_String(&d->sourceMime, param);
576 } 585 }
577 else if (equal_Rangecc(param, "text/gemini")) { 586 else if (equal_Rangecc(param, "text/gemini")) {
578 docFormat = gemini_GmDocumentFormat; 587 docFormat = gemini_GmDocumentFormat;
588 setRange_String(&d->sourceMime, param);
579 } 589 }
580 else if (startsWith_Rangecc(param, "image/")) { 590 else if (startsWith_Rangecc(param, "image/")) {
581 docFormat = gemini_GmDocumentFormat; 591 docFormat = gemini_GmDocumentFormat;
592 setRange_String(&d->sourceMime, param);
582 if (!d->request || isFinished_GmRequest(d->request)) { 593 if (!d->request || isFinished_GmRequest(d->request)) {
583 /* Make a simple document with an image. */ 594 /* Make a simple document with an image. */
584 const char *imageTitle = "Image"; 595 const char *imageTitle = "Image";
@@ -1015,20 +1026,6 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
1015 } 1026 }
1016 else { 1027 else {
1017 dealloc_VisBuf(d->visBuf); 1028 dealloc_VisBuf(d->visBuf);
1018#if 0
1019 iZap(d->visBuffer->validRange);
1020 d->visBuffer->size = size;
1021 iAssert(size.x > 0);
1022 iForIndices(i, d->visBuffer->texture) {
1023 d->visBuffer->texture[i] =
1024 SDL_CreateTexture(renderer_Window(get_Window()),
1025 SDL_PIXELFORMAT_RGBA8888,
1026 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
1027 size.x,
1028 size.y);
1029 SDL_SetTextureBlendMode(d->visBuffer->texture[i], SDL_BLENDMODE_NONE);
1030 }
1031#endif
1032 } 1029 }
1033} 1030}
1034 1031
@@ -1199,37 +1196,62 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1199 else if (!isEmpty_Block(&d->sourceContent)) { 1196 else if (!isEmpty_Block(&d->sourceContent)) {
1200 /* Figure out a file name from the URL. */ 1197 /* Figure out a file name from the URL. */
1201 /* TODO: Make this a utility function. */ 1198 /* TODO: Make this a utility function. */
1202#if 0
1203 iUrl parts; 1199 iUrl parts;
1204 init_Url(&parts, d->mod.url); 1200 init_Url(&parts, d->mod.url);
1205 if (endsWith_Rangecc(parts.path, "/")) { 1201 while (startsWith_Rangecc(parts.path, "/")) {
1202 parts.path.start++;
1203 }
1204 while (endsWith_Rangecc(parts.path, "/")) {
1206 parts.path.end--; 1205 parts.path.end--;
1207 } 1206 }
1208 iString *name = collectNew_String(); 1207 iString *name = collectNewCStr_String("pagecontent");
1209 if (isEmpty_Range(&parts.path)) { 1208 if (isEmpty_Range(&parts.path)) {
1210 if (isEmpty_String(name)) { 1209 if (!isEmpty_Range(&parts.host)) {
1211 setCStr_String(name, "pagecontent");
1212 }
1213 else {
1214 setRange_String(name, parts.host); 1210 setRange_String(name, parts.host);
1211 replace_Block(&name->chars, '.', '_');
1215 } 1212 }
1216 } 1213 }
1217 else { 1214 else {
1218 1215 iRangecc fn = { parts.path.start + lastIndexOfCStr_Rangecc(parts.path, "/") + 1,
1219 size_t slashPos = lastIndexOfCStr_Rangecc(parts.path, "/"); 1216 parts.path.end };
1220 if (slashPos == size_Range(&parts.path) - 1) { 1217 if (!isEmpty_Range(&fn)) {
1221 slashPos = lastIndexOfCStr_Rangecc(parts.path - 1, "/"); 1218 setRange_String(name, fn);
1222 } 1219 }
1220 }
1221 iString *savePath = concat_Path(downloadDir_App(), name);
1222 if (lastIndexOfCStr_String(savePath, ".") == iInvalidPos) {
1223 /* No extension specified in URL. */
1224 if (startsWith_String(&d->sourceMime, "text/gemini")) {
1225 appendCStr_String(savePath, ".gmi");
1226 }
1227 else if (startsWith_String(&d->sourceMime, "text/")) {
1228 appendCStr_String(savePath, ".txt");
1223 } 1229 }
1230 else if (startsWith_String(&d->sourceMime, "image/")) {
1231 appendCStr_String(savePath, cstr_String(&d->sourceMime) + 6);
1232 }
1233 }
1234 if (fileExists_FileInfo(savePath)) {
1235 /* Make it unique. */
1236 iDate now;
1237 initCurrent_Date(&now);
1238 size_t insPos = lastIndexOfCStr_String(savePath, ".");
1239 if (insPos == iInvalidPos) {
1240 insPos = size_String(savePath);
1241 }
1242 const iString *date = collect_String(format_Date(&now, "_%Y-%m-%d_%H%M%S"));
1243 insertData_Block(&savePath->chars, insPos, cstr_String(date), size_String(date));
1224 } 1244 }
1225 iAssert(!isEmpty_Range(&name));
1226 /* Write the file. */ { 1245 /* Write the file. */ {
1227 iFile *f = 1246 iFile *f = new_File(savePath);
1228 new_File(collect_String(concat_Path(downloadDir_App(), string_Rangecc(name))));
1229 if (open_File(f, writeOnly_FileMode)) { 1247 if (open_File(f, writeOnly_FileMode)) {
1230 write_File(f, &d->sourceContent); 1248 write_File(f, &d->sourceContent);
1249 const size_t size = size_Block(&d->sourceContent);
1250 const iBool isMega = size >= 1000000;
1231 makeMessage_Widget(uiHeading_ColorEscape "PAGE SAVED", 1251 makeMessage_Widget(uiHeading_ColorEscape "PAGE SAVED",
1232 cstr_String(path_File(f))); 1252 format_CStr("%s\nSize: %.3f %s", cstr_String(path_File(f)),
1253 isMega ? size / 1.0e6f : (size / 1.0e3f),
1254 isMega ? "MB" : "KB"));
1233 } 1255 }
1234 else { 1256 else {
1235 makeMessage_Widget(uiTextCaution_ColorEscape "ERROR SAVING PAGE", 1257 makeMessage_Widget(uiTextCaution_ColorEscape "ERROR SAVING PAGE",
@@ -1237,7 +1259,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1237 } 1259 }
1238 iRelease(f); 1260 iRelease(f);
1239 } 1261 }
1240#endif 1262 delete_String(savePath);
1241 } 1263 }
1242 return iTrue; 1264 return iTrue;
1243 } 1265 }