main.go (9713B)
1 package main 2 3 import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "sort" 11 "strings" 12 13 "git.openbsd.org.ru/vasyahacker/openidec/ii" 14 ) 15 16 func open_db(path string) *ii.DB { 17 db := ii.OpenDB(path) 18 if db == nil { 19 fmt.Printf("Can no open db: %s\n", path) 20 os.Exit(1) 21 } 22 return db 23 } 24 25 func open_users_db(path string) *ii.UDB { 26 db := ii.OpenUsers(path) 27 if err := db.LoadUsers(); err != nil { 28 fmt.Printf("Can no load db: %s\n", path) 29 os.Exit(1) 30 } 31 return db 32 } 33 34 func GetFile(path string) string { 35 var file *os.File 36 var err error 37 if path == "-" { 38 file = os.Stdin 39 } else { 40 file, err = os.Open(path) 41 if err != nil { 42 fmt.Printf("Can not open file %s: %s\n", path, err) 43 os.Exit(1) 44 } 45 defer file.Close() 46 } 47 b, err := ioutil.ReadAll(file) 48 if err != nil { 49 fmt.Printf("Can not read file %s: %s\n", path, err) 50 os.Exit(1) 51 } 52 return string(b) 53 } 54 55 func main() { 56 ii.OpenLog(ioutil.Discard, os.Stdout, os.Stderr) 57 58 db_opt := flag.String("db", "./db", "II database path (directory)") 59 lim_opt := flag.Int("lim", 0, "Fetch last N messages") 60 verbose_opt := flag.Bool("v", false, "Verbose") 61 force_opt := flag.Bool("f", false, "Force full sync") 62 users_opt := flag.String("u", "points.txt", "Users database") 63 conns_opt := flag.Int("j", 6, "Maximum parallel jobs") 64 topics_opt := flag.Bool("t", false, "Select topics only") 65 from_opt := flag.String("from", "", "Select from") 66 to_opt := flag.String("to", "", "Select to") 67 flag.Parse() 68 ii.MaxConnections = *conns_opt 69 if *verbose_opt { 70 ii.OpenLog(os.Stdout, os.Stdout, os.Stderr) 71 } 72 73 args := flag.Args() 74 if len(args) < 1 { 75 fmt.Printf(`Help: %s [options] command [arguments] 76 Commands: 77 search <string> [echo] - search in base 78 send <server> <pauth> <msg|-> - send message 79 clean - cleanup database 80 fetch <url> [echofile|-] - fetch 81 store <bundle|-> - import bundle to database 82 get <msgid> - show message from database 83 select <echo> [[start]:lim] - get slice from echo 84 index - recreate index 85 blacklist <msgid> - blacklist msg 86 useradd <name> <e-mail> <password> - adduser 87 Options: 88 -db=<path> - database path 89 -lim=<lim> - fetch lim last messages 90 -u=<path> - points account file 91 -t - topics only (select,get) 92 -from=<user> - select from 93 -to=<user> - select to 94 `, os.Args[0]) 95 os.Exit(1) 96 } 97 switch cmd := args[0]; cmd { 98 case "search": 99 echo := "" 100 if len(args) < 2 { 101 fmt.Printf("No string supplied\n") 102 os.Exit(1) 103 } 104 if len(args) > 2 { 105 echo = args[2] 106 } 107 db := open_db(*db_opt) 108 db.Lock() 109 defer db.Unlock() 110 if err := db.LoadIndex(); err != nil { 111 fmt.Printf("Can not load index: %s\n", err) 112 os.Exit(1) 113 } 114 for _, v := range db.Idx.List { 115 if echo != "" { 116 mi := db.Idx.Hash[v] 117 if mi.Echo != echo { 118 continue 119 } 120 } 121 m := db.GetFast(v) 122 if m == nil { 123 continue 124 } 125 if strings.Contains(m.Text, args[1]) { 126 fmt.Printf("%s\n", v) 127 if *verbose_opt { 128 fmt.Printf("%s\n", m) 129 } 130 } 131 } 132 case "blacklist": 133 if len(args) < 2 { 134 fmt.Printf("No msgid supplied\n") 135 os.Exit(1) 136 } 137 db := open_db(*db_opt) 138 m := db.Get(args[1]) 139 if m != nil { 140 if err := db.Blacklist(m); err != nil { 141 fmt.Printf("Can not blacklist: %s\n", err) 142 os.Exit(1) 143 } 144 } else { 145 fmt.Printf("No such msg") 146 } 147 case "send": 148 if len(args) < 4 { 149 fmt.Printf("No argumnet(s) supplied\nShould be: <server> <pauth> and <file|->.\n") 150 os.Exit(1) 151 } 152 msg := GetFile(args[3]) 153 if _, err := ii.DecodeMsgline(string(msg), false); err != nil { 154 fmt.Printf("Wrong message format\n") 155 os.Exit(1) 156 } 157 n, err := ii.Connect(args[1]) 158 if err != nil { 159 fmt.Printf("Can not connect to %s: %s\n", args[1], err) 160 os.Exit(1) 161 } 162 if err := n.Post(args[2], msg); err != nil { 163 fmt.Printf("Can not send message: %s\n", err) 164 os.Exit(1) 165 } 166 case "useradd": 167 if len(args) < 4 { 168 fmt.Printf("No argumnet(s) supplied\nShould be: name, e-mail and password.\n") 169 os.Exit(1) 170 } 171 db := open_users_db(*users_opt) 172 if err := db.Add(args[1], args[2], args[3]); err != nil { 173 fmt.Printf("Can not add user: %s\n", err) 174 os.Exit(1) 175 } 176 case "clean": 177 hash := make(map[string]int) 178 last := make(map[string]string) 179 nr := 0 180 dup := 0 181 fmt.Printf("Pass 1...\n") 182 err := ii.FileLines(*db_opt, func(line string) bool { 183 nr++ 184 a := strings.Split(line, ":") 185 if len(a) != 2 { 186 ii.Error.Printf("Error in line: %d", nr) 187 return true 188 } 189 if !ii.IsMsgId(a[0]) { 190 ii.Error.Printf("Error in line: %d", nr) 191 return true 192 } 193 if _, ok := hash[a[0]]; ok { 194 hash[a[0]]++ 195 dup++ 196 last[a[0]] = line 197 } else { 198 hash[a[0]] = 1 199 } 200 return true 201 }) 202 if err != nil { 203 fmt.Printf("Error: %s\n", err) 204 os.Exit(1) 205 } 206 fmt.Printf("%d lines... %d dups...\n", nr, dup) 207 if dup == 0 { 208 os.Exit(0) 209 } 210 fmt.Printf("Pass 2...\n") 211 nr = 0 212 f, err := os.OpenFile(*db_opt+".new", os.O_CREATE|os.O_WRONLY, 0644) 213 if err != nil { 214 fmt.Printf("Error: %s\n", err) 215 os.Exit(1) 216 } 217 skip := 0 218 err = ii.FileLines(*db_opt, func(line string) bool { 219 nr++ 220 a := strings.Split(line, ":") 221 id := a[0] 222 if len(a) != 2 { 223 fmt.Printf("Error in line: %d\n", nr) 224 skip++ 225 return true 226 } 227 if !ii.IsMsgId(id) { 228 fmt.Printf("Error in line: %d\n", nr) 229 skip++ 230 return true 231 } 232 if v, ok := hash[id]; !ok || v == 0 { 233 fmt.Printf("Error. DB has changed. Aborted.\n") 234 os.Exit(1) 235 } 236 if hash[id] > 0 { // first record 237 hash[id] = -hash[id] 238 l := line 239 if hash[id] < -1 { 240 l = last[id] 241 } 242 if _, err := f.WriteString(l + "\n"); err != nil { 243 fmt.Printf("Error: %s\n", err) 244 os.Exit(1) 245 } 246 } else { 247 skip++ 248 } 249 hash[id] += 1 250 if hash[id] > 0 { 251 fmt.Println("Error. DB has changed. Aborted.") 252 os.Exit(1) 253 } 254 return true 255 }) 256 f.Close() 257 if err != nil { 258 fmt.Println("Error:", err) 259 os.Exit(1) 260 } 261 for _, v := range hash { 262 if v != 0 { 263 fmt.Println("Error. DB shrinked. Aborted.") 264 os.Exit(1) 265 } 266 } 267 fmt.Printf("%d messages removed. File %s created.\n", skip, *db_opt+".new") 268 case "fetch": 269 var echolist []string 270 if len(args) < 2 { 271 fmt.Println("No url supplied") 272 os.Exit(1) 273 } 274 db := open_db(*db_opt) 275 n, err := ii.Connect(args[1]) 276 if err != nil { 277 fmt.Printf("Can not connect to %s: %s\n", args[1], err) 278 os.Exit(1) 279 } 280 if *force_opt { 281 n.Force = true 282 } 283 if len(args) > 2 { 284 str := GetFile(args[2]) 285 for _, v := range strings.Split(str, "\n") { 286 echolist = append(echolist, strings.Split(v, ":")[0]) 287 } 288 } 289 err = n.Fetch(db, echolist, *lim_opt) 290 if err != nil { 291 fmt.Printf("Can not fetch from %s: %s\n", args[1], err) 292 os.Exit(1) 293 } 294 case "store": 295 if len(args) < 2 { 296 fmt.Printf("No bundle file supplied\n") 297 os.Exit(1) 298 } 299 db := open_db(*db_opt) 300 var f *os.File 301 var err error 302 if args[1] == "-" { 303 f = os.Stdin 304 } else { 305 f, err = os.Open(args[1]) 306 } 307 if err != nil { 308 fmt.Printf("Can no open bundle: %s\n", args[1]) 309 os.Exit(1) 310 } 311 defer f.Close() 312 reader := bufio.NewReader(f) 313 for { 314 line, err := reader.ReadString('\n') 315 if err != nil && err != io.EOF { 316 fmt.Printf("Can read input (%s)\n", err) 317 os.Exit(1) 318 } 319 line = strings.TrimSuffix(line, "\n") 320 if err == io.EOF { 321 break 322 } 323 m, err := ii.DecodeBundle(line) 324 if m == nil { 325 fmt.Printf("Can not parse message: %s (%s)\n", line, err) 326 continue 327 } 328 if db.Lookup(m.MsgId) == nil { 329 if err := db.Store(m); err != nil { 330 fmt.Printf("Can not store message: %s\n", err) 331 os.Exit(1) 332 } 333 } 334 } 335 case "get": 336 if len(args) < 2 { 337 fmt.Printf("No msgid supplied\n") 338 os.Exit(1) 339 } 340 db := open_db(*db_opt) 341 342 if *topics_opt { 343 mi := db.Lookup(args[1]) 344 if mi == nil { 345 return 346 } 347 mis := db.LookupIDS(db.SelectIDS(ii.Query{Echo: mi.Echo})) 348 topic := mi.Id 349 for p := mi; p != nil; p = db.LookupFast(p.Repto, false) { 350 if p.Repto == p.Id { 351 break 352 } 353 if p.Echo != mi.Echo { 354 continue 355 } 356 topic = p.Id 357 } 358 ids := db.GetTopics(mis)[topic] 359 if len(ids) == 0 { 360 ids = append(ids, args[1]) 361 } 362 for _, m := range ids { 363 fmt.Println(m) 364 } 365 return 366 } 367 368 m := db.Get(args[1]) 369 if m != nil { 370 fmt.Println(m) 371 } 372 case "select": 373 if len(args) < 2 { 374 fmt.Printf("No echo supplied\n") 375 os.Exit(1) 376 } 377 db := open_db(*db_opt) 378 req := ii.Query{Echo: args[1]} 379 if *from_opt != "" { 380 req.From = *from_opt 381 } 382 if *to_opt != "" { 383 req.To = *to_opt 384 } 385 386 if *topics_opt { 387 req.Repto = "!" 388 } 389 if len(args) > 2 { 390 fmt.Sscanf(args[2], "%d:%d", &req.Start, &req.Lim) 391 } 392 resp := db.SelectIDS(req) 393 for _, v := range resp { 394 if *verbose_opt { 395 fmt.Println(db.Get(v)) 396 } else { 397 fmt.Println(v) 398 } 399 } 400 case "sort": 401 db := open_db(*db_opt) 402 if err := db.LoadIndex(); err != nil { 403 fmt.Printf("Can not load index: %s\n", err) 404 os.Exit(1) 405 } 406 scanner := bufio.NewScanner(os.Stdin) 407 var mm []*ii.Msg 408 for scanner.Scan() { 409 mi := db.LookupFast(scanner.Text(), false) 410 if mi != nil { 411 mm = append(mm, db.Get(mi.Id)) 412 } 413 } 414 sort.SliceStable(mm, func(i, j int) bool { 415 return mm[i].Date < mm[j].Date 416 }) 417 for _, v := range mm { 418 if *verbose_opt { 419 fmt.Println(v) 420 } else { 421 fmt.Println(v.MsgId) 422 } 423 } 424 case "index": 425 db := open_db(*db_opt) 426 if err := db.CreateIndex(); err != nil { 427 fmt.Printf("Can not rebuild index: %s\n", err) 428 os.Exit(1) 429 } 430 default: 431 fmt.Printf("Wrong cmd: %s\n", cmd) 432 os.Exit(1) 433 } 434 }