diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-17 16:52:35 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-17 16:52:35 +0300 |
commit | d7bd8ee9a9b057d3cc1b75881668e1a6362b646a (patch) | |
tree | 97cb320194dbd63154dc825861a97de7a776a289 /src | |
parent | 65778911126ed14d2c194c167f7a899fd4dafb14 (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.c | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 94 |
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 | ||
108 | void init_Model(iModel *d) { | 109 | void 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 | ||
113 | void deinit_Model(iModel *d) { | 114 | void 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(¶m); | 581 | trim_Rangecc(¶m); |
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 | } |