ct 0.10.8.114__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. cantools/__init__.py +24 -0
  2. cantools/_db.py +142 -0
  3. cantools/_memcache.py +76 -0
  4. cantools/_pay.py +46 -0
  5. cantools/admin.py +31 -0
  6. cantools/cfg.py +347 -0
  7. cantools/config.py +131 -0
  8. cantools/db/__init__.py +18 -0
  9. cantools/db/admin.py +27 -0
  10. cantools/db/gae/__init__.py +0 -0
  11. cantools/db/gae/model.py +127 -0
  12. cantools/db/gae/properties.py +35 -0
  13. cantools/db/wp.py +99 -0
  14. cantools/geo.py +188 -0
  15. cantools/hooks.py +13 -0
  16. cantools/scripts/__init__.py +0 -0
  17. cantools/scripts/bench.py +167 -0
  18. cantools/scripts/builder.py +272 -0
  19. cantools/scripts/deploy.py +154 -0
  20. cantools/scripts/doc.py +239 -0
  21. cantools/scripts/index.py +226 -0
  22. cantools/scripts/init.py +345 -0
  23. cantools/scripts/migrate.py +593 -0
  24. cantools/scripts/pubsub/__init__.py +28 -0
  25. cantools/scripts/pubsub/actor.py +13 -0
  26. cantools/scripts/pubsub/bots.py +143 -0
  27. cantools/scripts/pubsub/channel.py +85 -0
  28. cantools/scripts/pubsub/ps.py +145 -0
  29. cantools/scripts/pubsub/user.py +51 -0
  30. cantools/scripts/start.py +53 -0
  31. cantools/scripts/util.py +24 -0
  32. cantools/util/__init__.py +78 -0
  33. cantools/util/admin.py +620 -0
  34. cantools/util/data.py +109 -0
  35. cantools/util/media.py +303 -0
  36. cantools/util/package.py +125 -0
  37. cantools/util/system.py +73 -0
  38. cantools/web/__init__.py +9 -0
  39. cantools/web/dez_server/__init__.py +1 -0
  40. cantools/web/dez_server/controller.py +129 -0
  41. cantools/web/dez_server/cron.py +115 -0
  42. cantools/web/dez_server/daemons.py +64 -0
  43. cantools/web/dez_server/mail.py +24 -0
  44. cantools/web/dez_server/response.py +63 -0
  45. cantools/web/dez_server/routes.py +21 -0
  46. cantools/web/dez_server/server.py +229 -0
  47. cantools/web/dez_server/sms.py +12 -0
  48. cantools/web/gae_server.py +68 -0
  49. cantools/web/util.py +552 -0
  50. ct-0.10.8.114.dist-info/LICENSE +9 -0
  51. ct-0.10.8.114.dist-info/METADATA +25 -0
  52. ct-0.10.8.114.dist-info/RECORD +55 -0
  53. ct-0.10.8.114.dist-info/WHEEL +5 -0
  54. ct-0.10.8.114.dist-info/entry_points.txt +10 -0
  55. ct-0.10.8.114.dist-info/top_level.txt +1 -0
cantools/util/admin.py ADDED
@@ -0,0 +1,620 @@
1
+ import os, sys, rel, time, datetime
2
+ from cantools.util import cmd, output, error, log, set_log, close_log, read, write, confirm, rm
3
+
4
+ coremods = ["screen", "ctstart", "ctpubsub", "ctutil", "ctinit", "dez_reverse_proxy", "dez_websocket_proxy"]
5
+ installers = ["apt", "yum", "pkg", "brew"]
6
+
7
+ def _starter(sname=None):
8
+ if sname == "None":
9
+ sname = None
10
+ starter = "screen -wipe ; screen -L -dm"
11
+ return sname and "%s -S %s"%(starter, sname) or starter
12
+
13
+ def _which(*names):
14
+ success = True
15
+ for name in names:
16
+ if not output("which %s"%(name,)):
17
+ log("%s not in path!"%(name,))
18
+ success = False
19
+ return success
20
+
21
+ def first(*names):
22
+ for name in names:
23
+ if _which(name):
24
+ log(name)
25
+ return name
26
+
27
+ def installer():
28
+ return first(*installers)
29
+
30
+ def install(*pkgs):
31
+ pacman = installer()
32
+ pline = " ".join(pkgs)
33
+ log("using %s to install %s"%(pacman, pline), important=True)
34
+ cmd("%s install %s"%(pacman, pline), sudo=True)
35
+
36
+ def snapinstall(pkg, classic=False):
37
+ iline = "snap install %s"%(pkg,)
38
+ if classic:
39
+ iline = "%s --classic"%(iline,)
40
+ cmd(iline, sudo=True)
41
+
42
+ def simplecfg(fname, sequential=False):
43
+ if not os.path.exists(fname):
44
+ return log("configuration file not found: %s"%(fname,))
45
+ data = [] if sequential else {}
46
+ for line in read(fname).split("\n"):
47
+ if not line or line.startswith("#"):
48
+ continue
49
+ variety = "basic"
50
+ if ":" in line:
51
+ variety, line = line.split(":", 1)
52
+ if sequential:
53
+ data.append({"variety": variety, "line": line})
54
+ else:
55
+ if variety not in data:
56
+ data[variety] = []
57
+ data[variety].append(line)
58
+ return data
59
+
60
+ def certs(dpath="/root", sname=None):
61
+ os.chdir(dpath)
62
+ set_log("cron-certs.log")
63
+ log("attempting to renew certs", important=True)
64
+ cmd('certbot renew --pre-hook "killall screen" --post-hook "%s"'%(_starter(sname),))
65
+ log("goodbye")
66
+ close_log()
67
+
68
+ def pcount(pname):
69
+ log("checking count: %s"%(pname,), important=True)
70
+ num = int(output("ps -ef | grep %s | egrep -v 'screener|pcount|grep' | wc -l"%(pname,)))
71
+ log("%s count: %s"%(pname, num), 1)
72
+ return num
73
+
74
+ def pcheck(pname, target, starter):
75
+ if target and pcount(pname) != target:
76
+ log("not enough %s processes - restarting screen!"%(pname,), 1)
77
+ log(output("screen -Q windows"), important=True)
78
+ cmd("killall screen; %s"%(starter,))
79
+ return True
80
+
81
+ def binpath(bpath="/usr/bin/"):
82
+ log("checking %s core modules"%(len(coremods),), important=True)
83
+ missings = []
84
+ for name in coremods:
85
+ mpath = "%s%s"%(bpath, name)
86
+ log("checking %s"%(name,), 1)
87
+ if not os.path.exists(mpath):
88
+ mloc = output("which %s"%(name,))
89
+ mloc or error("%s not installed"%(name,))
90
+ log("%s not in %s - found at %s instead"%(name, bpath, mloc))
91
+ missings.append(mloc)
92
+ if missings and confirm("symlink %s modules to %s"%(len(missings), bpath)):
93
+ os.chdir(bpath)
94
+ for mloc in missings:
95
+ cmd("ln -s %s"%(mloc,))
96
+ log("goodbye", important=True)
97
+
98
+ def postup():
99
+ from cantools.util.package import refresh_plugins
100
+ refresh_plugins()
101
+ binpath()
102
+
103
+ def zipit(fname, oname=None, remove=False, keepsyms=False):
104
+ log("zipping up %s"%(fname,), important=True)
105
+ if keepsyms == "ask":
106
+ keepsyms = confirm("keep symlinks", True)
107
+ ops = keepsyms and "ry" or "r"
108
+ oname = oname or "%s.zip"%(fname,)
109
+ cmd("zip -%s %s %s"%(ops, oname, fname))
110
+ remove and cmd("rm -rf %s"%(fname,))
111
+
112
+ def termap(term, default=1):
113
+ m = {}
114
+ terms = term.split("|")
115
+ for key in terms:
116
+ if ":" in key:
117
+ key, val = key.split(":")
118
+ val = int(val)
119
+ else:
120
+ val = default
121
+ m[key] = val
122
+ print(m)
123
+ return m
124
+
125
+ def dedchek(logname="screenlog.0", flag="server not ready"):
126
+ ldata = read(logname, default="")
127
+ count = len(ldata.split(flag)) - 1
128
+ set_log("dedchek.log")
129
+ log("dedchek: %s"%(count,), important=True)
130
+ if count:
131
+ from cantools.web import email_admins
132
+ email_admins("dedchek", "\n\n".join([
133
+ "unresponsive count: %s"%(count,),
134
+ "rotating log!",
135
+ "restarting screen!"
136
+ ]))
137
+ tstamp = str(datetime.datetime.now()).replace(" ", "_")
138
+ dname = "%sarchive"%(logname,)
139
+ if not os.path.isdir(dname):
140
+ cmd("mkdir %s"%(dname,))
141
+ cmd("mv %s %s"%(logname, os.path.join(dname, tstamp)))
142
+ cmd("killall screen; %s"%(_starter(),))
143
+ log("goodbye", important=True)
144
+ close_log()
145
+
146
+ def islocked(pname): # eg "dpkg" or "apt/lists"
147
+ unlocked = "Lock acquired"
148
+ lpath = "/var/lib/%s/lock"%(pname,)
149
+ log("testing %s lock at %s"%(pname, lpath))
150
+ fbase = "flock --timeout 0 --exclusive --nonblock"
151
+ fcmd = '%s %s -c "echo %s" || echo Lock is held'%(fbase, lpath, unlocked)
152
+ op = output(fcmd, sudo=True, silent=True, loud=True)
153
+ return op != unlocked
154
+
155
+ def screener(ctnum=None, dpath="/root", drpnum=None, psnum=None, sname=None, tmap=None):
156
+ os.chdir(dpath)
157
+ set_log("scrn.log")
158
+ log("checking modules", important=True)
159
+ _which(*coremods) or error("update your path!")
160
+ starter = _starter(sname)
161
+ if ctnum and type(ctnum) is not int:
162
+ ctnum = ctnum.isdigit() and int(ctnum)
163
+ if drpnum and type(drpnum) is not int:
164
+ drpnum = drpnum.isdigit() and int(drpnum)
165
+ if psnum and type(psnum) is not int:
166
+ psnum = psnum.isdigit() and int(psnum)
167
+ if "No Sockets found" in output("screen -list"):
168
+ log("no screen! restarting", 1)
169
+ cmd(starter)
170
+ else:
171
+ restarted = pcheck("ctstart", ctnum, starter) or pcheck("dez_reverse_proxy", drpnum, starter) or pcheck("ctpubsub", psnum, starter)
172
+ if tmap:
173
+ for k, v in termap(tmap).items():
174
+ restarted = restarted or pcheck(k, v, starter)
175
+ log("goodbye", important=True)
176
+ close_log()
177
+
178
+ def check(cmd="df"):
179
+ oz = output(cmd).split("\n")
180
+ for o in oz:
181
+ if o.endswith("/"):
182
+ return int(o.rsplit(" ", 2)[1][:-1])
183
+
184
+ def plugdirs(cb, pdir=".ctplug", filt="ct", ask=True):
185
+ opath = os.path.abspath(".")
186
+ os.chdir(pdir)
187
+ pz = os.listdir(".")
188
+ log("%s items in directory"%(len(pz),))
189
+ if filt:
190
+ pz = list(filter(lambda name : name.startswith(filt), pz))
191
+ log("found %s plugins:\n\n%s"%(len(pz), "\n".join(pz)))
192
+ if ask and not confirm("process %s plugins"%(len(pz),), True):
193
+ return
194
+ for p in pz:
195
+ log(p)
196
+ os.chdir(p)
197
+ cb()
198
+ os.chdir("..")
199
+ os.chdir(opath)
200
+
201
+ def gc(pdir=".ctplug", filt="ct", ask=False):
202
+ plugdirs(lambda : cmd("git gc"), pdir, filt, ask)
203
+
204
+ def cleanup(force=False):
205
+ if force or confirm("garbage collect git repos"):
206
+ gc()
207
+ if force or confirm("vacuum up old journals"):
208
+ cmd("journalctl --vacuum-size=50M")
209
+ if force or confirm("remove old packages"):
210
+ cmd("apt autoremove")
211
+ if force or confirm("clean up screenlogs"):
212
+ slogs = output("du -a . | sort -n -r | head -n 20 | grep screenlog")
213
+ if slogs:
214
+ lines = slogs.split("\n")
215
+ paths = [l.split("\t").pop() for l in lines]
216
+ plen = len(paths)
217
+ log("founds %s screenlogs:\n\n%s"%(plen, "\n".join(paths)), important=True)
218
+ if plen and (force or confirm("delete %s screenlogs"%(plen,))):
219
+ for slog in paths:
220
+ rm(slog)
221
+ log("all clear!")
222
+
223
+ def matchline(oline, start=None, end=None, sudo=False, loud=True):
224
+ for line in output(oline, sudo=sudo, loud=loud).split("\n"):
225
+ if start and line.startswith(start):
226
+ return line
227
+ if end and line.endswith(end):
228
+ return line
229
+
230
+ def sysup(upit=False, upct=False, dpath="."):
231
+ from cantools.web import email_admins
232
+ os.chdir(dpath)
233
+ set_log("cron-sysup.log")
234
+ log("updating package index", important=True)
235
+ if islocked("dpkg") or islocked("apt/lists"):
236
+ email_admins("can't update package list", "dpkg or apt/lists file is locked :(")
237
+ cmd("apt update", sudo=True)
238
+ alist = output("apt list --upgradable")
239
+ adrep = []
240
+ if alist.endswith("Listing..."):
241
+ adrep.append("no system updates available")
242
+ else:
243
+ ublock = alist.split("Listing...\n").pop()
244
+ ulist = ublock.split("\n")
245
+ ulen = len(ulist)
246
+ log("%s upgrades available"%(ulen,))
247
+ adrep.append("%s system updates %s:"%(ulen, upit and "attempted" or "available"))
248
+ adrep.append(ublock)
249
+ if upit:
250
+ log("upgrading %s packages"%(ulen,), important=True)
251
+ adrep.append(matchline("apt upgrade -y", end=" not upgraded.", sudo=True))
252
+ if upct:
253
+ adrep.append("updating web framework and plugins")
254
+ fullupline = matchline("ctinit -du", "Successfully installed ")
255
+ fullupline and adrep.append(fullupline)
256
+ shouldReboot = False
257
+ if os.path.exists("/var/run/reboot-required"):
258
+ upaks = output("cat /var/run/reboot-required.pkgs", loud=True)
259
+ adrep.append("updates include: %s"%(upaks,))
260
+ adrep.append("system restart required!")
261
+ if upit == "auto":
262
+ shouldReboot = True
263
+ adrep.append("restarting system!")
264
+ if adrep:
265
+ adrep = "\n\n".join(adrep)
266
+ log(adrep, important=True)
267
+ email_admins("system updates", adrep)
268
+ log("goodbye")
269
+ shouldReboot and reboot()
270
+ close_log()
271
+
272
+ def reboot(wait=5):
273
+ log("rebooting in %s seconds!"%(wait,))
274
+ if wait:
275
+ time.sleep(int(wait))
276
+ cmd("reboot")
277
+
278
+ def running(proc):
279
+ if not "active (running)" in output("service %s status"%(proc,)):
280
+ return log("%s isn't running!!!"%(proc,))
281
+ log("%s is running"%(proc,))
282
+ return True
283
+
284
+ def upcheck(*procs):
285
+ from cantools.web import email_admins
286
+ set_log("cron-upcheck.log")
287
+ log("checking services: %s"%(", ".join(procs),), important=True)
288
+ restarts = []
289
+ for proc in procs:
290
+ if not running(proc):
291
+ restarts.append(proc)
292
+ log("restarting %s!"%(proc,), important=True)
293
+ servicer(proc)
294
+ restarts and email_admins("services restarted", "\n\n".join(restarts))
295
+ log("goodbye")
296
+ close_log()
297
+
298
+ def vitals(clean=False, thresh=90, dpath="."):
299
+ if clean == "False":
300
+ clean = False
301
+ thresh = int(thresh)
302
+ from cantools.web import email_admins
303
+ os.chdir(dpath)
304
+ set_log("cron-vitals.log")
305
+ log("scanning vitals", important=True)
306
+ lz = []
307
+ hdrive = check()
308
+ lz.append("hard drive usage: %s%%"%(hdrive,))
309
+ inodes = check("df -i")
310
+ lz.append("inode usage: %s%%"%(inodes,))
311
+ memuse = float(output("free | grep Mem | awk '{print $3/$2 * 100.0}'"))
312
+ lz.append("memory usage: %s%%"%(memuse,))
313
+ for l in lz:
314
+ log(l)
315
+ if hdrive > thresh or inodes > thresh or memuse > thresh:
316
+ log("threshold exceeded - notifying admins")
317
+ if hdrive > thresh and clean:
318
+ log("cleaning up!")
319
+ cleanup(True)
320
+ lz.append("cleaned up hard drive!")
321
+ email_admins("threshold exceeded", "\n".join(lz))
322
+ log("goodbye")
323
+ close_log()
324
+
325
+ def sslredirect(port=80):
326
+ from dez.http.reverseproxy import startreverseproxy
327
+ from cantools.config import Config
328
+ startreverseproxy(Config({
329
+ "port": port,
330
+ "ssl_redirect": "auto"
331
+ }))
332
+
333
+ def blacklist(ip):
334
+ if not confirm("blacklist %s?"%(ip,)):
335
+ return log("okay, bye!")
336
+ from cantools.web import controller
337
+ from cantools import config
338
+ controller.setBlacklist()
339
+ blist = config.web.blacklist.obj()
340
+ if ip in blist:
341
+ return log("%s is already blacklisted! reason: %s"%(ip, blist[ip]))
342
+ log("adding %s to black.list"%(ip,))
343
+ blist[ip] = input("reason? [default: 'manual ban'] ") or "manual ban"
344
+ log("saving black.list")
345
+ write(blist, "black.list", isjson=True)
346
+ confirm("restart screen?") and cmd("killall screen ; screen -L")
347
+
348
+ def replace(flag, swap, ext="md"):
349
+ fz = list(filter(lambda fn : fn.endswith(ext), os.listdir()))
350
+ log("processing %s %s files"%(len(fz), ext))
351
+ for f in fz:
352
+ log(f, 1)
353
+ write(read(f).replace(flag, swap), f)
354
+ log("goodbye!")
355
+
356
+ def json2abi(fname):
357
+ write(read(fname, isjson=True)['abi'], fname.replace("json", "abi"), isjson=True)
358
+
359
+ def enc(fname, oname=None, nowrite=False, asdata=False, nolog=False, replace=None):
360
+ from cantools.web import enc as wenc
361
+ oname = oname or fname.replace("txt", "enc")
362
+ nolog or log("enc(%s -> %s) nowrite=%s"%(asdata and "data" or fname, oname, nowrite))
363
+ data = asdata and fname or read(fname)
364
+ if replace: # tuple
365
+ rfrom, rto = replace
366
+ data = data.replace(rfrom, rto)
367
+ enced = wenc(data)
368
+ nowrite or write(enced, oname)
369
+ return enced
370
+
371
+ def dec(fname, oname=None, nowrite=False, asdata=False, nolog=False, replace=None):
372
+ from cantools.web import dec as wdec
373
+ oname = oname or fname.replace("enc", "txt")
374
+ nolog or log("dec(%s -> %s) nowrite=%s"%(asdata and "data" or fname, oname, nowrite))
375
+ deced = wdec(asdata and fname or read(fname))
376
+ if replace: # tuple
377
+ rfrom, rto = replace
378
+ deced = deced.replace(rfrom, rto)
379
+ nowrite or write(deced, oname)
380
+ return deced
381
+
382
+ def qenc(fname, asdata=False, nolog=True):
383
+ enced = enc(fname, nowrite=True, asdata=asdata, nolog=nolog)
384
+ print(enced)
385
+ return enced
386
+
387
+ def qdec(fname, asdata=False, nolog=True):
388
+ deced = dec(fname, nowrite=True, asdata=asdata, nolog=nolog)
389
+ print(deced)
390
+ return deced
391
+
392
+ def ushort(url):
393
+ from cantools import config
394
+ csl = config.shortlinker
395
+ if not csl:
396
+ error("no shortlinker configured")
397
+ from urllib.parse import quote
398
+ from cantools.web import fetch
399
+ code = fetch("https://%s?u=%s"%(csl, quote(url)), ctjson=True)
400
+ print("code:", code)
401
+ return "https://%s?k=%s"%(csl, code)
402
+
403
+ class Creeper(object):
404
+ def __init__(self, total=120, mid=40, short=10):
405
+ self.total = total
406
+ self.mid = mid
407
+ self.short = short
408
+ self.duration = 0
409
+ self.last = None
410
+ self.diffs = []
411
+ self.diff = 0
412
+ self.start()
413
+
414
+ def signed(self, num):
415
+ num = round(num, 2)
416
+ return num < 0 and num or "+%s"%(num,)
417
+
418
+ def pad(self, s, target=10):
419
+ ls = len(s)
420
+ if ls < target:
421
+ return "%s%s"%(s, " " * (target - ls))
422
+ return s
423
+
424
+ def ave(self, size=None):
425
+ dz = self.diffs
426
+ if size == "diff":
427
+ diff = self.diff
428
+ elif size == "cur":
429
+ diff = dz[-1]
430
+ else:
431
+ if size:
432
+ nums = dz[-size:]
433
+ else:
434
+ nums = dz
435
+ size = len(dz)
436
+ diff = sum(nums) / size
437
+ size = "%ss"%(size,)
438
+ return self.pad("%s: %s"%(size, self.signed(diff)))
439
+
440
+ def calc(self, diff):
441
+ self.diff += diff
442
+ dz = self.diffs
443
+ dz.append(diff)
444
+ if len(dz) > self.total:
445
+ dz.pop(0)
446
+ dl = len(dz)
447
+ parts = [self.ave("cur")]
448
+ if dl > 1:
449
+ if dl > self.short:
450
+ parts.append(self.ave(self.short))
451
+ if dl > self.mid:
452
+ parts.append(self.ave(self.mid))
453
+ parts.append(self.ave())
454
+ parts.append(self.ave("diff"))
455
+ return " ; ".join(parts)
456
+
457
+ def creep(self):
458
+ self.duration += 1
459
+ current = int(output("free | grep Mem | awk '{print $3}'", silent=True))
460
+ if self.last:
461
+ print("dur: %s ; %s"%(self.duration, self.calc(current - self.last)))
462
+ self.last = current
463
+ return True
464
+
465
+ def start(self):
466
+ rel.signal(2, rel.abort)
467
+ rel.timeout(1, self.creep)
468
+ rel.dispatch()
469
+
470
+ def memcreep(total=120, mid=40, short=10):
471
+ Creeper(int(total), int(mid), int(short))
472
+
473
+ def phpver():
474
+ if not _which("php"): return
475
+ v = output("php -v")[4:7]
476
+ log(v)
477
+ return v
478
+
479
+ def javaver():
480
+ if not _which("javac"): return
481
+ v = output("javac -version")[6:8]
482
+ log(v)
483
+ return v
484
+
485
+ def withtmp(fdata, fun, owner=None, fname="_tmp"):
486
+ write(fdata, fname)
487
+ owner and cmd("chown %s:%s %s"%(owner, owner, fname), True)
488
+ fun()
489
+ os.remove(fname)
490
+
491
+ def servicer(proc, action="restart", sycon="service", ask=False):
492
+ if ask and not confirm("%s %s"%(action, proc)):
493
+ return
494
+ if sycon == "service":
495
+ conline = "service " + proc + " %s"
496
+ else: # systemctl
497
+ conline = "systemctl %s " + proc
498
+ cmd(conline%(action,))
499
+
500
+ def whilestopped(proc, fun, sycon="service", starter="start"):
501
+ servicer(proc, "stop", sycon)
502
+ fun()
503
+ servicer(proc, starter, sycon)
504
+
505
+ # mysql stuff...
506
+
507
+ MYSQL_ALTERP = "ALTER USER '%s'@'%s' IDENTIFIED BY '%s';"
508
+ MYSQL_RESET = """flush privileges;
509
+ ALTER USER '%s'@'%s' IDENTIFIED BY '%s';"""
510
+ MYSQL_CREATE_USER = """FLUSH PRIVILEGES;
511
+ CREATE USER '%s'@'%s' IDENTIFIED BY '%s';
512
+ GRANT ALL PRIVILEGES ON *.* TO '%s'@'%s' WITH GRANT OPTION;
513
+ FLUSH PRIVILEGES;"""
514
+ MYSQL_USERS = """select User,Host from mysql.user;"""
515
+
516
+ def mysqltmp(fdata, fun, owner="mysql", sycon="service", starter="start"):
517
+ withtmp(fdata, lambda : whilestopped("mysql", fun, sycon, starter), owner)
518
+
519
+ def mysqlsafe(fname="_tmp", user="root"):
520
+ # output("mysqld_safe --skip-grant-tables &", loud=True)
521
+ cmd("mysqld --skip-grant-tables --skip-networking -u %s &"%(user,))
522
+ time.sleep(0.5)
523
+ o = output("mysql < %s"%(fname,), loud=True)
524
+ cmd("killall mysqld")
525
+ return o
526
+
527
+ def mysqlreset(user="root", hostname="localhost", password=None):
528
+ password = password or input("new password for '%s' user? "%(user,))
529
+ mysqltmp(MYSQL_ALTERP%(user, hostname, password),
530
+ lambda : cmd("mysqld -init-file=_tmp"))
531
+
532
+ def mysqlresetnp(user="root", hostname="localhost", password=None):
533
+ password = password or input("new password for '%s' user? "%(user,))
534
+ mysqltmp(MYSQL_RESET%(user, hostname, password), mysqlsafe)
535
+
536
+ def mysqluser(user="root", hostname="localhost", password=None):
537
+ log("creating mysql user: %s@%s"%(user, hostname), important=True)
538
+ password = password or input("new password for '%s' user? "%(user,))
539
+ mysqltmp(MYSQL_CREATE_USER%(user, hostname,
540
+ password, user, hostname), mysqlsafe)
541
+
542
+ def mysqlexists(user="root", hostname="localhost", ensure=False):
543
+ log("checking for user '%s' at hostname '%s'"%(user, hostname), important=True)
544
+ for uline in mysqlsafe().split("\n"):
545
+ u, h = uline.split("\t")
546
+ if u == user and h == hostname:
547
+ log("user found!", important=True)
548
+ return True
549
+ log("user not found :(", important=True)
550
+ if ensure == "ask":
551
+ ensure = confirm("create %s@%s mysql user"%(user, hostname), True)
552
+ ensure and mysqluser(user, hostname)
553
+
554
+ def mysqlcheck(user="root", hostname="localhost", ensure=False):
555
+ mysqltmp(MYSQL_USERS, lambda : mysqlexists(user, hostname, ensure))
556
+
557
+ # ccbill stuff...
558
+
559
+ def _getmems(fname, simple=True, count=False, xls=False, tsv=False):
560
+ from cantools.util.data import getcsv, gettsv, getxls
561
+ members = {}
562
+ if xls:
563
+ rows = getxls(read(fname))
564
+ elif tsv:
565
+ rows = gettsv(read(fname))
566
+ else:
567
+ rows = getcsv(fname)
568
+ for row in rows:
569
+ if simple:
570
+ members[row[14].lower()] = row[3]
571
+ else: # diff (obsolete?) report format...
572
+ members[row[8]] = count or {
573
+ "date": row[13],
574
+ "status": row[15],
575
+ "initial_price": row[16],
576
+ "initial_period": row[17],
577
+ "recurring_price": row[18],
578
+ "recurring_period": row[19],
579
+ "rebills": row[20]
580
+ }
581
+ if count:
582
+ return len(list(members.keys()))
583
+ return members
584
+
585
+ def getCCmems(fname="active-members.csv", simple=True, count=False):
586
+ try:
587
+ mems = _getmems(fname, simple, count)
588
+ except:
589
+ log("getmems: csv read failed - trying tsv")
590
+ try:
591
+ mems = _getmems(fname, simple, count, tsv=True)
592
+ except:
593
+ log("getmems: tsv read failed - trying xls")
594
+ mems = _getmems(fname, simple, count, xls=True)
595
+ return mems
596
+
597
+ def ccbreport(fpath):
598
+ from cantools.web import fetch
599
+ from cantools.util import cp
600
+ from cantools import config
601
+ username = config.cache("datalink username? ")
602
+ password = config.cache("datalink password? ")
603
+ accnum = config.cache("datalink account number? ")
604
+ data = fetch("datalink.ccbill.com", "/data/main.cgi", timeout=10, protocol="https", qsp={
605
+ "clientSubacc": "0000",
606
+ "username": username,
607
+ "password": password,
608
+ "clientAccnum": accnum,
609
+ "transactionTypes": "ACTIVEMEMBERS"
610
+ }).decode()
611
+ cp(data, fpath)
612
+
613
+ def ccbmems():
614
+ fn = str(datetime.datetime.now()).split(" ").pop(0).replace("-", "") + ".csv"
615
+ fp = os.path.join("activemembers", fn)
616
+ log("checking for ccbill report at %s"%(fp,))
617
+ if not os.path.exists(fp):
618
+ log("file not present - acquiring from ccbill")
619
+ ccbreport(fp)
620
+ return getCCmems(fp)