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}