From 75197707e0bb149cb9c2e4a983d92fdfb381c17f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= jaakko.keranen@iki.fi
Date: Wed, 30 Mar 2022 22:30:53 +0300
Subject: [PATCH 1/1] Exporting and importing user data
CMakeLists.txt | 2 +
lib/the_Foundation | 2 +-
po/en.po | 54 +++++++++
res/lang/cs.bin | Bin 33699 -> 34385 bytes
res/lang/de.bin | Bin 32858 -> 33544 bytes
res/lang/en.bin | Bin 28252 -> 28938 bytes
res/lang/eo.bin | Bin 27470 -> 28156 bytes
res/lang/es.bin | Bin 32745 -> 33431 bytes
res/lang/es_MX.bin | Bin 29391 -> 30077 bytes
res/lang/fi.bin | Bin 32295 -> 32981 bytes
res/lang/fr.bin | Bin 33540 -> 34226 bytes
res/lang/gl.bin | Bin 31751 -> 32437 bytes
res/lang/hu.bin | Bin 33331 -> 34017 bytes
res/lang/ia.bin | Bin 31193 -> 31879 bytes
res/lang/ie.bin | Bin 31319 -> 32005 bytes
res/lang/isv.bin | Bin 26973 -> 27659 bytes
res/lang/it.bin | Bin 31302 -> 31988 bytes
res/lang/nl.bin | Bin 30688 -> 31374 bytes
res/lang/pl.bin | Bin 31676 -> 32362 bytes
res/lang/ru.bin | Bin 47412 -> 48098 bytes
res/lang/sk.bin | Bin 27337 -> 28023 bytes
res/lang/sr.bin | Bin 47414 -> 48100 bytes
res/lang/tok.bin | Bin 29424 -> 30110 bytes
res/lang/tr.bin | Bin 31735 -> 32421 bytes
res/lang/uk.bin | Bin 47606 -> 48292 bytes
res/lang/zh_Hans.bin | Bin 27286 -> 27972 bytes
res/lang/zh_Hant.bin | Bin 27683 -> 28369 bytes
src/app.c | 57 +++++++++-
src/bookmarks.c | 179 +++++++++++++++++++++--------
src/bookmarks.h | 3 +
src/defs.h | 11 +-
src/export.c | 205 +++++++++++++++++++++++++++++++++
src/export.h | 44 ++++++++
src/gmcerts.c | 244 +++++++++++++++++++++++-----------------
src/gmcerts.h | 4 +
src/gmrequest.c | 6 +-
src/sitespec.c | 108 ++++++++++--------
src/sitespec.h | 4 +
src/ui/documentwidget.c | 49 ++++++--
src/ui/util.c | 115 ++++++++++++++++++-
src/ui/util.h | 1 +
src/ui/window.c | 1 +
src/visited.c | 92 +++++++++------
src/visited.h | 2 +
44 files changed, 928 insertions(+), 255 deletions(-)
create mode 100644 src/export.c
create mode 100644 src/export.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bcf1c7bf..69ac3df0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -150,6 +150,8 @@ set (SOURCES
src/bookmarks.c
src/bookmarks.h
src/defs.h
src/feeds.c
src/feeds.h
src/fontpack.c
diff --git a/lib/the_Foundation b/lib/the_Foundation
index 05fe3fdb..32e91939 160000
--- a/lib/the_Foundation
+++ b/lib/the_Foundation
@@ -1 +1 @@
-Subproject commit 05fe3fdb17ebf64f1830dc7bbe3359e5f062b1f0
+Subproject commit 32e91939443d6e831eb438a81b6a8bc5e17ac021
diff --git a/po/en.po b/po/en.po
index 699fabb9..f8cc0caa 100644
--- a/po/en.po
+++ b/po/en.po
@@ -9,6 +9,9 @@ msgstr "Loading"
msgid "doc.archive"
msgstr "%s is a compressed archive."
+msgid "heading.archive.userdata"
+msgstr "User Data Archive"
msgid "doc.archive.view"
msgstr "View archive contents"
@@ -181,6 +184,12 @@ msgstr "Open Location…"
msgid "menu.downloads"
msgstr "Show Downloads"
+msgid "menu.export"
+msgstr "Export User Data"
+msgid "menu.import"
+msgstr "Import User Data…"
msgid "menu.pageinfo"
msgstr "Show Page Information"
@@ -971,6 +980,9 @@ msgstr "Import Certificate/Key File"
msgid "dlg.certimport.import"
msgstr "Import"
+msgid "dlg.certimport.paste"
+msgstr "Paste from Clipboard"
msgid "dlg.certimport.notes"
msgstr "Notes:"
@@ -989,6 +1001,48 @@ msgstr "Audio"
msgid "link.hint.image"
msgstr "Image"
+msgid "heading.import.userdata"
+msgstr "Import User Data"
+msgid "heading.import.userdata.error"
+msgstr "Import Failed"
+msgid "import.userdata.bookmarks"
+msgstr "Bookmarks:"
+msgid "import.userdata.idents"
+msgstr "Identities:"
+msgid "import.userdata.history"
+msgstr "History:"
+msgid "import.userdata.trusted"
+msgstr "Trusted certificates:"
+msgid "import.userdata.sitespec"
+msgstr "Site settings:"
+msgid "dlg.userdata.no"
+msgstr "None"
+msgid "dlg.userdata.missing"
+msgstr "If Missing"
+msgid "dlg.userdata.all"
+msgstr "All"
+msgid "dlg.userdata.alldup"
+msgstr "All (Keep Duplicates)"
+msgid "import.userdata"
+msgstr "Import Selected Data"
+msgid "import.userdata.dupfolder"
+msgstr "Imported Duplicates"
+msgid "import.userdata.error"
+msgstr "%s is not a valid Lagrange export archive."
msgid "bookmark.title.blank"
msgstr "Blank Page"
diff --git a/res/lang/cs.bin b/res/lang/cs.bin
index e70b4c84459e45e1a2fa1b67bba7955dac428f96..d2889c1a4c6ca1464af5716b394eed92e4be9b09 100644
GIT binary patch
delta 695
zcmZXS&q@O^5XRHusZbFqhzDW(2a2!{(2LYovDk{xdR5ZR>^3yJS(3EqsouQ!7J_GS
zpG03k@Bu{Mz-+g>((NIc$>hi6%a=SqmfxSsuRqmIstuB>WCl;k0GfhF14$K&KdXDS
zsqme#M#ZS14i$nl>|li=8%IeU4lWQQXpJMmU20GtE|LM)nuk7Vd(fSy3!OqqE@g;>
zN!Ovu9r6j1cy(uWYdc-!LddmM1T8BG8tGQNeq37V@F0@P{A2Ce8giNpkus~mDS!?t
zB^8<DXOs(Maiv+WObcL!^iZH{kj*rZ0^|&&90gR3G&#w#`VwS4^Q2&y9h)@Ejmzk1
zZhfk7Ujx?=N&^%oRB#3z>MI)h2zZy=9p=6{S%<vAkZU8=JvmR~vLMbdG@7)Nzzs)j
zk^Z8gj^_4NBXmj6CZN$6>t+HLIHSh)KQYozBe<vx_tH;MkDJ}^wT)CSIZb8fPZS^B
Te3WXL&w2Ul;U)gPecSy5QgZ-|
delta 44
zcmV+{0Mq}`i~^&F0<iE6vl<Q)6tkfs86C6bKlcN(F+?#6v$ap{5VOBv%mK5jVx}XU
C%@X$j
diff --git a/res/lang/de.bin b/res/lang/de.bin
index a96ffb1772587df51a7acc92c09b511feb4107a7..cc4b305e9e4f6ba917758ebed33ace9759a975ea 100644
GIT binary patch
delta 695
zcmZXS!Ab)$5QbCnAVNV<ks`t<6zXLkpn}laip45I>rKRTGrJAVZk8l13PO7luU>ov
z&%(Zd&*9N$@Bz$ryDQxul9^0CCV&3qd$#uavi4aiSEx2fZju>1B?CAUJQ_%<SiDiL
zZBK>oj5R7o4Rxpxq+tgu6xldR>QK8xjG#4+1b3-HeY{KtTx%Zsq~pQWB3<efN^&hj
zBuu&vRql{aki;kOdvKU8av|i_DuR}k1dVhnuAV-ucX<#=W&W|wd<{8GhDe!J;1oay
zm6D1q@N>!qviP~Vzd0>{8PY?6u0b}}KnjpEka84IHPWP+W%U)vdge*NFgrGBmK&GR
z)7<)0VO0ay5K03SCRA_+UFs_u`Uv=#+#MFaIa!Ci!H{bs)f2f$<EkLeFf^KUlE4i|
zZIS-6p^oPERU>ps&nBSJ80%&N7C58E_CGPwPb0Xf4ENGcQIFrdrLDbGFKMT;^Cybm
U?!HPpna_FoXZ8`d?{|;>00!~?^8f$<
delta 43
zcmV+0M!48h637v0<akkvqKFR6SK}B{~fdVK2
?qeBu3qX<tBvn5};0kiL2_kKK
B5MBTP
diff --git a/res/lang/en.bin b/res/lang/en.bin
index c7ce6d11b1c1b65456707e80e6d6418bce950efb..b1801eec113a7738f8a35e126cd54e91998338ac 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtCsggQ6~q9{HGL6;i^yS3J0v6e#X<dk0CT|;x1Bgu&lQU@pT6S(Q%
zEZmpyLllI555Ziochc*SyqA|BFaP|L>9q1Pt9(6Icc?Z<dg%<Fk^!6x9t|Z`EYa2X
z&57`xSfgUpP=^XZ>UOYFk;PG3hvO^62%2#uxJwP{lVvjCTJz8+Z4a&&=~AaqlCBJq
zFzGr}xg&muBsth??H*=}LI}CBilAvFK|R|_Zclq_9UeqdnSZQ3UqeBY5mIIqI3>_Q
zrKBPYyhXV{mVBIjuTM%~j`UHWYmm(~kP_q!r5p!TjWucHS#1Tfo_SI*%#Tf*6~^WC
zq_93yc%*@A2&DlEcT{i&9qKC@`UrTR-W?XcIeCYo!H8=k)dRWA;;JCdFf^L9)4&Z!
zZIQvUp^oPERU>ps-zK2Z80%&VmN=tg=1yYWD#6chI
qks3pAdwy~Y*B^R0O;)#;q
T?rUW$_qiy~o<5Vu-p~FYIAQ{i
delta 44
zcmV+{0Mq}9;sM;;0kDJ%v#tw}5VNuzQ5v%@H%0@q$vVFXv)e?O46~6@h5@rTQ_~%}
C6B7IY
diff --git a/res/lang/eo.bin b/res/lang/eo.bin
index bdb009e9e81a5be71de499d7e61f30e9d339aa59..155ef35cddb46ed85a5291866fbb0ad82d11f3b6 100644
GIT binary patch
delta 695
zcmZXSF-rq66vuP8sNg1)F7g}%T@D1Nf5rB#I1RY))IuA#Zhk>rX_75o%_0cYVB
zC%=H8n~R&Dz`;duFxTsy^g1N(<>klAKmX+Q-TD1+9-Y-X)dtB$HiNHZ2uFg)14$K2
zzgB+MCL(YXjfzo2TB}=+QBMCmc&^dwmXP1w31kGj~diF^JK
i=21Y}KAg?cxlWNJ
z=Q2XVq~}uQ4fz;JdypUH}gd)gj
xh(6W-CnQx_h_iCxj!&oZwkF}?3C}}c8%B%vn
z0=lS_RAh#qP%e<A&wEeHlL}ZMJrw8}WK#{K0=WYzM<G=sO%98!u>e`md?^?f$7an+
z;|h9GTAwT2(!e!D(g1}q6`Vnr28u=j0$yi#hna6~(V=WG<l0DeLr(L!D2OwRj3(_Y
zaKlksq(5({tGRvE7(LRn31~FNx|x9$&ZxxxCr0{t1P_(re*P&MX|n&eR?GF0<6L(6
WMCn22-dQhvF3X>{59v$yZsQLnUIWhn
delta 44
zcmV+{0Mq~c+X2qj0k8oIvnvYd4YP+BiW##!GE@Vz<u$Jev*$m~4YSfslL51ZPj?=-
Cl@mPx
diff --git a/res/lang/es.bin b/res/lang/es.bin
index f20d84dac0f1e63a6258e3d860bb6db8dfbcc7c0..3f8b7a6171a66240d11e11d1f1bb45477f20efcd 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtC>k_tivaS)%1pvyV96&$LqSX-sgI*CJid2K^;mm|p)o$BJ~EKY9D
zh5H(Q0arJ7cg^*BC%q2IdwKcs^3Ol{{w=&bElnoHO{xu&Ry;#bNe|8h54w^n7QGcO
zOJmV@LXC=1Lmesvso24CMHU8e9S$0Z0aU|4aF-g?hx4SzwdQ`G)O&C>OXoWIl3Yt4
z36rivl{?@gB+<|I-qudC$b^s^s|c!A5>%3{X!Yc1xyijiD)W!kr)$V)GC<0#0w)JL
zsFYM>hM!X|kVVIp(#kjorbrtFx(3-)1Ia;7SIVJB)lieuG%GJa0Y`z4AW!dW|?s
zJ<hC86z*%_8hmMh!iWmaph^3R`h5hvkM9mM-<-5V)?mQ3k?Mh5Bymv?XXqPE>T%$P
zqqazA-cUz#>FxDq-_(>XpD6;26LQIq5V&cbdm@zD#Lrprzl6Y#
D@nqL<VX+1V3C
VAGe={_0;FA{Pp-Az23EU{{Zio0Z{+|
delta 44
zcmV+{0Mq}Mg#zjS0kE(Pv)T+f6SIdPE*-PrJtYLQT0#m6v$su*5VK%j(*d(nU_>IO
C#}U2&
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin
index 8770ba16b86d8fb60140dd98dcd5710efc56b883..b19bfa090303d1b1239963436654de8aa43a0597 100644
GIT binary patch
delta 694
zcmZXSF-rq66vtD<!485Th&uQj1YI(UbgQjmX%(gI2S_jPuA#Zhk))zi9sDeH6>o9z
zGq^enE^dAR!CbF*((91CmzN(e|NN8pugdRt<=L-pGh>llr!)9kh0u^99w?@H(yy*=
zOk_aE1~q4vQ6?p+yTMCEK918m9JUc-XpUnkJZ8}xEs~)yMnnN=`EWT;7dl0XT&V~N
zR~}{B8;V;b3GV*v>}QKY2<baT&~%cZo^2(a<F}QL2xFz~Kh~P9p`ghS8Fvb338bjC
z(qxXGGa-?)SlKRB
znrDABXXXA(#Wq64KyA=4v6PV=m`1UbumB{}A&rp*f9a(YrY
zp9wrLAPhvx0)ty71&0m`G>ZZRyi2bRbK7X%p=dA^#wvYJF0!~RNN|j-A+0nBE6_Nk
zx9BG|!aZt?9_hLS3|i~VOu-Uo_1JwUPI_4c549D3_9tq|v~jY&mFXpCne5_)l8>9$
R%4Y6!QT}|GCXe0r-XB7>0vP}R
delta 44
zcmV+{0Mq~d>H*K?0kCEYvyck(5VQCj{2H?#H?sq?Cp(@AvlvD<4zp}jvHO
RzDxr
C6B362
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index 28f7f22ebde240445c96716c9274f41ebc9f9510..b7c7acb8456d26285f26ceec81a8439444f048ff 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtC>5xOX%h=R{SP=xycMZsznt5t+n5X2$9ylX?#%aP=YPSp?K<m}?0
zxCyuU1st7R9Q^=(0ta)w-bt@R@?Ktky!V|o_
CUw-c{##RaMjlGAtwPsuLq3LZ2i
zRV;cb{>=16%Lz3qMh$hS5TtAe%N1D|#C2FZLJXi127<fPpk5y(yIgDTw@B54<6%0|
z$(Q6rbd~
9je?m?;(jEm+zNWl0_zjoLNOsv67&iY(*QJi<33p4Wu&vSaq<5j3#ZQ
z%qnnlpo2<DMTYo3wf!~K&w(jYM}e+EHqbzFkkgd1)1|7T$xfP;#vtpNCk4aw
ztl6{TuS#d>l1~m8n^~u8lcdlf-|VmmZE+O0k7h_!_YS;?T|HSb8V!$CI?9z7sMI
zMw4nBxZ$WR(ik<=(cHdjfG(-q1T-3B-HgE;XH;na6C;fzf{V&<FZmRuXzuWCdOp!h
a_7d6I6Gaaf?}gda=dApF^Aw$5e!w3m+5)}+
delta 44
zcmV+{0Mq}~fdZ%e0kFCZvZlj60<cRoc9OJP`!5WI@phvxrQL5wkH~!2z=pV8bBE
CUlS_;
diff --git a/res/lang/fr.bin b/res/lang/fr.bin
index 97a48bb22478722ce3ac8696aa791c1951d2895d..28eb98ae0be48ddbbf1d359505a62e7d062e9639 100644
GIT binary patch
delta 695
zcmZXSze@u#6vtz65(*9l(Lr7XL6>uL=pxngM`<lW+fgySyt{_xE=Q6Uo$4Si;=kc6
z-oKY>|bK8E{KTNZ!lKhnH`@$@5F)^R@DES>2}EAh}6r@RbbVOz?Opsba}-wN{&n
zz?o=Nj2h}tAxP5>Rw}YdoYvu>jTnPFi3RtlL4CMPhFohN1*GM}^&(yB6iL#R5fUan
zhbnKxr$~}P{cX3NEeauIU=@LDB|$UWN`6k->m44(Qkj3OHD5zPlMzy86*wi(L8YW3
z3;cp|fh^f^-HllZ%#jn^bE4O22z5Yp_Jp0s<9@gdDd8gtY^Ly4D(~tW
%J%Ju9rw
z6z*%_8X{?c!juZmphEiqW}RP(!0aLHz)5<G#GJhq<SPSzHyw8Ae8vRvNhBs4dc8
zHq_DFzG{pf>DdG{8eo|!4hZG#QrBn
dI`ImEnH&DH_RbcXM+q(@V}X*~Jqj!QIzd
QE%&)7|31AZNB7@*f1y|Zr~m)}
delta 44
zcmV+{0Mq}nivom(0#pMKtTkvK}9<Xv(Hcu5VQMW&H=OOV#y+a
C84@A@
diff --git a/res/lang/gl.bin b/res/lang/gl.bin
index 274bcf2b4d87f23fac97746cc300e816dd55bb6a..e79c0db368236d3b002ba8a99cc4f65d9b043d32 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtE1LF^z!5kc^E5Olc@PzR}2@vw^6I*1^omv?PwdO4C@wNu?({R%G5
zg~Jcx>gcC%5zO^^C%q2IdwKcs^3Okc`6+$;mL_lIZK@5D>tqIB$pM@Q9(E*EEPgG2
zR>q?1L>d*NhB{OTQn!N@iYy9~I_zH{hR}#Y!98kFAIy^jt~C$3#P#8Fmd<qwB)O6S
z5+*%|DzC?fNMfISxe#(|6+y#Ff_l0YKOU|vH~Aox%KT&QbPYL8dPtd7;1oay
zm6D3g@H5H<vUqg#wK^_<8PY<5o<TO%Knjr4k+MIas;|j$mem#@>zOYF!|d3kS#DfL
zk8|r&g?k#fhCmviFr<PrXwt5tK^Fm^lDosqHz(_mH|TL~q#BX)G%gC_3<INyn*?q+
zYKyez4RtiPuNtC9S~dZV##lEKu)rA=+5f~yJB{F>GTcu;MJ>K@E9+aSUUHhs&YviL
UxSNzVGN1GE_w#$abiY#l121I&t^fc4
delta 44
zcmV+{0Mq}q{Q-yg0kEMAv&#&?60pwARMzwJOKo=pg_?Hv&2gg5VP
IssXdSUHKr&
CUlbSs
diff --git a/res/lang/hu.bin b/res/lang/hu.bin
index b2bbc5384078eb71b14a0e668381d325c480cd01..c72f74ad769357cd6db85a32cbaadc2bb98e76c4 100644
GIT binary patch
delta 695
zcmZXSu};G<5Qa^~!cYkbBw#?gA|b(&2k3y*QV<mdA+?MMx$z~j>co-lRCFo>3opS2
zD@frnSa|?=2Q~(rrU_~~*mpjEI{*Id^r80kSbLk)cc?Z<uCf_CB||tCJRVA_SQ^xy
zH>SdO5{-&cLmesvY1zRlMV7=_9S+VBW9THY;4U?&4;RUhYt18{bUnD3rwg4TNiJoC
zgh|(-$_@A!NxI$eb{qMk6hf}8BIsC2(8{+`bJSh!@i3Ok{A1nO8cLc3NSRgOR6qxn
zl8VgnQ_2Oh^u7JNHm!gK(no==K{nGsDv&djauiZE(xhEv%_Ycs=1IY@I5ulm8duQM
z()wKCz6P!#k_IS@so)HH)K@g}5%4a%JIsA^iVkIifNLYwgq-DZSrBI!8BMxb;D)2N
z$Y9Y>M|1nCF}kF06VPakbu$AioKcDWPmB!m2reqaz5G)&)7Re5`c|%&oaD00CrY1g
TK36sipUd+1{Y&cKe(e1L*e3%b
delta 44
zcmV+{0Mq~Bi2}2P0<dvv!e}^6SJ!!aUHWqKR*Ps+e7mSvmsCP5VJC1&jGU
V!$G(
CixQ9k
diff --git a/res/lang/ia.bin b/res/lang/ia.bin
index 9aa98b70b03e59a6f3f4a349c1504729e7b4fa5f..0527016fc4f3861d3fa8bec9f93af20978174a71 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtC>5eiZeL_~NFf-bjPC{nG(ViloPT+_?zH8giQl3Y=jx;cxhiy*El
z_d%Rp{Q?e7ZsvNulU|49y}bN*RAW}{*>;1OK;ERHL4Agvt)+8l0F;=9
+<vEPg0g
zDr4a}kw(R+p$-*-)a_t}B8$SL4%^3wAvB^;aF-g?JF}$EwdR3GT75W~rZb%aNls;e
zgh|(-${p|#lK6GwX?-(Y<U+`~RRj$y3F_%q+}vGQY;!-9%KT%k$r^H+43ILbz$t(Z
zDkT+};!Vm0vUsodzBDd?8PY+4u0b}@Knjr4lXB=&HPmE3%c^sb^{g)i!|d3kS#DfL
zk8|r&g<Bf9hCmviFrtDpXj4zoz(c^R<nA!_&B;3C4F+5rsjkUU8s`OZhJn$fl>}}$
zYKwGd4RtiPuNtCDIyM1~##lEKu)rA=+5f~yH;v$;GQ6LDifa7bykA~T^^(I>cK$^1
U&BaG)CG$Bif8D;swadrNKXO3=SO5S3
delta 43
zcmV+0M!47
2pGU0kEVDqs|NwvqB!M8?zZY_ye<MKBx(^VM&k=vkqCw0kij7kRhrR
B614yT
diff --git a/res/lang/ie.bin b/res/lang/ie.bin
index 317b9ddb0457de3c83ee24df4bb3d49d3054c84d..85cfd1a994f8482487ddae85ad54a07a5b92031f 100644
GIT binary patch
delta 695
zcmZXSze@u#6vtDXlnyF_h#=2F(B*au4%PajSVd?>9Mj9|H8yuSlAP#tg7_b}y9sW>
zE&7+ZJGdIoXqulC%q2IdwKcr^36B-7-yBE3c2$6{-!Avvh`@l0NJS9(E;FEV-|K
zFT}!gB8`esLmesvsoTLyMHYo=9X5{y;4U?&w<bxSYs~|XG<$F|PA57AlAOu_
z36rivmD}gnNRoJMZgo9d6hg?kRRj$y3F_Ha5^oP@THFt%GXGd}w1$EveWc7Pa7v(q
zN=Ze=_yOetS#qWG8>n
9BHFK*B~2dASKA@N;&YU8fdbcXSFHFde)PIVSa4dtS~O8
z<HGt(;f4mTA&>?rTvNdrw5X?O;342mdUqK6=HwlU27RuLRJY_Pi_?NQ!@y|LOanI@
zwM9CUhB}(tR}Il6ZJU5bW2~DgSmKO|?0;gUlSOb*8Q#l2MJ-u8{F-0R^pgEdcJV~X
U&&7LXDfhW3f8ISOPnU1-2OM<+_y7O^
delta 44
zcmV+{0Mq}4`T^JU0kD(|v%U=A5wj8>6&tfsI_d+nkv^aavxG_I5VM9_vjMYNT&y6%
CkrQSB
diff --git a/res/lang/isv.bin b/res/lang/isv.bin
index 343d17e4fa3c0337c82852ae9f32c9261693440a..8e7c8169358dcb6f88fe4684f4e94896bce256f7 100644
GIT binary patch
delta 695
zcmZXSy-ve05XW7yAQdK%_z=S(fdmrDz`)P}sZc(u7F5yl0B(FqtvYdJyA@1jhna~D
ziHQM}C*T#BSXg)vgwr%ZO$YnV=bz4h|LyP3(*0NIZL7RSwL#KKX7H2@;8^geE2(1f
zQu%ptBz$L}Q88+$Lxmt!J6NH}22oOnod#kAwLv7fOAYGXX)@qi^Ux=C4=yI@RHsmq
zOBo_z(sihEdwhr_{#ZR*-%J;|5OQr5LCs2nYPuEw?ziTeJcy(+|5$yzhMXonq|7RC
z3ZR2ZNku03DdhrLd~$F#KPrG3(nf)<K{nPv3Xs#4vL8^@*W@V6Dl?Gv%#(s)c5Kot
zH!h<`x%H{SZ4F#QC=E~;Qo$KCsjq10Bj8PPcbNF*WF7JbJ+6&Zx8yvHvw}Fo&}dRm
z0yiABMLN@lI-1*8jnE}+n}9}RteXi~;EWpB|HMcqjo_j(+)F=2C4M}7T3AW-l9N<+
X{zUO!<Ds;i`J9)3?q1^8=KIDUAnyTp
delta 44
zcmV+{0Mq}Aa6+r0k9+svrY?=4YO1kQW~?-Ge!fmY&S^=vj{<(470#Zh5@r?Po4&
C$PncK
diff --git a/res/lang/it.bin b/res/lang/it.bin
index ef1450082d83b226dc9a3f88fd99c39f73b90273..e1296993af452f31c6bbac3b3d5a7d04e5cf2ed4 100644
GIT binary patch
delta 694
zcmZXS%}N6?5XVzI2?Y@Z5k#g3K`;9Ndl0JiqxgZ)Dqhpg>^3yJS(0qgQ~L_~3W{fC
z@f8X_fhR9MLoeRUcDpOx9+H_%eoX%PCzEMy`dfQ@tZz|mkX&ao_)3OwBzWADRIzki
z|6Cu5z)3VJMh$hS5Tt1bs}xxhXLZ;+M~tDB#DaU&px$33L#{QC0@C*3a-J@9iX^#`
z5fUanhbphnhe*=yPO#m7o
w#V--QmN`hv-mA<=AD;GSBr856md$xv>CViyLDsU>G
zgGxz7=J*Na0$J)FysVBYV1aZ{pl6WHG>{79^rRewR1GvaEHZZqvYz=;Ff5MEnw7>C
z^r*BxSGcQzYlx%)3PUP5g9{oc8U+Y=pWPkizBxsQvO%9~Bh>>r%j2>j&M-2Xw6nks
zM{SYrqM?rF_Elr_NXI6i(HQGy239zu68oPR>E;nUREGQcr*NOYPuDhcx#T!kUA|EI
Ub^B4<C~PjvKjYVQd^c(Q0b}I@ng9R*
delta 44
zcmV+{0Mq~U2oiC0kDw_v%3tq5wqtWavZaSJBS0bVLzJ*vrkI@4zp
nuK}|0S
CG87j8
diff --git a/res/lang/nl.bin b/res/lang/nl.bin
index 7bec0514e2ae76dee897ad1d3d000713b42b2e62..85470de6032ae5e74a595bd127ac4f3779ef8569 100644
GIT binary patch
delta 695
zcmZXS!Ab)$5QbB{Sg02fL=l-D1ikD7R47!dSgazn>N(xaZbP%1B}t2(>J#`FJq!B~
zzJkZ%#iJ+Bnr*r(-5!#eOg<)m{$&1LdHt!p?br6GHc0NW83H9EI2Sw_NUB&`t)<&j
z5qe{dicv#7Dg<fS!AeCoPO>^2Un3^a9w&nP)Sy0DB_pmik3-T4;AWYwbc!Xpl`#?~
zeUB=C$R|kB_d|A2cZ)&@xwnd-Z6!g=ZKd_ouZ=E`5~<8T)>*8fpve#^vkJTt=%G?l
zktKdfxj>fwHiPQ41m;K&1^NcrLIWv5-ayJxMAb->vpj38LDsWC3WoWyS+l~poSqie
zJB3FYxQ197pfI6=Gw9M#(KtlFr|j;q^v%mV6b*)48>ybjm5b|wIK$Xz(#Zlh9JNLI
ztA=`-+gDA{Cq0{hMq{j-8Cc?s8r%QGNZ&>9Q5hb%Pti!()qHE$=_MCVcJV}M^I=xm
Q$$c)$pD(lY<8c%I02fFCWdHyG
delta 44
zcmV+{0Mq}D^#S1T0k9ejvqTJ{60=$#ZydAqJKY1bCO};Yv(`%A4YSf%paHXzTKpfi
C)f3SG
diff --git a/res/lang/pl.bin b/res/lang/pl.bin
index 171356c7b7a298f06f35cd33b4270aa47e07d072..ec8e61da38486b47037a8bc53b113ea47ab900b9 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtECq#%MIg2?Mo(B(#lI!LvO#VSH8id%Yly@uv4N0KW#)eqqAH*gb`
zTm1+weuaL;G~O#xnA$2CBZ?FF#)X`6sVGrH|jz^SHc5wLx;0%+OQPhke1ruB3{^
z@8z%Mk?@>Iqhi!hhYCUJcCbQMPX8h&11w68c
^?OAYF+S<>fP^S~p`9-K_mnNEQu
zr!qjor0Y=S_W2M={I>DAUP%|Z5OQu6LBmRddb$-q?Yu0sxF1So{;}p{4LMEvNSRgO
z6hH@+l8Q|6L&^oR_+oc^aZ~^^q>TbygKVOK6d<Q7<-n(EpvhjA)#f1USxXv|ABp
z+_;P$<<_SPs~Wh5KpLPhq=GYOQBTppL%{3g?lASu$vWf>`dk~SZpcv@=LK<wfzhOy
z1a3HLi*#lUbu_oH8lp?uHUW*sST_@}z!??U|HMcqjo_j(yqA88T71_GmsV4~<RF!u
XKT-UBB+-Xe9p_
+lTo6s#^I243q@q
delta 44
zcmV+{0Mq~K~kf80kGl>vkVR25wl+(_#Cr>Jf{P*en2e>v;IqO53
C}rvbB3T^S+T
CtP@iJ
diff --git a/res/lang/ru.bin b/res/lang/ru.bin
index 92169af5eb439d8723799233c7b0feb4f8ba6e9b..1467d74ceb26b62db4863bc70e4503c3f675087c 100644
GIT binary patch
delta 695
zcmZXSF-rq66vsoswMzx*AUp@5Dg<%tR;^;OiqN{bq?dQs(A?!naz&>)inFt$vv7U@
zaq@F?ad&Yw*Xy11IwbGq<;TlE|K$CrHvg@??KhgtSR~ik48B$&oJbLm6w^FyHsbA>
z3}|9db7mQ3Qj(4vyi(zoYkRqffz$KiKXzEMYFd`hQb&T1GT0<uYCA6e)70A|zaS
zlxc4)rbyD~!<U1dd{GJ^H%<|Boh0bwTWRa|Yojm1SZVu@^%iR=X);E}odQ|`DQc}W
zS>k6*NaX1^>(^%$us{YV(X+@G8b}4ABc&!G(-T9Ei>$o{InR70ITpue%}V16dRAJW
zE8I083`EKTgDI1OL!SkjMF9fdWp{_AZ?x!8HW&+ImA)tEd0ZDHI7ZfxUKWHEXdE(J
zHIy3RUNuIK3|s;Rt#xi@V1=_fasP>vVIILlZH1qIigtPw&$qU6z2r2PT|QA-fBdLz
P7Cx8dNM1a
K0L|+5`h<
delta 44
zcmV+{0Mq~C_X4!?0<bC*vs4pb9kVAfsVlSlT)qXf4r4<Ov!rt27qg;@x&pI&jb<*v
CY7^7|
diff --git a/res/lang/sk.bin b/res/lang/sk.bin
index 4d1979337e88065c628a73f97db9ea6486346cbc..b92fdc800b3f131d573f84fb8d0bfc1955d0472b 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtCstk6v;3c_;bjcvNbP%gmEN!LI
T^3*>oqjJ97$Sqs++sJt2zp|
zxH<a)T%5#j;N)tq*E{KTNZ!lKkC%V$@@p)_p|WwQ
}(2B5B7n^tBqni4<W^G0mf|
z;!|lVeL6O%IkSv1DM{50o-6Wk7}sI9ffzz<97^Fai)L?;90_AY;FEeETJv<FQ=rI&
z3XpK+QKr3tm>Mn%deZ;$s!X%uACyMIZ04WwxXl
_DWNXLZ$6LR-dgQqsagncM50@
zq^PyhWR9OPA(2OS2hXe19GD^<l;~OHGYupM(VkMn5z|9Mj?=8N1Ub+8N^(q(jhki0
zrSvqjK2f-1Ko|&=1qKr)1&1c{H4A(MypHb<bKhv%A!{%Y#wvY7&Xc$-NN^0SA@w*2
zE6_NkyJ#pi!o6yU9_hFQ3|i~ljKLgd_1OI<PP$11549Ej<Wp24dU#nXCwj?gB0GDc
V=%(>dSWkV<%HOw-(OTkm9h0-^u_
delta 44
zcmV+{0Mq~X+5ySc0kH82vl|MQ4YMT}!y2;_Ge!fmt~Q?tv!Fmw4YLhSmjScHPqQ7Z
Ctr8Fb
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index d733cbef4c176df57a1c9084a02de01a72d976fd..75e3ac79e96781591791a30466994e6d5433fbfc 100644
GIT binary patch
delta 695
zcmZXSF-rq66vsnBa1jIr5glF!K^@}Wty;xm6`}3oBI)Jz8k)NtNv<dkbr2N%9?rr+
zH$Q>jLi7uh?}
y@1)lucq+NUjF$flkdvsPv!MtZIc;
<T9P1uT=m?Qbaw)G*8Uh
z-S$}eG&ZO?vy3t+N!<-zD)Kl=>#%!<7(pYBr0|$Uvo}u$!Wa?yq}hk_SvuD#ROCX1
zNVxJS)80UgkR-nc)%|L=D1?x%Qv?ks3F_HavU)XHYKb6H+WuqB=^6@}43KfBfR;dt
zS}RRv_z4pddGdDszA`R>InqXno<%;@KuQqpDK!k39vX6(XSD^$dDd5wV}5MftS~O8
z$A$Ho!W{#K&UJ*7%?e0w3x41=p*1wdUu%lM)M9ugMlzs>05G|#YI7aV
vR&ra@SN
z#vz?~L#Yw&RUCB+a+MoTIXg8mN=_p_n$cFWDz{nR
jz^QA-{JzO|9*CC8cU;)#;4
Tr*36E_qiy4+`lCBd71tJ(+&fC
delta 44
zcmV+{0Mq~E_X4)^0<eS=v#t|r9J7otEGo0(TC4@L<Y2-Lv-xjh8?$+fPXe>(jr=X^
CuodS3
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index 6ac6ac9f5013e4e556d4193c7d3e1e24584950c6..f501f91cd6e1f036fa731d48b4099f145d8b0707 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtDX?9@Rhio@$5=yF2`hfY?jSgcZP{Q&9Z^%|PH97(R!seTA&M@JEE
z6daud@hb@8;GiGDT(5W1>yW&cmme?x{FC3$(%pCI@wL23wL#KNW*92z!;#?OKvKox
zr*f?_6P^=kRE!$xP$5Xu4pu0#C`{_GdxjW7D+&d7sX@KBO!{1F9(bfZg!4tZ)G3hU
zLIy~fbRDYP5uYH5zqa11wRDjSAy-xrw5%j(rd#nt{b#Mi{ZK0NkG1D($Z0Y{%B%vX
z06M6YRAhmlP%e<gxBGAFvjUhQJrw8~WOEIq067CG$39hKO%AiHu>x7ohEg!hj!l~7
z#%1&@w?0+4qk(G(qyYODma4<^%M;}1e_!hlOuW)*){&;@U`cO-|FeDu^=-j3(_Q
zaKlksqz#aqq%+65M9!<31~FNx|x6l&Zx-#Cr0{d1Q(Uz!}L=$;=#dorJCv
$Eoc6
WiQ;MZrL>XxoR>dt?&JF9GyDNPp99wb
delta 44
zcmV+{0Mq}T>jCiP0kF&pv-S!Q5VLw4+!wQfGvx!byEn!Ov(!Qx5VH(ZzyY)DRI(u3
C@)Se>
diff --git a/res/lang/tr.bin b/res/lang/tr.bin
index 0dc81e689f6cfbf624aafe7bdd734f69112bc3af..7ac94fc41164acd26dc6d5b16769ddaeee887105 100644
GIT binary patch
delta 694
zcmZXS!Ab)$5QbCKgHZ6GAcBYSAgG6ZfYO7lRV-FfT0y*}o7rt>cC#ees;Bm46!awS
z&9@NWz?bMrFx%~}bbCl<GWnSN`IG6i^88zQ|E%s%ZIE22Gk8h{a3XlrmsGLjwR*ET
z7QPc}RE!$xP$5Xe4pu6%I7;hqaDf;>D~<$rsX=|XNCsSM9{Qy1!R0(%=oCtFB|{`k
zx(-$DfRB(QYtY<<Y7dyw^k9ftR!g6wvu{%y3l6B$fH6+OstjFc~0aR)A9i9aKsx
zGRMy-7s!&M<G1y33Cxi$3Um#!nFLaToW7L9fU2P;%{;3uLDn))3WoWyX|uw&oE{g>
zXA1W<a1Eg}Kw(4$XV9U(qM?s~kLlfEZkv;LC>jj7Hc~y2^DHh4;tWHhNjnYPaMTv*
zEgI@*ZXY#5mvn6c8jZ1TreKLPDz@K=kzN+TMP;~{fS!g-Co_;&h(PgOm^
^$=BUS
RWh?i&DE~}el85`r-XEUi0x19h
delta 44
zcmV+{0Mq}a{Q>v)0kFCZv*Zjw6SJ5gO&qfjJ?sOsQ9+>zvqek;53^-ktO2tzU34M8
Cx)J>V
diff --git a/res/lang/uk.bin b/res/lang/uk.bin
index 4ada5dfe78ff6365664f0510de1d3012dca88668..82e8208a1e350e6640c57a45601a327a49fb8484 100644
GIT binary patch
delta 695
zcmZXSF-rq66vsosMZe_br7C|pa
dPx7J!Lts=BePU+>{H8yuSlC<blH^Hx=xQT@u
z{R(~xzk(pRnQM9{y$;EHdHM13&p-M3uDtwKrk@)-R2w8$*$jb_5u6I1^d(g+{n%*k
zPDJR9G%7|7^{5b}W(O-3*(k~CaL6fpfO4W_o+dBxJX7^YaWNB6~M(jUFZ}
aw%gZ
zO!^*G{(z5>q-!VaXwNMQA>UBf
j@HMf-pHYH)9wkzlf2=iILqU@PQf3u+CD22q
zq#|>?Nx49luG0GYqy*+j7X|tT*-Qf|L0(_VVMNtXlX{+2mmup|AOwxsNJ$Tux64
z>z%@V4O~Mk4Nw?U!5MUDsAwD_;9YiinEU4C9f}46u8map<lMz&L7ZW1G-+jl8;;r{
zy+uPk&F!lu=##EZK%+6%%?vDYMvd%$Vx;FH_^1pI+^49fZF#%B<@Ay>C%br}^!@R<
RvYGo_l)oNc)8^Ck_zw@M0_Fe!
delta 43
zcmV+0M!4a
2zOy0yv-T1-9kZV>Z!5DoT%!fEF=9Cmv%PU?7_&o)odU9rq%XoG
B6H)*G
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index 818445b6c1acadc47d71819a72a929e1c6b61ed7..4697c37ea83c30ebd4b28c68642dda018fd1a3e2 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtCs?4XliUF10kx*URoPz0$~v06oF{Q&9Z-8D3KIg(t_#X7mTI|>du
zi}w|r++D=U#jjzm*E{KTNZ!lKkC%V$-
&m{j2i&R$ZamAh}3q@RSVTK=7z1sba}f
z^?q?Ad?(hZ7&X+PLXd_XtW;!il-6P66fuHk90~4HgL-qC47k=j^hwKuvq?JDDU{?~
zhDexn9je?uA0kP1YJ0V{Y*7dymsSxptt4n<TZ!1Zo@?_UlFIyJt??QPn)Hz}tH3FN
z4k{%Tnczp13uMXn_Q(9F1m;Kw1-b^=SOY0RPEX1~K-EB#{XDDBK-M!)3WoWyX|uw(
zoE{a{XA0Lfa1Eg}Kw(G)XV9j;qM?s~SLxkh;+vCqC>r#+Hd0-a<1Ee!;tWHhNh=N9
zaMTvP8;fIZeKMmvn3b8jZ1TreKLPDz^WLk!}{jMP;~`eTsVWy!*SboarTpne5_;
VlDm_~%2MugQU1AoNowtz)jxJu0($@e
delta 44
zcmV+{0Mq}(+5wi;0kE<Ov)l=K4ztP_N*J?0F>eF212w@2vll<+4YL|em;tlWPI4Zs
C84>9K
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin
index 17d95f10671415d8add29a367d0178c0e6bb7f1b..54050a83acddf7c09349980a90af7a4fa26abcc2 100644
GIT binary patch
delta 695
zcmZXSF-rq66vtCsN{3ED6$hV#pv%$0so+qpVzElG^#i1rch}I|<w$ZxL8#xuQE(H4
zw-&j=AwgN!O_KBuXobxki3_dA20v>lgE$B
)B24tF}qCLDElW@RSVTSny~hsbWd3
z_Ew(>--$IUMh$hS5TtDfD-~HBrFGalM~t8oM}oW5px$351Fkg>ebV*dVv#O&3MILe
zArdBChbni>r$~~ojn~FbwkU*<YpV!4RuZ(ct>mG3x7Oo9B$fHcy7M&@G#MjhR)JFj
z9aKsxvcOL%7s!(O!Ebd|0&`@50$qb_u7Q*wXC&n$plYJYQJ%F{AnTbY1;hN<v{_+X
zPR|PKGljbvxQ0*~pfII^Gw4xY(a=Z0oAmCm@Xg6P6b;5)8>w!|Sr%6XafYGMq?-nA
zIBJUwmko6^x33zZO9nOpjmB6vQ?SGt72E&B$S{lGqB7jeK1D039`@^vOfNafWEW4A
VT%A8x)^neW^7s8y^3!W>{{iDl0jU50
delta 44
zcmV+{0Mq}-2tQ60k9hivqcKH4zqC?(igLCF>(X5H8t
GvwA;I4zuP>rvbCAPb43t
CHxd>A
diff --git a/src/app.c b/src/app.c
index e2802644..e317e4c6 100644
--- a/src/app.c
+++ b/src/app.c
@@ -23,17 +23,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "app.h"
#include "bookmarks.h"
#include "defs.h"
-#include "resources.h"
+#include "export.h"
#include "feeds.h"
-#include "mimehooks.h"
#include "gmcerts.h"
#include "gmdocument.h"
#include "gmutil.h"
#include "history.h"
#include "ipc.h"
+#include "mimehooks.h"
#include "periodic.h"
+#include "resources.h"
#include "sitespec.h"
-#include "updater.h"
#include "ui/certimportwidget.h"
#include "ui/color.h"
#include "ui/command.h"
@@ -43,13 +43,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "ui/labelwidget.h"
#include "ui/root.h"
#include "ui/sidebarwidget.h"
-#include "ui/touch.h"
#include "ui/text.h"
+#include "ui/touch.h"
#include "ui/uploadwidget.h"
#include "ui/util.h"
#include "ui/window.h"
+#include "updater.h"
#include "visited.h"
+#include <the_Foundation/buffer.h>
#include <the_Foundation/commandline.h>
#include <the_Foundation/file.h>
#include <the_Foundation/fileinfo.h>
@@ -3744,6 +3746,53 @@ iBool handleCommand_App(const char *cmd) {
}
return iTrue;
}
iExport *export = new_Export();
iBuffer *zip = new_Buffer();
generate_Export(export);
openEmpty_Buffer(zip);
serialize_Archive(archive_Export(export), stream_Buffer(zip));
iDocumentWidget *expTab = newTab_App(NULL, iTrue);
iDate now;
initCurrent_Date(&now);
setUrlAndSource_DocumentWidget(
expTab,
collect_String(format_Date(&now, "file:Lagrange_User_Data_%Y-%m-%d_%H%M%S.zip")),
collectNewCStr_String("application/zip"),
data_Buffer(zip));
iRelease(zip);
delete_Export(export);
return iTrue;
const iString *path = collect_String(suffix_Command(cmd, "path"));
iArchive *zip = iClob(new_Archive());
if (openFile_Archive(zip, path)) {
if (!arg_Command(cmd)) {
makeUserDataImporter_Dialog(path);
return iTrue;
}
const int bookmarks = argLabel_Command(cmd, "bookmarks");
const int trusted = argLabel_Command(cmd, "trusted");
const int idents = argLabel_Command(cmd, "idents");
const int visited = argLabel_Command(cmd, "visited");
const int siteSpec = argLabel_Command(cmd, "sitespec");
iExport *export = new_Export();
if (load_Export(export, zip)) {
import_Export(export, bookmarks, idents, trusted, visited, siteSpec);
}
else {
makeSimpleMessage_Widget(uiHeading_ColorEscape "${heading.import.userdata.error}",
format_Lang("${import.userdata.error}", cstr_String(path)));
}
delete_Export(export);
}
else {
makeSimpleMessage_Widget(uiHeading_ColorEscape "${heading.import.userdata.error}",
format_Lang("${import.userdata.error}", cstr_String(path)));
}
return iTrue;
#if defined (LAGRANGE_ENABLE_IPC)
else if (equal_Command(cmd, "ipc.list.urls")) {
iProcessId pid = argLabel_Command(cmd, "pid");
diff --git a/src/bookmarks.c b/src/bookmarks.c
index 500caa38..6f4b13ca 100644
--- a/src/bookmarks.c
+++ b/src/bookmarks.c
@@ -277,21 +277,43 @@ static void loadOldFormat_Bookmarks(iBookmarks *d, const char *dirPath) {
iDeclareType(BookmarkLoader)
struct Impl_BookmarkLoader {
};
static void handleTable_BookmarkLoader_(void *context, const iString *table, iBool isStart) {
iBookmarkLoader *d = context;
if (isStart) {
iAssert(!d->bm);
iAssert(d->method != none_ImportMethod);
d->bm = new_Bookmark();
const int id = toInt_String(table);
d->bookmarks->idEnum = iMax(d->bookmarks->idEnum, id);
insertId_Bookmarks_(d->bookmarks, d->bm, id);
d->loadId = toInt_String(table) + d->baseId;
/* Check if import rules. */
if (d->baseId && !isFolder_Bookmark(d->bm)) {
const uint32_t existing = findUrl_Bookmarks(d->bookmarks, &d->bm->url);
if (existing) {
if (d->method == ifMissing_ImportMethod) {
/* Already have this one. */
delete_Bookmark(d->bm);
d->bm = NULL;
return;
}
else {
d->bm->parentId = d->dupFolderId;
d->didImportDuplicates = iTrue;
}
}
}
d->bookmarks->idEnum = iMax(d->bookmarks->idEnum, d->loadId);
insertId_Bookmarks_(d->bookmarks, d->bm, d->loadId);
d->bm = NULL;
}
}
@@ -319,7 +341,7 @@ static void handleKeyValue_BookmarkLoader_(void *context, const iString *table,
initSeconds_Time(&bm->when, tv->value.int64);
}
else if (!cmp_String(key, "parent") && tv->type == int64_TomlType) {
bm->parentId = tv->value.int64;
bm->parentId = tv->value.int64 + d->baseId;
}
else if (!cmp_String(key, "order") && tv->type == int64_TomlType) {
bm->order = tv->value.int64;
@@ -335,16 +357,29 @@ static void init_BookmarkLoader(iBookmarkLoader *d, iBookmarks *bookmarks) {
setHandlers_TomlParser(d->toml, handleTable_BookmarkLoader_, handleKeyValue_BookmarkLoader_, d);
d->bookmarks = bookmarks;
d->bm = NULL;
}
static void deinit_BookmarkLoader(iBookmarkLoader *d) {
delete_TomlParser(d->toml);
}
-static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) {
fprintf(stderr, "[Bookmarks] syntax error(s) in %s\n", cstr_String(path_File(file)));
+static void load_BookmarkLoader(iBookmarkLoader *d, iStream *stream) {
/* Make a folder for possible duplicate bookmarks. */
d->dupFolderId =
add_Bookmarks(d->bookmarks, NULL, string_Lang("import.userdata.dupfolder"), NULL, 0);
fprintf(stderr, "[Bookmarks] syntax error in bookmarks.ini\n");
remove_Bookmarks(d->bookmarks, d->dupFolderId);
}
iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b)
@@ -364,6 +399,46 @@ void sort_Bookmarks(iBookmarks *d, uint32_t parentId, iBookmarksCompareFunc cmp)
unlock_Mutex(d->mtx);
}
+static void mergeFolders_BookmarkLoader(iBookmarkLoader *d) {
/* Only merge after importing. */
return;
iBookmark *imported = (iBookmark *) i.value;
if (isFolder_Bookmark(imported) && id_Bookmark(imported) >= d->baseId) {
/* If there already is a folder with a matching name, merge this one into it. */
iForEach(Hash, j, hash) {
iBookmark *old = (iBookmark *) j.value;
if (isFolder_Bookmark(old) && id_Bookmark(old) < d->baseId &&
equal_String(&imported->title, &old->title)) {
iForEach(Hash, k, hash) {
iBookmark *bm = (iBookmark *) k.value;
if (bm->parentId == id_Bookmark(imported)) {
bm->parentId = id_Bookmark(old);
}
}
remove_HashIterator(&i);
delete_Bookmark(imported);
break;
}
}
}
+}
+void deserialize_Bookmarks(iBookmarks *d, iStream *ins, enum iImportMethod method) {
+}
void load_Bookmarks(iBookmarks *d, const char *dirPath) {
clear_Bookmarks(d);
/* Load new .ini bookmarks, if present. */
@@ -377,49 +452,53 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) {
}
iBookmarkLoader loader;
init_BookmarkLoader(&loader, d);
deinit_BookmarkLoader(&loader);
}
+void serialize_Bookmarks(const iBookmarks *d, iStream *out) {
const iBookmark *bm = (const iBookmark *) i.value;
if (bm->flags & remote_BookmarkFlag) {
/* Remote bookmarks are not saved. */
continue;
}
iBeginCollect();
const iString *packedTags = collect_String(packedDotTags_Bookmark_(bm));
format_String(str,
"[%d]\n"
"url = \"%s\"\n"
"title = \"%s\"\n"
"tags = \"%s\"\n"
"icon = 0x%x\n"
"created = %.0f # %s\n",
id_Bookmark(bm),
cstrCollect_String(quote_String(&bm->url, iFalse)),
cstrCollect_String(quote_String(&bm->title, iFalse)),
cstrCollect_String(quote_String(packedTags, iFalse)),
bm->icon,
seconds_Time(&bm->when),
cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d")));
if (bm->parentId) {
appendFormat_String(str, "parent = %d\n", bm->parentId);
}
if (bm->order) {
appendFormat_String(str, "order = %d\n", bm->order);
}
appendCStr_String(str, "\n");
writeData_Stream(out, cstr_String(str), size_String(str));
iEndCollect();
+}
void save_Bookmarks(const iBookmarks *d, const char *dirPath) {
lock_Mutex(d->mtx);
iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_));
iString *str = collectNew_String();
format_String(str, "recentfolder = %u\n\n", d->recentFolderId);
writeData_File(f, cstr_String(str), size_String(str));
iConstForEach(Hash, i, &d->bookmarks) {
const iBookmark *bm = (const iBookmark *) i.value;
if (bm->flags & remote_BookmarkFlag) {
/* Remote bookmarks are not saved. */
continue;
}
iBeginCollect();
const iString *packedTags = collect_String(packedDotTags_Bookmark_(bm));
format_String(str,
"[%d]\n"
"url = \"%s\"\n"
"title = \"%s\"\n"
"tags = \"%s\"\n"
"icon = 0x%x\n"
"created = %.0f # %s\n",
id_Bookmark(bm),
cstrCollect_String(quote_String(&bm->url, iFalse)),
cstrCollect_String(quote_String(&bm->title, iFalse)),
cstrCollect_String(quote_String(packedTags, iFalse)),
bm->icon,
seconds_Time(&bm->when),
cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d")));
if (bm->parentId) {
appendFormat_String(str, "parent = %d\n", bm->parentId);
}
if (bm->order) {
appendFormat_String(str, "order = %d\n", bm->order);
}
appendCStr_String(str, "\n");
writeData_File(f, cstr_String(str), size_String(str));
iEndCollect();
}
serialize_Bookmarks(d, stream_File(f));
}
iRelease(f);
unlock_Mutex(d->mtx);
diff --git a/src/bookmarks.h b/src/bookmarks.h
index 08afdd8b..13a93748 100644
--- a/src/bookmarks.h
+++ b/src/bookmarks.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#pragma once
+#include "defs.h"
#include <the_Foundation/hash.h>
#include <the_Foundation/ptrarray.h>
#include <the_Foundation/string.h>
@@ -97,6 +98,8 @@ typedef int (*iBookmarksCompareFunc) (const iBookmark **, const iBookmark **)
void clear_Bookmarks (iBookmarks *);
void load_Bookmarks (iBookmarks *, const char *dirPath);
void save_Bookmarks (const iBookmarks *, const char *dirPath);
+void serialize_Bookmarks (const iBookmarks *, iStream *outs);
+void deserialize_Bookmarks (iBookmarks *, iStream *ins, enum iImportMethod);
uint32_t add_Bookmarks (iBookmarks *, const iString *url, const iString *title,
const iString *tags, iChar icon);
diff --git a/src/defs.h b/src/defs.h
index fed7fe82..93db7fa0 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -31,6 +31,12 @@ enum iSourceFormat {
markdown_SourceFormat,
};
+enum iImportMethod {
+};
enum iFileVersion {
initial_FileVersion = 0,
addedResponseTimestamps_FileVersion = 1,
@@ -127,7 +133,8 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
#define backArrow_Icon "\U0001f870"
#define forwardArrow_Icon "\U0001f872"
#define upArrow_Icon "\U0001f871"
-#define upArrowBar_Icon "\u2912"
+#define upArrowBar_Icon "\u2b71"
+#define keyUpArrow_Icon "\u2191"
#define downArrowBar_Icon "\u2913"
#define rightArrowWhite_Icon "\u21e8"
#define rightArrow_Icon "\u279e"
@@ -152,7 +159,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
#define check_Icon "\u2714"
#define ballotChecked_Icon "\u2611"
#define ballotUnchecked_Icon "\u2610"
-#define inbox_Icon "\U0001f4e5"
+#define import_Icon "\U0001f4e5"
#define book_Icon "\U0001f56e"
#define bookmark_Icon "\U0001f516"
#define folder_Icon "\U0001f4c1"
diff --git a/src/export.c b/src/export.c
new file mode 100644
index 00000000..a343020a
--- /dev/null
+++ b/src/export.c
@@ -0,0 +1,205 @@
+/* Copyright 2022 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#include "export.h"
+#include "app.h"
+#include "bookmarks.h"
+#include "gmcerts.h"
+#include "sitespec.h"
+#include "visited.h"
+#include <the_Foundation/buffer.h>
+#include <the_Foundation/file.h>
+#include <the_Foundation/fileinfo.h>
+#include <the_Foundation/path.h>
+#include <the_Foundation/time.h>
+const char *mimeType_Export = "application/lagrange-export+zip";
+struct Impl_Export {
+};
+iDefineTypeConstruction(Export)
+static const char *metadataEntryName_Export_ = "lagrange-export.ini";
+void init_Export(iExport *d) {
+}
+void deinit_Export(iExport *d) {
+}
+void generate_Export(iExport *d) {
"# Lagrange user data exported on %s\n"
"version = \"" LAGRANGE_APP_VERSION "\"\n"
"timestamp = %llu\n",
cstrCollect_String(format_Date(&today, "%Y-%m-%d %H:%M")),
(unsigned long long) integralSeconds_Time(&now));
openEmpty_Buffer(buf);
serialize_Bookmarks(bookmarks_App(), stream_Buffer(buf));
setDataCStr_Archive(d->arch, "bookmarks.ini", data_Buffer(buf));
close_Buffer(buf);
iBuffer *buf2 = new_Buffer();
openEmpty_Buffer(buf2);
openEmpty_Buffer(buf);
serialize_GmCerts(certs_App(), stream_Buffer(buf), stream_Buffer(buf2));
setDataCStr_Archive(d->arch, "trusted.txt", data_Buffer(buf));
setDataCStr_Archive(d->arch, "idents.lgr", data_Buffer(buf2));
iRelease(buf2);
iForEach(DirFileInfo,
info,
iClob(new_DirFileInfo(collect_String(concatCStr_Path(dataDir_App(), "idents"))))) {
const iString *idPath = path_FileInfo(info.value);
const iRangecc baseName = baseName_Path(idPath);
if (!startsWith_Rangecc(baseName, ".") &&
(endsWith_Rangecc(baseName, ".crt") || endsWith_Rangecc(baseName, ".key"))) {
iFile *f = new_File(idPath);
if (open_File(f, readOnly_FileMode)) {
setData_Archive(d->arch,
collectNewFormat_String("idents/%s", cstr_Rangecc(baseName)),
collect_Block(readAll_File(f)));
}
iRelease(f);
}
}
close_Buffer(buf);
openEmpty_Buffer(buf);
serialize_SiteSpec(stream_Buffer(buf));
setDataCStr_Archive(d->arch, "sitespec.ini", data_Buffer(buf));
close_Buffer(buf);
openEmpty_Buffer(buf);
serialize_Visited(visited_App(), stream_Buffer(buf));
setDataCStr_Archive(d->arch, "visited.txt", data_Buffer(buf));
close_Buffer(buf);
+}
+iBool load_Export(iExport *d, const iArchive *archive) {
return iFalse;
+}
+iBuffer *openEntryBuffer_Export_(const iExport *d, const char *entryPath) {
return buf;
+}
+void import_Export(const iExport *d, enum iImportMethod bookmarks, enum iImportMethod identities,
enum iImportMethod trusted, enum iImportMethod visited,
enum iImportMethod siteSpec) {
iBuffer *buf = openEntryBuffer_Export_(d, "bookmarks.ini");
if (buf) {
deserialize_Bookmarks(bookmarks_App(), stream_Buffer(buf), bookmarks);
iRelease(buf);
postCommand_App("bookmarks.changed");
}
iBuffer *buf = openEntryBuffer_Export_(d, "trusted.txt");
if (buf) {
deserializeTrusted_GmCerts(certs_App(), stream_Buffer(buf), trusted);
iRelease(buf);
}
/* First extract any missing .crt/.key files to the idents directory. */
const iString *identsDir = collect_String(concatCStr_Path(dataDir_App(), "idents"));
iConstForEach(StringSet, i,
iClob(listDirectory_Archive(d->arch, collectNewCStr_String("idents/")))) {
iString *dataPath = concatCStr_Path(identsDir, cstr_Rangecc(baseName_Path(i.value)));
if (identities == all_ImportMethod || !fileExists_FileInfo(dataPath)) {
iFile *f = new_File(dataPath);
if (open_File(f, writeOnly_FileMode)) {
write_File(f, data_Archive(d->arch, i.value));
}
iRelease(f);
}
delete_String(dataPath);
}
iBuffer *buf = openEntryBuffer_Export_(d, "idents.lgr");
if (buf) {
deserializeIdentities_GmCerts(certs_App(), stream_Buffer(buf), identities);
iRelease(buf);
postCommand_App("idents.changed");
}
iBuffer *buf = openEntryBuffer_Export_(d, "visited.txt");
if (buf) {
deserialize_Visited(visited_App(), stream_Buffer(buf), iTrue /* keep latest */);
iRelease(buf);
postCommand_App("visited.changed");
}
iBuffer *buf = openEntryBuffer_Export_(d, "sitespec.ini");
if (buf) {
deserialize_SiteSpec(stream_Buffer(buf), siteSpec);
iRelease(buf);
}
+}
+iBool detect_Export(const iArchive *d) {
return iTrue;
+}
+const iArchive *archive_Export(const iExport *d) {
+}
diff --git a/src/export.h b/src/export.h
new file mode 100644
index 00000000..149a08c8
--- /dev/null
+++ b/src/export.h
@@ -0,0 +1,44 @@
+/* Copyright 2022 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#pragma once
+#include "defs.h"
+#include <the_Foundation/archive.h>
+extern const char *mimeType_Export;
+iDeclareType(Export)
+iDeclareTypeConstruction(Export)
+void generate_Export (iExport *);
+iBool load_Export (iExport *, const iArchive *archive);
+void import_Export (const iExport *,
enum iImportMethod bookmarks,
enum iImportMethod identities,
enum iImportMethod trusted,
enum iImportMethod visited,
enum iImportMethod siteSpec);
+iBool detect_Export (const iArchive *);
+const iArchive * archive_Export (const iExport *);
diff --git a/src/gmcerts.c b/src/gmcerts.c
index 7b05103b..dc36cb59 100644
--- a/src/gmcerts.c
+++ b/src/gmcerts.c
@@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include <the_Foundation/time.h>
#include <ctype.h>
-static const char *filename_GmCerts_ = "trusted.2.txt";
+static const char *trustedFilename_GmCerts_ = "trusted.2.txt";
static const char *identsDir_GmCerts_ = "idents";
static const char *oldIdentsFilename_GmCerts_ = "idents.binary";
static const char *identsFilename_GmCerts_ = "idents.lgr";
@@ -248,26 +248,8 @@ static const char *magicIdentity_GmCerts_ = "iden";
iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir)
-void saveIdentities_GmCerts(const iGmCerts *d) {
writeData_File(f, magicIdMeta_GmCerts_, 4);
writeU32_File(f, idents_FileVersion); /* version */
iConstForEach(PtrArray, i, &d->idents) {
const iGmIdentity *ident = i.ptr;
if (~ident->flags & temporary_GmIdentityFlag) {
writeData_File(f, magicIdentity_GmCerts_, 4);
serialize_GmIdentity(ident, stream_File(f));
}
}
-}
-static void save_GmCerts_(const iGmCerts *d) {
+void serialize_GmCerts(const iGmCerts *d, iStream *trusted, iStream *identsMeta) {
iString line;
init_String(&line);
iConstForEach(StringHash, i, d->trusted) {
@@ -277,57 +259,39 @@ static void save_GmCerts_(const iGmCerts *d) {
cstr_String(key_StringHashConstIterator(&i)),
integralSeconds_Time(&trust->validUntil),
cstrCollect_String(hexEncode_Block(&trust->fingerprint)));
write_File(f, &line.chars);
write_Stream(trusted, &line.chars);
}
deinit_String(&line);
deinit_String(&line);
}
-}
-static void loadIdentities_GmCerts_(iGmCerts *d) {
char magic[4];
readData_File(f, sizeof(magic), magic);
if (memcmp(magic, magicIdMeta_GmCerts_, sizeof(magic))) {
printf("%s: format not recognized\n", cstr_String(path_File(f)));
return;
}
const uint32_t version = readU32_File(f);
if (version > latest_FileVersion) {
printf("%s: unsupported version\n", cstr_String(path_File(f)));
return;
}
setVersion_Stream(stream_File(f), version);
while (!atEnd_File(f)) {
readData_File(f, sizeof(magic), magic);
if (!memcmp(magic, magicIdentity_GmCerts_, sizeof(magic))) {
iGmIdentity *id = new_GmIdentity();
deserialize_GmIdentity(id, stream_File(f));
pushBack_PtrArray(&d->idents, id);
}
else {
printf("%s: invalid file contents\n", cstr_String(path_File(f)));
break;
writeData_Stream(identsMeta, magicIdMeta_GmCerts_, 4);
writeU32_Stream(identsMeta, idents_FileVersion); /* version */
iConstForEach(PtrArray, i, &d->idents) {
const iGmIdentity *ident = i.ptr;
if (~ident->flags & temporary_GmIdentityFlag) {
writeData_Stream(identsMeta, magicIdentity_GmCerts_, 4);
serialize_GmIdentity(ident, identsMeta);
}
}
}
}
}
-iGmIdentity *findIdentity_GmCerts(iGmCerts *d, const iBlock *fingerprint) {
return NULL;
+void saveIdentities_GmCerts(const iGmCerts *d) {
serialize_GmCerts(d, NULL, stream_File(f));
}
iGmIdentity *ident = i.ptr;
if (cmp_Block(fingerprint, &ident->fingerprint) == 0) { /* TODO: could use a hash */
return ident;
}
+}
+static void save_GmCerts_(const iGmCerts *d) {
serialize_GmCerts(d, stream_File(f), NULL);
}
}
static void loadIdentityFromCertificate_GmCerts_(iGmCerts *d, const iString *crtPath) {
@@ -354,53 +318,127 @@ static void loadIdentityFromCertificate_GmCerts_(iGmCerts *d, const iString *crt
delete_Block(finger);
}
-static void load_GmCerts_(iGmCerts *d) {
iRegExp * pattern = new_RegExp("([^\\s]+) ([0-9]+) ([a-z0-9]+)", 0);
const iRangecc src = range_Block(collect_Block(readAll_File(f)));
iRangecc line = iNullRange;
while (nextSplit_Rangecc(src, "\n", &line)) {
iRegExpMatch m;
init_RegExpMatch(&m);
if (matchRange_RegExp(pattern, line, &m)) {
const iRangecc key = capturedRange_RegExpMatch(&m, 1);
const iRangecc until = capturedRange_RegExpMatch(&m, 2);
const iRangecc fp = capturedRange_RegExpMatch(&m, 3);
time_t sec;
sscanf(until.start, "%ld", &sec);
iDate untilDate;
initSinceEpoch_Date(&untilDate, sec);
insert_StringHash(d->trusted,
collect_String(newRange_String(key)),
new_TrustEntry(collect_Block(hexDecode_Rangecc(fp)),
&untilDate));
}
+static void loadIdentityCertsAndDiscardInvalid_GmCerts_(iGmCerts *d) {
makeDirs_Path(idDir);
const iFileInfo *entry = i.value;
if (endsWithCase_String(path_FileInfo(entry), ".crt")) {
loadIdentityFromCertificate_GmCerts_(d, path_FileInfo(entry));
}
iRelease(pattern);
}
loadIdentities_GmCerts_(d);
const iString *idDir = collect_String(concatCStr_Path(&d->saveDir, identsDir_GmCerts_));
if (!fileExists_FileInfo(idDir)) {
makeDirs_Path(idDir);
iGmIdentity *ident = j.ptr;
if (!isValid_GmIdentity_(ident)) {
delete_GmIdentity(ident);
remove_PtrArrayIterator(&j);
}
iForEach(DirFileInfo, i, iClob(directoryContents_FileInfo(iClob(new_FileInfo(idDir))))) {
const iFileInfo *entry = i.value;
if (endsWithCase_String(path_FileInfo(entry), ".crt")) {
loadIdentityFromCertificate_GmCerts_(d, path_FileInfo(entry));
+}
+iBool deserializeIdentities_GmCerts(iGmCerts *d, iStream *ins, enum iImportMethod method) {
fprintf(stderr, "[GmCerts] idents file format not recognized\n");
return iFalse;
fprintf(stderr, "[GmCerts] unsupported version (%u)\n", version);
return iFalse;
readData_Stream(ins, sizeof(magic), magic);
if (!memcmp(magic, magicIdentity_GmCerts_, sizeof(magic))) {
iGmIdentity *id = new_GmIdentity();
deserialize_GmIdentity(id, ins);
if (method == all_ImportMethod ||
(method == ifMissing_ImportMethod && !findIdentity_GmCerts(d, &id->fingerprint))) {
pushBack_PtrArray(&d->idents, id);
}
else {
delete_GmIdentity(id);
}
}
/* Remove certificates whose crt/key files were missing. */
iForEach(PtrArray, j, &d->idents) {
iGmIdentity *ident = j.ptr;
if (!isValid_GmIdentity_(ident)) {
delete_GmIdentity(ident);
remove_PtrArrayIterator(&j);
else {
fprintf(stderr, "[GmCerts] invalid idents file\n");
return iFalse;
}
+}
+static void loadIdentities_GmCerts_(iGmCerts *d) {
deserializeIdentities_GmCerts(d, stream_File(f), all_ImportMethod);
/* In any case, load any .crt/.key files that may be present in the "idents" dir. */
loadIdentityCertsAndDiscardInvalid_GmCerts_(d);
+}
+iGmIdentity *findIdentity_GmCerts(iGmCerts *d, const iBlock *fingerprint) {
return NULL;
iGmIdentity *ident = i.ptr;
if (cmp_Block(fingerprint, &ident->fingerprint) == 0) { /* TODO: could use a hash */
return ident;
}
+}
+void deserializeTrusted_GmCerts(iGmCerts *d, iStream *ins, enum iImportMethod method) {
iRegExpMatch m;
init_RegExpMatch(&m);
if (matchRange_RegExp(pattern, line, &m)) {
iBeginCollect();
const iRangecc key = capturedRange_RegExpMatch(&m, 1);
const iRangecc until = capturedRange_RegExpMatch(&m, 2);
const iRangecc fp = capturedRange_RegExpMatch(&m, 3);
time_t sec;
sscanf(until.start, "%ld", &sec);
iDate untilDate;
initSinceEpoch_Date(&untilDate, sec);
/* TODO: import method? */
const iString *hashKey = collect_String(newRange_String(key));
if (method == all_ImportMethod ||
(method == ifMissing_ImportMethod && !contains_StringHash(d->trusted, hashKey))) {
insert_StringHash(d->trusted,
hashKey,
new_TrustEntry(collect_Block(hexDecode_Rangecc(fp)), &untilDate));
}
iEndCollect();
}
}
+}
+static void load_GmCerts_(iGmCerts *d) {
deserializeTrusted_GmCerts(d, stream_File(f), all_ImportMethod);
}
iBool verify_GmCerts_(iTlsRequest *request, const iTlsCertificate *cert, int depth) {
diff --git a/src/gmcerts.h b/src/gmcerts.h
index 6ece1954..b451a690 100644
--- a/src/gmcerts.h
+++ b/src/gmcerts.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#pragma once
+#include "defs.h"
#include <the_Foundation/ptrarray.h>
#include <the_Foundation/stringset.h>
#include <the_Foundation/tlsrequest.h>
@@ -87,6 +88,9 @@ void importIdentity_GmCerts (iGmCerts *, iTlsCertificate *cert,
const iString *notes); /* takes ownership */
void deleteIdentity_GmCerts (iGmCerts *, iGmIdentity *identity);
void saveIdentities_GmCerts (const iGmCerts *);
+void serialize_GmCerts (const iGmCerts *, iStream *trusted, iStream *identsMeta);
+void deserializeTrusted_GmCerts (iGmCerts *, iStream *ins, enum iImportMethod method);
+iBool deserializeIdentities_GmCerts (iGmCerts *, iStream *ins, enum iImportMethod method);
const iString * certificatePath_GmCerts (const iGmCerts *, const iGmIdentity *identity);
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 55b7b471..aa55aca2 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -711,7 +711,7 @@ void submit_GmRequest(iGmRequest *d) {
iString *page = collectNew_String();
iString *parentDir = collectNewRange_String(dirName_Path(path));
#if !defined (iPlatformMobile)
appendFormat_String(page, "=> %s " upArrow_Icon " %s" iPathSeparator "\n\n",
appendFormat_String(page, "=> %s " keyUpArrow_Icon " %s" iPathSeparator "\n\n",
cstrCollect_String(makeFileUrl_String(parentDir)),
cstr_String(parentDir));
#endif
@@ -792,14 +792,14 @@ void submit_GmRequest(iGmRequest *d) {
if (!equal_Rangecc(parentDir, ".")) {
/* A subdirectory. */
appendFormat_String(page,
"=> ../ " upArrow_Icon " %s" iPathSeparator
"=> ../ " keyUpArrow_Icon " %s" iPathSeparator
"\n",
cstr_Rangecc(parentDir));
}
else {
/* Top-level directory. */
appendFormat_String(page,
"=> %s/ " upArrow_Icon " Root\n",
"=> %s/ " keyUpArrow_Icon " Root\n",
cstr_String(containerUrl));
}
appendFormat_String(page, "# %s\n\n", cstr_Rangecc(baseName_Path(collectNewRange_String(curDir))));
diff --git a/src/sitespec.c b/src/sitespec.c
index 0db471d8..5f29e55f 100644
--- a/src/sitespec.c
+++ b/src/sitespec.c
@@ -75,6 +75,7 @@ struct Impl_SiteSpec {
iString saveDir;
iStringHash sites;
iSiteParams *loadParams;
};
static iSiteSpec siteSpec_;
@@ -127,7 +128,10 @@ static void handleIniTable_SiteSpec_(void *context, const iString *table, iBool
}
else {
iAssert(d->loadParams != NULL);
insert_StringHash(&d->sites, table, d->loadParams);
if (d->loadMethod == all_ImportMethod ||
(d->loadMethod == ifMissing_ImportMethod && !contains_StringHash(&d->sites, table))) {
insert_StringHash(&d->sites, table, d->loadParams);
}
iReleasePtr(&d->loadParams);
}
}
@@ -163,62 +167,74 @@ static void handleIniKeyValue_SiteSpec_(void *context, const iString *table, con
}
static iBool load_SiteSpec_(iSiteSpec *d) {
iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, fileName_SiteSpec_)));
if (open_File(f, readOnly_FileMode | text_FileMode)) {
iTomlParser *toml = new_TomlParser();
setHandlers_TomlParser(toml, handleIniTable_SiteSpec_, handleIniKeyValue_SiteSpec_, d);
ok = parse_TomlParser(toml, collect_String(readString_File(f)));
delete_TomlParser(toml);
ok = deserialize_SiteSpec(stream_File(f), all_ImportMethod);
}
iRelease(f);
iAssert(d->loadParams == NULL);
return ok;
}
+iBool deserialize_SiteSpec(iStream *ins, enum iImportMethod loadMethod) {
+}
+void serialize_SiteSpec(iStream *out) {
iBeginCollect();
const iBlock * key = &i.value->keyBlock;
const iSiteParams *params = i.value->object;
clear_String(buf);
if (params->titanPort) {
appendFormat_String(buf, "titanPort = %u\n", params->titanPort);
}
if (!isEmpty_String(¶ms->titanIdentity)) {
appendFormat_String(
buf, "titanIdentity = \"%s\"\n", cstr_String(¶ms->titanIdentity));
}
if (params->dismissWarnings) {
appendFormat_String(buf, "dismissWarnings = 0x%x\n", params->dismissWarnings);
}
if (!isEmpty_StringArray(¶ms->usedIdentities)) {
appendFormat_String(
buf,
"usedIdentities = \"%s\"\n",
cstrCollect_String(joinCStr_StringArray(¶ms->usedIdentities, " ")));
}
if (!isEmpty_String(¶ms->paletteSeed)) {
appendCStr_String(buf, "paletteSeed = \"");
append_String(buf, collect_String(quote_String(¶ms->paletteSeed, iFalse)));
appendCStr_String(buf, "\"\n");
}
if (!params->tlsSessionCache) {
appendCStr_String(buf, "tlsSessionCache = false\n");
}
if (!isEmpty_String(buf)) {
writeData_Stream(out, "[", 1);
writeData_Stream(out, constData_Block(key), size_Block(key));
writeData_Stream(out, "]\n", 2);
appendCStr_String(buf, "\n");
write_Stream(out, utf8_String(buf));
}
iEndCollect();
+}
static void save_SiteSpec_(iSiteSpec *d) {
iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, fileName_SiteSpec_)));
if (open_File(f, writeOnly_FileMode | text_FileMode)) {
iString *buf = new_String();
iConstForEach(StringHash, i, &d->sites) {
iBeginCollect();
const iBlock * key = &i.value->keyBlock;
const iSiteParams *params = i.value->object;
clear_String(buf);
if (params->titanPort) {
appendFormat_String(buf, "titanPort = %u\n", params->titanPort);
}
if (!isEmpty_String(¶ms->titanIdentity)) {
appendFormat_String(
buf, "titanIdentity = \"%s\"\n", cstr_String(¶ms->titanIdentity));
}
if (params->dismissWarnings) {
appendFormat_String(buf, "dismissWarnings = 0x%x\n", params->dismissWarnings);
}
if (!isEmpty_StringArray(¶ms->usedIdentities)) {
appendFormat_String(
buf,
"usedIdentities = \"%s\"\n",
cstrCollect_String(joinCStr_StringArray(¶ms->usedIdentities, " ")));
}
if (!isEmpty_String(¶ms->paletteSeed)) {
appendCStr_String(buf, "paletteSeed = \"");
append_String(buf, collect_String(quote_String(¶ms->paletteSeed, iFalse)));
appendCStr_String(buf, "\"\n");
}
if (!params->tlsSessionCache) {
appendCStr_String(buf, "tlsSessionCache = false\n");
}
if (!isEmpty_String(buf)) {
writeData_File(f, "[", 1);
writeData_File(f, constData_Block(key), size_Block(key));
writeData_File(f, "]\n", 2);
appendCStr_String(buf, "\n");
write_File(f, utf8_String(buf));
}
iEndCollect();
}
delete_String(buf);
serialize_SiteSpec(stream_File(f));
}
iRelease(f);
}
diff --git a/src/sitespec.h b/src/sitespec.h
index 15219c62..a12bdb00 100644
--- a/src/sitespec.h
+++ b/src/sitespec.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#pragma once
+#include "defs.h"
#include <the_Foundation/stringarray.h>
iDeclareType(SiteSpec)
@@ -38,6 +39,9 @@ enum iSiteSpecKey {
void init_SiteSpec (const char *saveDir);
void deinit_SiteSpec (void);
+void serialize_SiteSpec (iStream *);
+iBool deserialize_SiteSpec (iStream *, enum iImportMethod);
/* changes saved immediately */
void setValue_SiteSpec (const iString *site, enum iSiteSpecKey key, int value);
void setValueString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index d6c63a30..14fd2433 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "bookmarks.h"
#include "command.h"
#include "defs.h"
+#include "export.h"
#include "gempub.h"
#include "gmcerts.h"
#include "gmdocument.h"
@@ -2415,6 +2416,9 @@ static const char *zipPageHeading_(const iRangecc mime) {
else if (equalCase_Rangecc(mime, mimeType_FontPack)) {
return fontpack_Icon " Fontpack";
}
return package_Icon " ${heading.archive.userdata}";
iRangecc type = iNullRange;
nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */
nextSplit_Rangecc(mime, "/", &type);
@@ -2701,13 +2705,15 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
(equal_Rangecc(param, "application/zip") ||
(startsWith_Rangecc(param, "application/") &&
endsWithCase_Rangecc(param, "+zip")))) {
iArray *footerItems = collectNew_Array(sizeof(iMenuItem));
clear_String(&str);
docFormat = gemini_SourceFormat;
setRange_String(&d->sourceMime, param);
iArchive *zip = new_Archive();
openData_Archive(zip, &response->body);
if (equal_Rangecc(param, mimeType_FontPack)) {
/* Show some information about fontpacks, and set up footer actions. */
iArchive *zip = iClob(new_Archive());
if (openData_Archive(zip, &response->body)) {
if (isOpen_Archive(zip)) {
iFontPack *fp = new_FontPack();
setUrl_FontPack(fp, d->mod.url);
setStandalone_FontPack(fp, iTrue);
@@ -2726,28 +2732,54 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
}
}
else {
format_String(&str, "# %s\n", zipPageHeading_(param));
if (detect_Export(zip)) {
setCStr_String(&d->sourceMime, mimeType_Export);
pushBack_Array(footerItems,
&(iMenuItem){ openExt_Icon " ${menu.open.external}",
SDLK_RETURN,
KMOD_PRIMARY,
"document.save extview:1" });
}
format_String(&str, "# %s\n", zipPageHeading_(range_String(&d->sourceMime)));
appendFormat_String(&str,
cstr_Lang("doc.archive"),
cstr_Rangecc(baseName_Path(d->mod.url)));
appendCStr_String(&str, "\n");
}
iRelease(zip);
appendCStr_String(&str, "\n");
iString *localPath = localFilePathFromUrl_String(d->mod.url);
if (!localPath) {
if (!localPath || !fileExists_FileInfo(localPath)) {
iString *key = collectNew_String();
toString_Sym(SDLK_s, KMOD_PRIMARY, key);
appendFormat_String(&str, "%s\n\n",
format_CStr(cstr_Lang("error.unsupported.suggestsave"),
cstr_String(key),
saveToDownloads_Label));
pushBack_Array(footerItems,
&(iMenuItem){ translateCStr_Lang(download_Icon
" " saveToDownloads_Label),
0,
0,
"document.save" });
}
delete_String(localPath);
if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
appendFormat_String(&str, "=> %s/ " folder_Icon " ${doc.archive.view}\n",
if (localPath && fileExists_FileInfo(localPath)) {
if (!cmp_String(&d->sourceMime, mimeType_Export)) {
pushFront_Array(footerItems,
&(iMenuItem){ import_Icon " ${menu.import}",
SDLK_RETURN,
0,
format_CStr("!import path:%s",
cstr_String(localPath)) });
}
appendFormat_String(&str,
"=> %s/ " folder_Icon " ${doc.archive.view}\n",
cstr_String(withSpacesEncoded_String(d->mod.url)));
}
delete_String(localPath);
translate_Lang(&str);
makeFooterButtons_DocumentWidget_(
d, constData_Array(footerItems), size_Array(footerItems));
}
else if (startsWith_Rangecc(param, "image/") ||
startsWith_Rangecc(param, "audio/")) {
@@ -4287,7 +4319,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
}
else if (!isEmpty_Block(&d->sourceContent)) {
if (argLabel_Command(cmd, "extview")) {
if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file") &&
fileExists_FileInfo(collect_String(localFilePathFromUrl_String(d->mod.url)))) {
/* Already a file so just open it directly. */
postCommandf_Root(w->root, "!open default:1 url:%s", cstr_String(d->mod.url));
}
diff --git a/src/ui/util.c b/src/ui/util.c
index c3e39821..608ee116 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "command.h"
#include "defs.h"
#include "documentwidget.h"
+#include "export.h"
#include "feeds.h"
#include "gmutil.h"
#include "inputwidget.h"
@@ -2658,7 +2659,7 @@ iWidget *makePreferences_Widget(void) {
const iMenuItem identityPanelItems[] = {
{ "title id:sidebar.identities" },
{ "certlist" },
{ "navi.action id:prefs.ident.import text:" inbox_Icon, 0, 0, "ident.import" },
{ "navi.action id:prefs.ident.import text:" import_Icon, 0, 0, "ident.import" },
{ "navi.action id:prefs.ident.new text:" add_Icon, 0, 0, "ident.new" },
{ NULL }
};
@@ -3718,6 +3719,118 @@ iWidget *makeGlyphFinder_Widget(void) {
return dlg;
}
+static enum iImportMethod checkImportMethod_(const iWidget *dlg, const char *id) {
: isSelected_Widget(findChild_Widget(dlg, format_CStr("%s.1", id)))
? ifMissing_ImportMethod
: all_ImportMethod;
+}
+static iBool handleUserDataImporterCommands_(iWidget *dlg, const char *cmd) {
equalWidget_Command(cmd, dlg, "importer.accept")) {
if (equal_Command(cmd, "importer.accept")) {
/* Compose the final import command. */
enum iImportMethod bookmarkMethod = checkImportMethod_(dlg, "importer.bookmark");
enum iImportMethod identMethod = checkImportMethod_(dlg, "importer.idents");
enum iImportMethod trustedMethod = checkImportMethod_(dlg, "importer.trusted");
enum iImportMethod sitespecMethod = checkImportMethod_(dlg, "importer.sitespec");
enum iImportMethod visitedMethod =
isSelected_Widget(findChild_Widget(dlg, "importer.history")) ? all_ImportMethod
: none_ImportMethod;
postCommandf_App(
"import arg:1 bookmarks:%d idents:%d trusted:%d visited:%d sitespec:%d path:%s",
bookmarkMethod,
identMethod,
trustedMethod,
visitedMethod,
sitespecMethod,
suffixPtr_Command(cmd, "path"));
}
setupSheetTransition_Mobile(dlg, dialogTransitionDir_Widget(dlg));
destroy_Widget(dlg);
return iTrue;
postCommand_Widget(findChild_Widget(dlg, "importer.bookmark.1"), "trigger");
postCommand_Widget(findChild_Widget(dlg, "importer.trusted.1"), "trigger");
postCommand_Widget(findChild_Widget(dlg, "importer.idents.1"), "trigger");
postCommand_Widget(findChild_Widget(dlg, "importer.sitespec.1"), "trigger");
setToggle_Widget(findChild_Widget(dlg, "importer.history"), iTrue);
return iTrue;
+}
+iWidget *makeUserDataImporter_Dialog(const iString *archivePath) {
{ "${menu.selectall}", 0, 0, "importer.selectall" },
{ "---" },
{ "${cancel}", SDLK_ESCAPE, 0, "importer.cancel" },
{ uiTextAction_ColorEscape "${import.userdata}",
SDLK_RETURN, KMOD_PRIMARY,
format_CStr("importer.accept path:%s", cstr_String(archivePath)) },
dlg = makePanels_Mobile("importer", (iMenuItem[]){
{ NULL }
}, actions, iElemCount(actions));
dlg = makeSheet_Widget("importer");
addDialogTitle_(dlg, "${heading.import.userdata}", NULL);
iWidget *headings, *values;
addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values)));
/* Bookmarks. */
addChild_Widget(headings, iClob(makeHeading_Widget("${import.userdata.bookmarks}")));
iWidget *radio = new_Widget(); {
addRadioButton_(radio, "importer.bookmark.0", "${dlg.userdata.no}", ".");
addRadioButton_(radio, "importer.bookmark.1", "${dlg.userdata.missing}", ".");
addRadioButton_(radio, "importer.bookmark.2", "${dlg.userdata.alldup}", ".");
}
addChildFlags_Widget(values, iClob(radio), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
/* Site-specific. */
addChild_Widget(headings, iClob(makeHeading_Widget("${import.userdata.sitespec}")));
radio = new_Widget(); {
addRadioButton_(radio, "importer.sitespec.0", "${dlg.userdata.no}", ".");
addRadioButton_(radio, "importer.sitespec.1", "${dlg.userdata.missing}", ".");
addRadioButton_(radio, "importer.sitespec.2", "${dlg.userdata.all}", ".");
}
addChildFlags_Widget(values, iClob(radio), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
/* Trusted certs. */
addChild_Widget(headings, iClob(makeHeading_Widget("${import.userdata.trusted}")));
radio = new_Widget(); {
addRadioButton_(radio, "importer.trusted.0", "${dlg.userdata.no}", ".");
addRadioButton_(radio, "importer.trusted.1", "${dlg.userdata.missing}", ".");
addRadioButton_(radio, "importer.trusted.2", "${dlg.userdata.all}", ".");
}
addChildFlags_Widget(values, iClob(radio), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
/* Identities. */
addChild_Widget(headings, iClob(makeHeading_Widget("${import.userdata.idents}")));
radio = new_Widget(); {
addRadioButton_(radio, "importer.idents.0", "${dlg.userdata.no}", ".");
addRadioButton_(radio, "importer.idents.1", "${dlg.userdata.missing}", ".");
addRadioButton_(radio, "importer.idents.2", "${dlg.userdata.all}", ".");
}
addChildFlags_Widget(values, iClob(radio), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
addDialogToggle_(headings, values, "${import.userdata.history}", "importer.history");
addDialogPadding_(headings, values);
addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
addChild_Widget(dlg->root->widget, iClob(dlg));
arrange_Widget(dlg);
arrange_Widget(dlg);
setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | dialogTransitionDir_Widget(dlg));
+}
/----------------------------------------------------------------------------------------------/
void init_PerfTimer(iPerfTimer *d) {
diff --git a/src/ui/util.h b/src/ui/util.h
index 50440137..cda1af61 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -346,6 +346,7 @@ iWidget * makeFeedSettings_Widget (uint32_t bookmarkId);
iWidget * makeSiteSpecificSettings_Widget (const iString *url);
iWidget * makeTranslation_Widget (iWidget *parent);
iWidget * makeGlyphFinder_Widget (void);
+iWidget * makeUserDataImporter_Dialog (const iString *archivePath);
const char * languageId_String (const iString *menuItemLabel);
int languageIndex_CStr (const char *langId);
diff --git a/src/ui/window.c b/src/ui/window.c
index f58d7b46..c981e7cf 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -87,6 +87,7 @@ static const iMenuItem fileMenuItems_[] = {
{ saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" },
{ "---", 0, 0, NULL },
{ "${menu.downloads}", 0, 0, "downloads.open" },
};
static const iMenuItem editMenuItems_[] = {
diff --git a/src/visited.c b/src/visited.c
index f6299ddb..c1c091e2 100644
--- a/src/visited.c
+++ b/src/visited.c
@@ -72,53 +72,71 @@ void deinit_Visited(iVisited *d) {
delete_Mutex(d->mtx);
}
-void save_Visited(const iVisited *d, const char *dirPath) {
+void serialize_Visited(const iVisited *d, iStream *out) {
iString *line = new_String();
const iVisitedUrl *item = i.value;
format_String(line,
"%llu %04x %s\n",
(unsigned long long) integralSeconds_Time(&item->when),
item->flags,
cstr_String(&item->url));
writeData_Stream(out, cstr_String(line), size_String(line));
+}
+void save_Visited(const iVisited *d, const char *dirPath) {
iFile *f = newCStr_File(concatPath_CStr(dirPath, "visited.2.txt"));
if (open_File(f, writeOnly_FileMode | text_FileMode)) {
lock_Mutex(d->mtx);
iConstForEach(Array, i, &d->visited.values) {
const iVisitedUrl *item = i.value;
format_String(line,
"%llu %04x %s\n",
(unsigned long long) integralSeconds_Time(&item->when),
item->flags,
cstr_String(&item->url));
writeData_File(f, cstr_String(line), size_String(line));
}
unlock_Mutex(d->mtx);
serialize_Visited(d, stream_File(f));
}
iRelease(f);
+}
+void deserialize_Visited(iVisited *d, iStream *ins, iBool mergeKeepingLatest) {
if (size_Range(&line) < 8) continue;
char *endp = NULL;
const unsigned long long ts = strtoull(line.start, &endp, 10);
if (ts == 0) break;
const uint32_t flags = (uint32_t) strtoul(skipSpace_CStr(endp), &endp, 16);
const char *urlStart = skipSpace_CStr(endp);
iVisitedUrl item;
item.when.ts = (struct timespec){ .tv_sec = ts };
if (~flags & kept_VisitedUrlFlag &&
secondsSince_Time(&now, &item.when) > maxAge_Visited) {
continue; /* Too old. */
}
item.flags = flags;
initRange_String(&item.url, (iRangecc){ urlStart, line.end });
set_String(&item.url, &item.url);
if (mergeKeepingLatest) {
/* Check if we already have this. */
size_t existingPos;
if (locate_SortedArray(&d->visited, &item, &existingPos)) {
iVisitedUrl *existing = at_SortedArray(&d->visited, existingPos);
max_Time(&existing->when, &item.when);
existing->flags = item.flags;
continue;
}
}
insert_SortedArray(&d->visited, &item);
}
void load_Visited(iVisited *d, const char *dirPath) {
iFile *f = newCStr_File(concatPath_CStr(dirPath, "visited.2.txt"));
if (open_File(f, readOnly_FileMode | text_FileMode)) {
lock_Mutex(d->mtx);
const iRangecc src = range_Block(collect_Block(readAll_File(f)));
iRangecc line = iNullRange;
iTime now;
initCurrent_Time(&now);
while (nextSplit_Rangecc(src, "\n", &line)) {
if (size_Range(&line) < 8) continue;
char *endp = NULL;
const unsigned long long ts = strtoull(line.start, &endp, 10);
if (ts == 0) break;
const uint32_t flags = (uint32_t) strtoul(skipSpace_CStr(endp), &endp, 16);
const char *urlStart = skipSpace_CStr(endp);
iVisitedUrl item;
item.when.ts = (struct timespec){ .tv_sec = ts };
if (~flags & kept_VisitedUrlFlag &&
secondsSince_Time(&now, &item.when) > maxAge_Visited) {
continue; /* Too old. */
}
item.flags = flags;
initRange_String(&item.url, (iRangecc){ urlStart, line.end });
set_String(&item.url, &item.url);
insert_SortedArray(&d->visited, &item);
}
unlock_Mutex(d->mtx);
deserialize_Visited(d, stream_File(f), iFalse /* no merge */);
}
iRelease(f);
}
diff --git a/src/visited.h b/src/visited.h
index 0fde1d1f..1484492e 100644
--- a/src/visited.h
+++ b/src/visited.h
@@ -50,6 +50,8 @@ iDeclareTypeConstruction(Visited)
void clear_Visited (iVisited *);
void load_Visited (iVisited *, const char *dirPath);
void save_Visited (const iVisited *, const char *dirPath);
+void serialize_Visited (const iVisited *, iStream *out);
+void deserialize_Visited (iVisited *, iStream *ins, iBool mergeKeepingLatest);
iTime urlVisitTime_Visited (const iVisited *, const iString *url);
void visitUrl_Visited (iVisited *, const iString url, uint16_t visitFlags); / adds URL to the visited URLs set */
--
2.25.1
text/plain
This content has been proxied by September (3851b).