code wiki / _hdl_build / nx_vizsla_ics.nx

nx_vizsla_ics.nx source

↩ module page · 847 lines · 31783 B

1// nx_vizsla_ics.nx -- NISHI VIZSLA R-CAL.4 (census PA14 interop): RFC 5545 iCalendar EXPORT. 2// Operator 2026-07-07: "exposing a calendar for a group to coordinate" -- the sovereign calendar 3// store becomes a standards-compliant .ics artifact ANY calendar app (Google/Apple/Outlook/ 4// Thunderbird) can import or subscribe to. READ-ONLY over the R-CAL data plane (cal: events + 5// rec: RRULE records written by nx_vizsla_calendar); the data never needs a cloud account. 6// 7// Command (argv[1]): 8// export <prefix> <from YYYY-MM-DD> <to YYYY-MM-DD> <out.ics> 9// cal: events with from<=date<=to -> VEVENT (DTSTART/DTEND local floating time) 10// rec: rules intersecting [from,to] (first occurrence <= to AND last >= from) 11// -> VEVENT anchored at sdate + native RRULE passthrough 12// (FREQ=DAILY|WEEKLY|MONTHLY|YEARLY;INTERVAL;COUNT) 13// RFC 5545 discipline (the gate's oracle): CRLF line ends; VERSION:2.0 + PRODID; every VEVENT 14// carries UID (stable "<id>-<YYYYMMDD>@nishi.vizsla") + DTSTAMP; TEXT escaping of \ ; , in 15// SUMMARY; content lines folded at 75 octets (continuation = CRLF + single space). 16// DETERMINISM: no clocks -- DTSTAMP derives from the record's own date, so the same store 17// exports byte-identical .ics every run (subscribable artifact, CID-friendly). 18// HONEST SCOPE: floating local time (no VTIMEZONE) -- right for a same-timezone friend group; 19// TZ-aware export is a later rung. Loud-fail: bad dates, to<from, unwritable out, unknown cmd. 20// spec: knowledge/research/2026-06-22-vizsla-sclass-roadmap.md license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_seg_store.nx" 23import "nx_canon_cid.nx" 24const K_MAGIC_1970: i64 = 1970 25const K_MAGIC_146097: i64 = 146097 26const K_MAGIC_719468: i64 = 719468 27const K_MAGIC_146096: i64 = 146096 28const K_MAGIC_1460: i64 = 1460 29const K_MAGIC_36524: i64 = 36524 30const K_MAGIC_524288: i64 = 524288 31const K_MAGIC_4096: i64 = 4096 32const K_MAGIC_2048: i64 = 2048 33const K_MAGIC_1024: i64 = 1024 34 35func vi_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 36func vi_p(s: *u8) -> i64 { sys_write(1, s, vi_slen(s)); return 0 } 37 38func vi_eq(a: *u8, b: *u8) -> i64 { 39 var i: i64 = 0 40 var go: i64 = 1 41 while go == 1 { 42 if a[i] != b[i] { return 0 } 43 if a[i] == (0 as u8) { return 1 } 44 i = i + 1 45 } 46 return 0 47} 48 49func vi_atoi(s: *u8) -> i64 { 50 var v: i64 = 0 51 var i: i64 = 0 52 while s[i] != (0 as u8) { 53 let d: i64 = (s[i] as i64) - 48 54 if d >= 0 { if d <= 9 { v = v * 10 + d } } 55 i = i + 1 56 } 57 return v 58} 59 60func vi_cat(dst: *u8, off: i64, s: *u8) -> i64 { 61 var i: i64 = 0 62 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } 63 return off + i 64} 65 66func vi_catc(dst: *u8, off: i64, c: i64) -> i64 { 67 dst[off] = c as u8 68 return off + 1 69} 70 71func vi_catn(dst: *u8, off: i64, v: i64) -> i64 { 72 var o: i64 = off 73 var m: i64 = v 74 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m } 75 let t: *u8 = sys_mmap(28) 76 var k: i64 = 0 77 if m == 0 { t[0] = 48 as u8; k = 1 } 78 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 79 var i: i64 = 0 80 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 } 81 return o + k 82} 83 84func vi_kmemeq(b: *u8, off: i64, n: i64, s: *u8) -> i64 { 85 if vi_slen(s) != n { return 0 } 86 var i: i64 = 0 87 while i < n { 88 if b[off + i] != s[i] { return 0 } 89 i = i + 1 90 } 91 return 1 92} 93 94func vi_field(rec: *u8, rl: i64, want: *u8, out: *u8, cap: i64) -> i64 { 95 if rl < 8 { return 0 } 96 let nf: i64 = ss_r32(rec, 4) 97 var off: i64 = 8 98 var fi: i64 = 0 99 while fi < nf { 100 if off + 8 > rl { return 0 } 101 let kl: i64 = ss_r32(rec, off) 102 let koff: i64 = off + 4 103 let vl: i64 = ss_r32(rec, koff + kl) 104 let voff: i64 = koff + kl + 4 105 if vi_kmemeq(rec, koff, kl, want) == 1 { 106 var t: i64 = 0 107 while t < vl { 108 if t < cap - 1 { out[t] = rec[voff + t] } 109 t = t + 1 110 } 111 if t > cap - 1 { t = cap - 1 } 112 out[t] = 0 as u8 113 return 1 114 } 115 off = voff + vl 116 fi = fi + 1 117 } 118 return 0 119} 120 121func vi_iskind(b: *u8, koff: i64, kl: i64, pfx: *u8) -> i64 { 122 if kl <= 4 { return 0 } 123 return vi_kmemeq(b, koff, 4, pfx) 124} 125 126// civil "YYYY-MM-DD" -> days since epoch (Hinnant); -1 malformed 127func vi_datedays(s: *u8) -> i64 { 128 let nums: *i64 = sys_mmap(8 * 4) as *i64 129 var nn: i64 = 0 130 var cur: i64 = 0 131 var indig: i64 = 0 132 var i: i64 = 0 133 var go: i64 = 1 134 while go == 1 { 135 let c: i64 = s[i] as i64 136 var isd: i64 = 0 137 if c >= 48 { if c <= 57 { isd = 1 } } 138 if isd == 1 { cur = cur * 10 + (c - 48); indig = 1 } 139 if isd == 0 { 140 if indig == 1 { 141 if nn < 4 { nums[nn] = cur; nn = nn + 1 } 142 cur = 0 143 indig = 0 144 } 145 } 146 if c == 0 { go = 0 } 147 i = i + 1 148 } 149 if nn != 3 { return 0 - 1 } 150 var y: i64 = nums[0] 151 let m: i64 = nums[1] 152 let d: i64 = nums[2] 153 if y < K_MAGIC_1970 { return 0 - 1 } 154 if m < 1 { return 0 - 1 } 155 if m > 12 { return 0 - 1 } 156 if d < 1 { return 0 - 1 } 157 if d > 31 { return 0 - 1 } 158 if m <= 2 { y = y - 1 } 159 let era: i64 = y / 400 160 let yoe: i64 = y - era * 400 161 var mp: i64 = m - 3 162 if m <= 2 { mp = m + 9 } 163 let doy: i64 = (153 * mp + 2) / 5 + d - 1 164 let doe: i64 = yoe * 365 + yoe / 4 - yoe / 100 + doy 165 return era * K_MAGIC_146097 + doe - K_MAGIC_719468 166} 167 168func vi_ymd2days(y0: i64, m: i64, d: i64) -> i64 { 169 var y: i64 = y0 170 if m <= 2 { y = y0 - 1 } 171 let era: i64 = y / 400 172 let yoe: i64 = y - era * 400 173 var mp: i64 = m - 3 174 if m <= 2 { mp = m + 9 } 175 let doy: i64 = (153 * mp + 2) / 5 + d - 1 176 let doe: i64 = yoe * 365 + yoe / 4 - yoe / 100 + doy 177 return era * K_MAGIC_146097 + doe - K_MAGIC_719468 178} 179 180func vi_civil(z0: i64, out: *i64) -> i64 { 181 var z: i64 = z0 + K_MAGIC_719468 182 var era: i64 = z / K_MAGIC_146097 183 if z < 0 { era = (z - K_MAGIC_146096) / K_MAGIC_146097 } 184 let doe: i64 = z - era * K_MAGIC_146097 185 let yoe: i64 = (doe - doe / K_MAGIC_1460 + doe / K_MAGIC_36524 - doe / K_MAGIC_146096) / 365 186 let y0: i64 = yoe + era * 400 187 let doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100) 188 let mp: i64 = (5 * doy + 2) / 153 189 let d: i64 = doy - (153 * mp + 2) / 5 + 1 190 var m: i64 = mp + 3 191 if mp >= 10 { m = mp - 9 } 192 var y: i64 = y0 193 if m <= 2 { y = y0 + 1 } 194 out[0] = y; out[1] = m; out[2] = d 195 return 0 196} 197 198func vi_dim(y: i64, m: i64) -> i64 { 199 if m == 2 { 200 var leap: i64 = 0 201 if y % 4 == 0 { leap = 1 } 202 if y % 100 == 0 { leap = 0 } 203 if y % 400 == 0 { leap = 1 } 204 if leap == 1 { return 29 } 205 return 28 206 } 207 if m == 4 { return 30 } 208 if m == 6 { return 30 } 209 if m == 9 { return 30 } 210 if m == 11 { return 30 } 211 return 31 212} 213 214// k-th occurrence day (mirrors nx_vizsla_calendar vc_occ incl. YEARLY month-step + day clamp) 215func vi_occ(freq: *u8, sd0: i64, sy: i64, sm: i64, sd: i64, interval: i64, k: i64) -> i64 { 216 if vi_eq(freq, "DAILY" as *u8) == 1 { return sd0 + k * interval } 217 if vi_eq(freq, "WEEKLY" as *u8) == 1 { return sd0 + k * interval * 7 } 218 var mstep: i64 = interval 219 if vi_eq(freq, "YEARLY" as *u8) == 1 { mstep = interval * 12 } 220 let tot: i64 = (sm - 1) + k * mstep 221 let y2: i64 = sy + tot / 12 222 let m2: i64 = tot - (tot / 12) * 12 + 1 223 var d2: i64 = sd 224 let dim: i64 = vi_dim(y2, m2) 225 if d2 > dim { d2 = dim } 226 return vi_ymd2days(y2, m2, d2) 227} 228 229// "YYYY-MM-DD" -> append "YYYYMMDD" 230func vi_dt8(dst: *u8, off: i64, s: *u8) -> i64 { 231 var o: i64 = off 232 var i: i64 = 0 233 while s[i] != (0 as u8) { 234 if s[i] != (45 as u8) { dst[o] = s[i]; o = o + 1 } 235 i = i + 1 236 } 237 return o 238} 239 240// minute-of-day -> append "HHMMSS" (zero-padded, SS=00) 241func vi_hms(dst: *u8, off: i64, m: i64) -> i64 { 242 var o: i64 = off 243 let h: i64 = m / 60 244 let mm: i64 = m - h * 60 245 if h < 10 { dst[o] = 48 as u8; o = o + 1 } 246 o = vi_catn(dst, o, h) 247 if mm < 10 { dst[o] = 48 as u8; o = o + 1 } 248 o = vi_catn(dst, o, mm) 249 o = vi_cat(dst, o, "00" as *u8) 250 return o 251} 252 253// RFC5545 TEXT escaping: backslash, semicolon, comma (newlines cannot occur in store tokens) 254func vi_esc(dst: *u8, off: i64, s: *u8) -> i64 { 255 var o: i64 = off 256 var i: i64 = 0 257 while s[i] != (0 as u8) { 258 let c: i64 = s[i] as i64 259 if c == 92 { o = vi_catc(dst, o, 92); o = vi_catc(dst, o, 92) } 260 if c == 59 { o = vi_catc(dst, o, 92); o = vi_catc(dst, o, 59) } 261 if c == 44 { o = vi_catc(dst, o, 92); o = vi_catc(dst, o, 44) } 262 if c != 92 { if c != 59 { if c != 44 { 263 o = vi_catc(dst, o, c) 264 } } } 265 i = i + 1 266 } 267 return o 268} 269 270// append one content line with RFC5545 75-octet folding + CRLF termination. 271// first physical line carries <=75 octets; each continuation = single leading space + <=74 more. 272// st[0] accumulates folded-line count (evidence for the verdict). 273func vi_fold(rep: *u8, off: i64, lb: *u8, ll: i64, st: *i64) -> i64 { 274 var o: i64 = off 275 var i: i64 = 0 276 var seg: i64 = 75 277 var first: i64 = 1 278 while i < ll { 279 var take: i64 = ll - i 280 if take > seg { take = seg } 281 if first == 0 { 282 o = vi_catc(rep, o, 32) 283 } 284 var t: i64 = 0 285 while t < take { rep[o + t] = lb[i + t]; t = t + 1 } 286 o = o + take 287 i = i + take 288 o = vi_catc(rep, o, 13) 289 o = vi_catc(rep, o, 10) 290 if i < ll { st[0] = st[0] + 1 } 291 first = 0 292 seg = 74 293 } 294 if ll == 0 { 295 o = vi_catc(rep, o, 13) 296 o = vi_catc(rep, o, 10) 297 } 298 return o 299} 300 301// ---------------- EXPORT ---------------- 302func vi_cmd_export(argc: i64, argv: *i64) -> i64 { 303 if argc < 6 { vi_p("VIZSLA-ICS export needs <prefix> <from> <to> <out.ics> -- fail loud\n" as *u8); return 1 } 304 let prefix: *u8 = argv[2] as *u8 305 let qfrom: i64 = vi_datedays(argv[3] as *u8) 306 let qto: i64 = vi_datedays(argv[4] as *u8) 307 let outp: *u8 = argv[5] as *u8 308 if qfrom < 0 { vi_p("VIZSLA-ICS bad from date -- fail loud\n" as *u8); return 1 } 309 if qto < 0 { vi_p("VIZSLA-ICS bad to date -- fail loud\n" as *u8); return 1 } 310 if qto < qfrom { vi_p("VIZSLA-ICS to < from -- fail loud\n" as *u8); return 1 } 311 312 let rep: *u8 = sys_mmap(K_MAGIC_524288) 313 let lb: *u8 = sys_mmap(K_MAGIC_4096) 314 let st: *i64 = sys_mmap(16) as *i64 315 st[0] = 0 316 var o: i64 = 0 317 var ll: i64 = 0 318 ll = vi_cat(lb, 0, "BEGIN:VCALENDAR" as *u8) 319 o = vi_fold(rep, o, lb, ll, st) 320 ll = vi_cat(lb, 0, "VERSION:2.0" as *u8) 321 o = vi_fold(rep, o, lb, ll, st) 322 ll = vi_cat(lb, 0, "PRODID:-//Nishi//Vizsla Calendar//EN" as *u8) 323 o = vi_fold(rep, o, lb, ll, st) 324 ll = vi_cat(lb, 0, "CALSCALE:GREGORIAN" as *u8) 325 o = vi_fold(rep, o, lb, ll, st) 326 327 var nev: i64 = 0 328 var nru: i64 = 0 329 330 let segs: *i64 = sys_mmap(8 * 260) as *i64 331 let ns: i64 = ss_manifest(prefix, segs) 332 let fid: *u8 = sys_mmap(128) 333 let fdt: *u8 = sys_mmap(128) 334 let fst: *u8 = sys_mmap(128) 335 let fdu: *u8 = sys_mmap(128) 336 let ffr: *u8 = sys_mmap(64) 337 let fiv: *u8 = sys_mmap(64) 338 let fct: *u8 = sys_mmap(64) 339 let ftl: *u8 = sys_mmap(256) 340 let ymd: *i64 = sys_mmap(8 * 4) as *i64 341 var s: i64 = 0 342 while s < ns { 343 let path: *u8 = sys_mmap(512) 344 var po: i64 = 0 345 po = vi_cat(path, po, prefix) 346 po = vi_cat(path, po, segs[s] as *u8) 347 po = vi_cat(path, po, ".docs" as *u8) 348 path[po] = 0 as u8 349 let szp: *i64 = sys_mmap(16) as *i64 350 let b: *u8 = ss_readall(path, szp) 351 let sz: i64 = szp[0] 352 var j: i64 = 0 353 while j + 9 <= sz { 354 let kind: i64 = b[j] 355 let kl: i64 = ss_r32(b, j + 1) 356 let koff: i64 = j + 5 357 let vl: i64 = ss_r32(b, koff + kl) 358 let voff: i64 = koff + kl + 4 359 let rec: *u8 = (b as i64 + voff) as *u8 360 var iscal: i64 = 0 361 var isrec: i64 = 0 362 if kind == 1 { iscal = vi_iskind(b, koff, kl, "cal:" as *u8) } 363 if kind == 1 { isrec = vi_iskind(b, koff, kl, "rec:" as *u8) } 364 if iscal == 1 { 365 var dd: i64 = 0 - 1 366 if vi_field(rec, vl, "date" as *u8, fdt, 128) == 1 { dd = vi_datedays(fdt) } 367 var inwin: i64 = 0 368 if dd >= qfrom { if dd <= qto { inwin = 1 } } 369 if inwin == 1 { 370 vi_field(rec, vl, "id" as *u8, fid, 128) 371 vi_field(rec, vl, "start" as *u8, fst, 128) 372 vi_field(rec, vl, "dur" as *u8, fdu, 128) 373 vi_field(rec, vl, "title" as *u8, ftl, 256) 374 let smin: i64 = vi_atoi(fst) 375 let emin: i64 = smin + vi_atoi(fdu) 376 ll = vi_cat(lb, 0, "BEGIN:VEVENT" as *u8) 377 o = vi_fold(rep, o, lb, ll, st) 378 ll = vi_cat(lb, 0, "UID:" as *u8) 379 ll = vi_cat(lb, ll, fid) 380 ll = vi_catc(lb, ll, 45) 381 ll = vi_dt8(lb, ll, fdt) 382 ll = vi_cat(lb, ll, "@nishi.vizsla" as *u8) 383 o = vi_fold(rep, o, lb, ll, st) 384 ll = vi_cat(lb, 0, "DTSTAMP:" as *u8) 385 ll = vi_dt8(lb, ll, fdt) 386 ll = vi_cat(lb, ll, "T000000Z" as *u8) 387 o = vi_fold(rep, o, lb, ll, st) 388 ll = vi_cat(lb, 0, "DTSTART:" as *u8) 389 ll = vi_dt8(lb, ll, fdt) 390 ll = vi_catc(lb, ll, 84) 391 ll = vi_hms(lb, ll, smin) 392 o = vi_fold(rep, o, lb, ll, st) 393 ll = vi_cat(lb, 0, "DTEND:" as *u8) 394 ll = vi_dt8(lb, ll, fdt) 395 ll = vi_catc(lb, ll, 84) 396 ll = vi_hms(lb, ll, emin) 397 o = vi_fold(rep, o, lb, ll, st) 398 ll = vi_cat(lb, 0, "SUMMARY:" as *u8) 399 ll = vi_esc(lb, ll, ftl) 400 o = vi_fold(rep, o, lb, ll, st) 401 ll = vi_cat(lb, 0, "END:VEVENT" as *u8) 402 o = vi_fold(rep, o, lb, ll, st) 403 nev = nev + 1 404 } 405 } 406 if isrec == 1 { 407 vi_field(rec, vl, "id" as *u8, fid, 128) 408 vi_field(rec, vl, "sdate" as *u8, fdt, 128) 409 vi_field(rec, vl, "start" as *u8, fst, 128) 410 vi_field(rec, vl, "dur" as *u8, fdu, 128) 411 vi_field(rec, vl, "freq" as *u8, ffr, 64) 412 vi_field(rec, vl, "interval" as *u8, fiv, 64) 413 vi_field(rec, vl, "count" as *u8, fct, 64) 414 vi_field(rec, vl, "title" as *u8, ftl, 256) 415 let sd0: i64 = vi_datedays(fdt) 416 vi_civil(sd0, ymd) 417 let iv: i64 = vi_atoi(fiv) 418 let ct: i64 = vi_atoi(fct) 419 let lastocc: i64 = vi_occ(ffr, sd0, ymd[0], ymd[1], ymd[2], iv, ct - 1) 420 var inwin: i64 = 0 421 if sd0 <= qto { if lastocc >= qfrom { inwin = 1 } } 422 if inwin == 1 { 423 let smin: i64 = vi_atoi(fst) 424 let emin: i64 = smin + vi_atoi(fdu) 425 ll = vi_cat(lb, 0, "BEGIN:VEVENT" as *u8) 426 o = vi_fold(rep, o, lb, ll, st) 427 ll = vi_cat(lb, 0, "UID:" as *u8) 428 ll = vi_cat(lb, ll, fid) 429 ll = vi_catc(lb, ll, 45) 430 ll = vi_dt8(lb, ll, fdt) 431 ll = vi_cat(lb, ll, "@nishi.vizsla" as *u8) 432 o = vi_fold(rep, o, lb, ll, st) 433 ll = vi_cat(lb, 0, "DTSTAMP:" as *u8) 434 ll = vi_dt8(lb, ll, fdt) 435 ll = vi_cat(lb, ll, "T000000Z" as *u8) 436 o = vi_fold(rep, o, lb, ll, st) 437 ll = vi_cat(lb, 0, "DTSTART:" as *u8) 438 ll = vi_dt8(lb, ll, fdt) 439 ll = vi_catc(lb, ll, 84) 440 ll = vi_hms(lb, ll, smin) 441 o = vi_fold(rep, o, lb, ll, st) 442 ll = vi_cat(lb, 0, "DTEND:" as *u8) 443 ll = vi_dt8(lb, ll, fdt) 444 ll = vi_catc(lb, ll, 84) 445 ll = vi_hms(lb, ll, emin) 446 o = vi_fold(rep, o, lb, ll, st) 447 ll = vi_cat(lb, 0, "RRULE:FREQ=" as *u8) 448 ll = vi_cat(lb, ll, ffr) 449 ll = vi_cat(lb, ll, ";INTERVAL=" as *u8) 450 ll = vi_catn(lb, ll, iv) 451 ll = vi_cat(lb, ll, ";COUNT=" as *u8) 452 ll = vi_catn(lb, ll, ct) 453 o = vi_fold(rep, o, lb, ll, st) 454 ll = vi_cat(lb, 0, "SUMMARY:" as *u8) 455 ll = vi_esc(lb, ll, ftl) 456 o = vi_fold(rep, o, lb, ll, st) 457 ll = vi_cat(lb, 0, "END:VEVENT" as *u8) 458 o = vi_fold(rep, o, lb, ll, st) 459 nru = nru + 1 460 } 461 } 462 j = voff + vl 463 } 464 s = s + 1 465 } 466 467 ll = vi_cat(lb, 0, "END:VCALENDAR" as *u8) 468 o = vi_fold(rep, o, lb, ll, st) 469 470 let fd: i64 = sys_openat_wr(outp, 0x1a4) 471 if fd < 0 { vi_p("VIZSLA-ICS cannot write out.ics -- fail loud\n" as *u8); return 1 } 472 sys_write(fd, rep, o) 473 sys_close(fd) 474 475 let vb: *u8 = sys_mmap(256) 476 var vo: i64 = 0 477 vo = vi_cat(vb, vo, "VIZSLA-ICS-VERDICT events=" as *u8) 478 vo = vi_catn(vb, vo, nev) 479 vo = vi_cat(vb, vo, " rules=" as *u8) 480 vo = vi_catn(vb, vo, nru) 481 vo = vi_cat(vb, vo, " bytes=" as *u8) 482 vo = vi_catn(vb, vo, o) 483 vo = vi_cat(vb, vo, " folded=" as *u8) 484 vo = vi_catn(vb, vo, st[0]) 485 vo = vi_cat(vb, vo, "\n" as *u8) 486 sys_write(1, vb, vo) 487 return 0 488} 489 490// ================= IMPORT (RFC5545 .ics -> cal:/rec: records the calendar organ reads) ================= 491 492func vi_find2(list: *i64, n: i64, s: *u8) -> i64 { 493 var i: i64 = 0 494 while i < n { 495 if vi_eq(list[i] as *u8, s) == 1 { return i } 496 i = i + 1 497 } 498 return 0 - 1 499} 500 501func vi_dup(s: *u8) -> *u8 { 502 let n: i64 = vi_slen(s) 503 let d: *u8 = sys_mmap(n + 2) 504 var i: i64 = 0 505 while i <= n { d[i] = s[i]; i = i + 1 } 506 return d 507} 508 509// canon-store one record with CID dedup. st: 0=writer 1=seen 2=nseen 3=added 4=dupfile 5=dupstore 6=prefix 510func vi_put(keys: *i64, vals: *i64, nf: i64, kp: *u8, st: *i64) -> i64 { 511 let enc: *u8 = sys_mmap(K_MAGIC_2048) 512 let el: i64 = canon_encode(keys, vals, nf, enc) 513 let cid: *u8 = sys_mmap(96) 514 cid_of(enc, el, cid) 515 let kbuf: *u8 = sys_mmap(128) 516 var ko: i64 = 0 517 ko = vi_cat(kbuf, ko, kp) 518 ko = vi_cat(kbuf, ko, cid) 519 kbuf[ko] = 0 as u8 520 let seen: *i64 = st[1] as *i64 521 var fresh: i64 = 1 522 if vi_find2(seen, st[2], kbuf) >= 0 { fresh = 0; st[4] = st[4] + 1 } 523 if fresh == 1 { 524 if st[2] < K_MAGIC_2048 { let dk: *u8 = vi_dup(kbuf); seen[st[2]] = dk as i64; st[2] = st[2] + 1 } 525 } 526 if fresh == 1 { 527 let pp: *i64 = sys_mmap(16) as *i64 528 let ll: *i64 = sys_mmap(16) as *i64 529 if ss_get_idx(st[6] as *u8, kbuf, pp, ll) == 1 { fresh = 0; st[5] = st[5] + 1 } 530 } 531 if fresh == 1 { 532 if ss_add(st[0] as *i64, 1, kbuf, enc, el) != 0 { return 0 - 1 } 533 st[3] = st[3] + 1 534 } 535 return 0 536} 537 538// unfold RFC5545 continuation lines (CRLF + leading space/tab -> joined) + strip CR. Returns new length. 539func vi_unfold(src: *u8, slen: i64, dst: *u8) -> i64 { 540 var o: i64 = 0 541 var i: i64 = 0 542 while i < slen { 543 let c: i64 = src[i] as i64 544 if c == 13 { 545 // skip CR 546 i = i + 1 547 } else { 548 var join: i64 = 0 549 if c == 32 { if o > 0 { if dst[o - 1] == (10 as u8) { join = 1 } } } 550 if c == 9 { if o > 0 { if dst[o - 1] == (10 as u8) { join = 1 } } } 551 if join == 1 { o = o - 1 } else { dst[o] = c as u8; o = o + 1 } 552 i = i + 1 553 } 554 } 555 return o 556} 557 558// if line[0,llen) starts with property `name` (followed by ':' or ';'), copy the value after the 559// first ':' into out (unescaped for TEXT). returns 1 if matched. 560func vi_prop(line: *u8, llen: i64, name: *u8, out: *u8, cap: i64) -> i64 { 561 let nl: i64 = vi_slen(name) 562 if nl > llen { return 0 } 563 var j: i64 = 0 564 while j < nl { if line[j] != name[j] { return 0 } j = j + 1 } 565 if nl >= llen { return 0 } 566 let after: i64 = line[nl] as i64 567 if after != 58 { if after != 59 { return 0 } } // ':' or ';' 568 var col: i64 = nl 569 while col < llen { if line[col] == (58 as u8) { break } col = col + 1 } 570 if col >= llen { return 0 } 571 var p: i64 = col + 1 572 var t: i64 = 0 573 while p < llen { 574 var c: i64 = line[p] as i64 575 if c == 92 { // backslash unescape 576 if p + 1 < llen { 577 let nx: i64 = line[p + 1] as i64 578 if nx == 110 { c = 10; p = p + 1 } // \n 579 if nx == 92 { c = 92; p = p + 1 } 580 if nx == 59 { c = 59; p = p + 1 } 581 if nx == 44 { c = 44; p = p + 1 } 582 } 583 } 584 if t < cap - 1 { out[t] = c as u8; t = t + 1 } 585 p = p + 1 586 } 587 out[t] = 0 as u8 588 return 1 589} 590 591// "YYYYMMDDThhmmss" (or "YYYYMMDD") -> "YYYY-MM-DD" in datebuf, start-minute return. -1 if malformed. 592func vi_dtparse(v: *u8, datebuf: *u8) -> i64 { 593 let n: i64 = vi_slen(v) 594 if n < 8 { return 0 - 1 } 595 var i: i64 = 0 596 while i < 8 { let c: i64 = v[i] as i64; if c < 48 { return 0 - 1 } if c > 57 { return 0 - 1 } i = i + 1 } 597 var o: i64 = 0 598 datebuf[o] = v[0]; o = o + 1 599 datebuf[o] = v[1]; o = o + 1 600 datebuf[o] = v[2]; o = o + 1 601 datebuf[o] = v[3]; o = o + 1 602 datebuf[o] = 45 as u8; o = o + 1 603 datebuf[o] = v[4]; o = o + 1 604 datebuf[o] = v[5]; o = o + 1 605 datebuf[o] = 45 as u8; o = o + 1 606 datebuf[o] = v[6]; o = o + 1 607 datebuf[o] = v[7]; o = o + 1 608 datebuf[o] = 0 as u8 609 if n >= 15 { if v[8] == (84 as u8) { // 'T' 610 let hh: i64 = ((v[9] as i64) - 48) * 10 + ((v[10] as i64) - 48) 611 let mm: i64 = ((v[11] as i64) - 48) * 10 + ((v[12] as i64) - 48) 612 return hh * 60 + mm 613 } } 614 return 0 // all-day -> midnight 615} 616 617// derive a sanitized id from a UID: take up to '@', strip a trailing "-<8 digits>", sanitize. 618func vi_idfromuid(uid: *u8, out: *u8, cap: i64) -> i64 { 619 var n: i64 = 0 620 while uid[n] != (0 as u8) { if uid[n] == (64 as u8) { break } n = n + 1 } // stop at '@' 621 // strip trailing "-DDDDDDDD" 622 if n >= 9 { 623 if uid[n - 9] == (45 as u8) { 624 var alld: i64 = 1 625 var k: i64 = n - 8 626 while k < n { let c: i64 = uid[k] as i64; if c < 48 { alld = 0 } if c > 57 { alld = 0 } k = k + 1 } 627 if alld == 1 { n = n - 9 } 628 } 629 } 630 var t: i64 = 0 631 var i: i64 = 0 632 while i < n { 633 let c: i64 = uid[i] as i64 634 var ok: i64 = 0 635 if c >= 48 { if c <= 57 { ok = 1 } } 636 if c >= 65 { if c <= 90 { ok = 1 } } 637 if c >= 97 { if c <= 122 { ok = 1 } } 638 if c == 95 { ok = 1 } 639 if c == 45 { ok = 1 } 640 if t < cap - 1 { 641 if ok == 1 { out[t] = c as u8 } else { out[t] = 45 as u8 } 642 t = t + 1 643 } 644 i = i + 1 645 } 646 if t == 0 { out[0] = 105 as u8; out[1] = 109 as u8; out[2] = 112 as u8; t = 3 } // "imp" 647 out[t] = 0 as u8 648 return 0 649} 650 651// parse RRULE value "FREQ=W;INTERVAL=1;COUNT=8" -> freq/interval/count buffers. 1 if FREQ present. 652func vi_rrparse(v: *u8, freq: *u8, ival: *u8, cnt: *u8) -> i64 { 653 let n: i64 = vi_slen(v) 654 freq[0] = 0 as u8 655 ival[0] = 49 as u8; ival[1] = 0 as u8 // default INTERVAL=1 656 cnt[0] = 49 as u8; cnt[1] = 0 as u8 // default COUNT=1 (bounded; UNTIL/infinite = later rung) 657 var i: i64 = 0 658 while i < n { 659 var e: i64 = i 660 while e < n { if v[e] == (59 as u8) { break } e = e + 1 } // ';' 661 // token [i,e) = K=V 662 var eq: i64 = i 663 while eq < e { if v[eq] == (61 as u8) { break } eq = eq + 1 } // '=' 664 if eq < e { 665 var dst: *u8 = 0 as *u8 666 if vi_kmemeq(v, i, eq - i, "FREQ" as *u8) == 1 { dst = freq } 667 if vi_kmemeq(v, i, eq - i, "INTERVAL" as *u8) == 1 { dst = ival } 668 if vi_kmemeq(v, i, eq - i, "COUNT" as *u8) == 1 { dst = cnt } 669 if (dst as i64) != 0 { 670 var t: i64 = 0 671 var p: i64 = eq + 1 672 while p < e { dst[t] = v[p]; t = t + 1; p = p + 1 } 673 dst[t] = 0 as u8 674 } 675 } 676 i = e + 1 677 } 678 if freq[0] == (0 as u8) { return 0 } 679 return 1 680} 681 682func vi_cmd_import(argc: i64, argv: *i64) -> i64 { 683 if argc < 4 { vi_p("VIZSLA-ICS import needs <in.ics> <prefix> [segid] -- fail loud\n" as *u8); return 1 } 684 let infile: *u8 = argv[2] as *u8 685 let prefix: *u8 = argv[3] as *u8 686 var segid: i64 = 0 687 if argc > 4 { segid = vi_atoi(argv[4] as *u8) } 688 if segid == 0 { segid = sys_now_us() } 689 690 let szp: *i64 = sys_mmap(16) as *i64 691 let raw: *u8 = ss_readall(infile, szp) 692 let rsz: i64 = szp[0] 693 if rsz <= 0 { vi_p("VIZSLA-ICS import file MISSING/EMPTY -- fail loud\n" as *u8); return 1 } 694 let buf: *u8 = sys_mmap(rsz + 64) 695 let bn: i64 = vi_unfold(raw, rsz, buf) 696 697 let ckeys: *i64 = sys_mmap(8 * 8) as *i64 698 let cvals: *i64 = sys_mmap(8 * 8) as *i64 699 ckeys[0] = "kind" as *u8 as i64 700 ckeys[1] = "id" as *u8 as i64 701 ckeys[2] = "date" as *u8 as i64 702 ckeys[3] = "start" as *u8 as i64 703 ckeys[4] = "dur" as *u8 as i64 704 ckeys[5] = "title" as *u8 as i64 705 cvals[0] = "cal" as *u8 as i64 706 let rkeys: *i64 = sys_mmap(8 * 12) as *i64 707 let rvals: *i64 = sys_mmap(8 * 12) as *i64 708 rkeys[0] = "kind" as *u8 as i64 709 rkeys[1] = "id" as *u8 as i64 710 rkeys[2] = "sdate" as *u8 as i64 711 rkeys[3] = "start" as *u8 as i64 712 rkeys[4] = "dur" as *u8 as i64 713 rkeys[5] = "freq" as *u8 as i64 714 rkeys[6] = "interval" as *u8 as i64 715 rkeys[7] = "count" as *u8 as i64 716 rkeys[8] = "title" as *u8 as i64 717 rvals[0] = "rec" as *u8 as i64 718 719 let st: *i64 = sys_mmap(8 * 8) as *i64 720 st[0] = ss_begin() as i64 721 st[1] = sys_mmap(8 * K_MAGIC_2048) 722 st[2] = 0 723 st[3] = 0 724 st[4] = 0 725 st[5] = 0 726 st[6] = prefix as i64 727 728 let uid: *u8 = sys_mmap(512) 729 let vds: *u8 = sys_mmap(256) 730 let vde: *u8 = sys_mmap(256) 731 let summ: *u8 = sys_mmap(K_MAGIC_1024) 732 let rrule: *u8 = sys_mmap(512) 733 let sdate: *u8 = sys_mmap(64) 734 let edate: *u8 = sys_mmap(64) 735 let idb: *u8 = sys_mmap(256) 736 let startb: *u8 = sys_mmap(32) 737 let durb: *u8 = sys_mmap(32) 738 let freq: *u8 = sys_mmap(64) 739 let ival: *u8 = sys_mmap(64) 740 let cnt: *u8 = sys_mmap(64) 741 var inev: i64 = 0 742 var nev: i64 = 0 743 var nru: i64 = 0 744 745 var i: i64 = 0 746 while i < bn { 747 var e: i64 = i 748 var go: i64 = 1 749 while go == 1 { if e >= bn { go = 0 } else { if buf[e] == (10 as u8) { go = 0 } else { e = e + 1 } } } 750 let line: *u8 = (buf as i64 + i) as *u8 751 let llen: i64 = e - i 752 if vi_kmemeq(line, 0, llen, "BEGIN:VEVENT" as *u8) == 1 { 753 inev = 1 754 uid[0] = 0 as u8; vds[0] = 0 as u8; vde[0] = 0 as u8; summ[0] = 0 as u8; rrule[0] = 0 as u8 755 } else { 756 if vi_kmemeq(line, 0, llen, "END:VEVENT" as *u8) == 1 { 757 if inev == 1 { 758 if vds[0] != (0 as u8) { 759 let smin: i64 = vi_dtparse(vds, sdate) 760 if smin >= 0 { if vi_datedays(sdate) >= 0 { 761 vi_idfromuid(uid, idb, 256) 762 var so: i64 = 0 763 so = vi_catn(startb, 0, smin); startb[so] = 0 as u8 764 var dur: i64 = 60 765 if vde[0] != (0 as u8) { 766 let emin: i64 = vi_dtparse(vde, edate) 767 if emin > smin { dur = emin - smin } 768 } 769 var duo: i64 = 0 770 duo = vi_catn(durb, 0, dur); durb[duo] = 0 as u8 771 var ttl: *u8 = summ 772 if summ[0] == (0 as u8) { ttl = "untitled" as *u8 } 773 if rrule[0] != (0 as u8) { 774 if vi_rrparse(rrule, freq, ival, cnt) == 1 { 775 rvals[1] = idb as i64 776 rvals[2] = sdate as i64 777 rvals[3] = startb as i64 778 rvals[4] = durb as i64 779 rvals[5] = freq as i64 780 rvals[6] = ival as i64 781 rvals[7] = cnt as i64 782 rvals[8] = ttl as i64 783 if vi_put(rkeys, rvals, 9, "rec:" as *u8, st) != 0 { vi_p("VIZSLA-ICS import writer full -- fail loud\n" as *u8); return 1 } 784 nru = nru + 1 785 } 786 } else { 787 cvals[1] = idb as i64 788 cvals[2] = sdate as i64 789 cvals[3] = startb as i64 790 cvals[4] = durb as i64 791 cvals[5] = ttl as i64 792 if vi_put(ckeys, cvals, 6, "cal:" as *u8, st) != 0 { vi_p("VIZSLA-ICS import writer full -- fail loud\n" as *u8); return 1 } 793 nev = nev + 1 794 } 795 } } 796 } 797 } 798 inev = 0 799 } else { 800 if inev == 1 { 801 vi_prop(line, llen, "UID" as *u8, uid, 512) 802 vi_prop(line, llen, "DTSTART" as *u8, vds, 256) 803 vi_prop(line, llen, "DTEND" as *u8, vde, 256) 804 vi_prop(line, llen, "SUMMARY" as *u8, summ, K_MAGIC_1024) 805 vi_prop(line, llen, "RRULE" as *u8, rrule, 512) 806 } 807 } 808 } 809 i = e + 1 810 } 811 812 var committed: i64 = 0 813 if st[3] > 0 { 814 if ss_commit(prefix, st[0] as *i64, segid) != 0 { vi_p("VIZSLA-ICS import commit FAILED -- fail loud\n" as *u8); return 1 } 815 committed = 1 816 } 817 818 let rep: *u8 = sys_mmap(K_MAGIC_1024) 819 var o: i64 = 0 820 o = vi_cat(rep, o, "VIZSLA-ICS-IMPORT events=" as *u8) 821 o = vi_catn(rep, o, nev) 822 o = vi_cat(rep, o, " rules=" as *u8) 823 o = vi_catn(rep, o, nru) 824 o = vi_cat(rep, o, " new=" as *u8) 825 o = vi_catn(rep, o, st[3]) 826 o = vi_cat(rep, o, " dup_instore=" as *u8) 827 o = vi_catn(rep, o, st[5]) 828 o = vi_cat(rep, o, " segment=" as *u8) 829 if committed == 1 { 830 o = vi_cat(rep, o, "seg-" as *u8) 831 o = vi_catn(rep, o, segid) 832 } else { 833 o = vi_cat(rep, o, "none" as *u8) 834 } 835 o = vi_cat(rep, o, "\n" as *u8) 836 sys_write(1, rep, o) 837 return 0 838} 839 840func main(argc: i64, argv: *i64) -> i64 { 841 if argc < 2 { vi_p("VIZSLA-ICS usage: export <prefix> <from> <to> <out.ics> | import <in.ics> <prefix> [segid] -- fail loud\n" as *u8); return 1 } 842 let cmd: *u8 = argv[1] as *u8 843 if vi_eq(cmd, "export" as *u8) == 1 { return vi_cmd_export(argc, argv) } 844 if vi_eq(cmd, "import" as *u8) == 1 { return vi_cmd_import(argc, argv) } 845 vi_p("VIZSLA-ICS unknown command (export|import) -- fail loud\n" as *u8) 846 return 1 847}