1package models
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "strings"
8 "time"
9
10 "github.com/bluesky-social/indigo/atproto/syntax"
11 "tangled.org/core/api/tangled"
12)
13
14type String struct {
15 Did syntax.DID
16 Rkey string
17
18 Filename string
19 Description string
20 Contents string
21 Created time.Time
22 Edited *time.Time
23}
24
25func (s *String) StringAt() syntax.ATURI {
26 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", s.Did, tangled.StringNSID, s.Rkey))
27}
28
29func (s *String) AsRecord() tangled.String {
30 return tangled.String{
31 Filename: s.Filename,
32 Description: s.Description,
33 Contents: s.Contents,
34 CreatedAt: s.Created.Format(time.RFC3339),
35 }
36}
37
38func StringFromRecord(did, rkey string, record tangled.String) String {
39 created, err := time.Parse(record.CreatedAt, time.RFC3339)
40 if err != nil {
41 created = time.Now()
42 }
43 return String{
44 Did: syntax.DID(did),
45 Rkey: rkey,
46 Filename: record.Filename,
47 Description: record.Description,
48 Contents: record.Contents,
49 Created: created,
50 }
51}
52
53type StringStats struct {
54 LineCount uint64
55 ByteCount uint64
56}
57
58func (s String) Stats() StringStats {
59 lineCount, err := countLines(strings.NewReader(s.Contents))
60 if err != nil {
61 // non-fatal
62 // TODO: log this?
63 }
64
65 return StringStats{
66 LineCount: uint64(lineCount),
67 ByteCount: uint64(len(s.Contents)),
68 }
69}
70
71func countLines(r io.Reader) (int, error) {
72 buf := make([]byte, 32*1024)
73 bufLen := 0
74 count := 0
75 nl := []byte{'\n'}
76
77 for {
78 c, err := r.Read(buf)
79 if c > 0 {
80 bufLen += c
81 }
82 count += bytes.Count(buf[:c], nl)
83
84 switch {
85 case err == io.EOF:
86 /* handle last line not having a newline at the end */
87 if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
88 count++
89 }
90 return count, nil
91 case err != nil:
92 return 0, err
93 }
94 }
95}