1 package main 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "database/sql" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11 12 "vimagination.zapto.org/ics" 13 ) 14 15 func (c *Calls) calendar(w http.ResponseWriter, r *http.Request) { 16 c.mu.Lock() 17 cal, err := c.makeCalendar() 18 c.mu.Unlock() 19 if err != nil { 20 http.Error(w, err.Error(), http.StatusInternalServerError) 21 return 22 } 23 var buf bytes.Buffer 24 err = ics.Encode(&buf, cal) 25 if err != nil { 26 http.Error(w, err.Error(), http.StatusInternalServerError) 27 return 28 } 29 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 30 if strings.TrimSpace(e) == "gzip" { 31 w.Header().Set("Content-Encoding", "gzip") 32 var b bytes.Buffer 33 g := gzip.NewWriter(&b) 34 buf.WriteTo(g) 35 g.Close() 36 buf = b 37 break 38 } 39 } 40 w.Header().Add("Content-Length", strconv.Itoa(buf.Len())) 41 w.Header().Add("Content-Type", "text/v-calendar; charset=utf-8") 42 w.Header().Add("Content-Disposition", "attachment; filename=calendar.ics") 43 buf.WriteTo(w) 44 } 45 46 func (c *Calls) makeCalendar() (*ics.Calendar, error) { 47 var alarmTime int 48 if err := c.statements[AlarmTime].QueryRow().Scan(&alarmTime); err != nil { 49 return nil, err 50 } 51 var ( 52 a ics.AlarmDisplay 53 cal ics.Calendar 54 ) 55 if alarmTime < 0 { 56 a.Trigger.Duration = &ics.Duration{Negative: true, Minutes: uint(-alarmTime)} 57 } else { 58 a.Trigger.Duration = &ics.Duration{Minutes: uint(alarmTime)} 59 } 60 alarm := []ics.Alarm{{AlarmType: &a}} 61 cal.ProductID = "Academy Chauffeurs 1.1" 62 cal.Version = "2.0" 63 n := now() 64 rows, err := c.statements[CalendarData].Query((n - 3600*24*30*6) * 1000) 65 if err != nil { 66 return nil, err 67 } 68 defer rows.Close() 69 cal.Event = make([]ics.Event, 0, 1024) 70 for rows.Next() { 71 var ( 72 id, start, end, created, updated int64 73 from, to, client, company, driverStr, phoneNumber, note, other, flightTime string 74 driver sql.NullString 75 ) 76 err := rows.Scan(&id, &start, &end, &from, &to, &created, &updated, ¬e, &other, &driver, &client, &company, &phoneNumber, &flightTime) 77 if err != nil { 78 return nil, err 79 } 80 client2 := client 81 if other != "" { 82 client2 += " + " + other 83 } 84 from = strings.Replace(from, "\n", " ", -1) 85 to = strings.Replace(to, "\n", " ", -1) 86 if driver.Valid { 87 driverStr = driver.String 88 } else { 89 driverStr = "Unassigned" 90 } 91 var ev ics.Event 92 ev.UID = ics.PropUID(time.Unix(created, 0).In(time.UTC).Format("20060102T150405Z") + "-" + pad(strconv.FormatUint(uint64(id), 36)) + "@academy-chauffeurs.co.uk") 93 ev.DateTimeStamp.Time = time.Unix(created, 0).In(time.UTC) 94 ev.LastModified = &ics.PropLastModified{Time: time.Unix(updated, 0).In(time.UTC)} 95 startD := time.Unix(start/1000, start%1000).In(time.UTC) 96 y, m, d := startD.Date() 97 h, mi, s := startD.Clock() 98 ev.DateTimeStart = &ics.PropDateTimeStart{DateTime: &ics.DateTime{Time: time.Date(y, m, d, h, mi, s, 0, time.Local)}} 99 var days, hours uint 100 mins := uint(time.Unix(end/1000, end%1000).In(time.UTC).Sub(startD).Minutes()) 101 for mins > 60*24 { 102 days++ 103 mins -= 60 * 24 104 } 105 for mins > 60 { 106 hours++ 107 mins -= 60 108 } 109 ev.Duration = &ics.PropDuration{Days: days, Hours: hours, Minutes: mins} 110 ev.Location = &ics.PropLocation{Text: ics.Text(from)} 111 var ft string 112 if flightTime != "" { 113 ft = "\n" + flightTime 114 } 115 ev.Description = &ics.PropDescription{Text: ics.Text(driverStr + " - " + client2 + " (" + company + ") - " + from + " -> " + to + " - " + phoneNumber + ft + "\n" + note)} 116 ev.Summary = &ics.PropSummary{Text: ics.Text(driverStr + " - " + client + " (" + company + ")")} 117 ev.Alarm = alarm 118 ev.Contact = []ics.PropContact{{Text: ics.Text(client + ", " + company + " - " + phoneNumber)}} 119 ev.Comment = []ics.PropComment{{Text: ics.Text(note)}} 120 cal.Event = append(cal.Event, ev) 121 } 122 return &cal, nil 123 } 124 125 const padLength = 20 126 127 func pad(s string) string { 128 t := bytes.Repeat([]byte{'0'}, padLength) 129 copy(t[padLength-len(s):], s) 130 return string(t) 131 } 132