hackerbase/src/members-base.scm

261 lines
6.8 KiB
Scheme

;;
;; members-base.scm
;;
;; Storage for member files.
;;
;; ISC License
;;
;; Copyright 2023 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;;
;; Permission to use, copy, modify, and/or distribute this software
;; for any purpose with or without fee is hereby granted, provided
;; that the above copyright notice and this permission notice appear
;; in all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;
(declare (unit members-base))
(module
members-base
(
load-members
members-base-members
find-member-by-id
find-member-by-nick
find-members-by-nick
list-members-ids
filter-members-by-predicate
list-members-nicks
members-base-info
members-base-stats
get-free-members-ids
gen-member-id
members-base-update
members-base-add-unpaired
members-base-unpaired
members-base-active-emails
members-base-merge-mailman
)
(import scheme
(chicken base)
(chicken string)
(chicken random)
(chicken sort)
testing
util-list
dictionary
primes
member-record
ansi
period
month
configuration
progress
members-dir)
;; Loads members database, if the second argument is true, shows
;; progress. Members database is a dictionary with id being the key
;; (number) and member record being the value.
(define (load-members dn . opts)
(let ((progress? (and (not (null? opts))
(car opts))))
(make-dict
`((members
.
,(with-progress%
progress? "members"
(let* ((fss (load-members-dir dn))
(tot (sub1 (length (dict-keys fss))))
(mb0 (dict-map
(lambda (symfn symlinks prg)
(progress%-advance (/ prg tot))
(members-dir-load-member dn
symfn
symlinks))
fss))
(mb1 (dict-reduce (make-dict)
(lambda (acc symfn mr)
(dict-set acc (dict-ref mr 'id) mr))
mb0))
(mb (dict-reduce '()
(lambda (acc id mr)
(cons mr acc))
mb1)))
mb)))))))
;; Returns the internal members list
(define (members-base-members mb)
(dict-ref mb 'members))
;; Gets member based by generic predicate
(define (find-member-by-predicate mb pred)
(let loop ((mdb (members-base-members mb)))
(if (null? mdb)
#f
(let ((mr (car mdb)))
(if (pred mr)
mr
(loop (cdr mdb)))))))
;; Returns member record found by id
(define (find-member-by-id mb id)
(find-member-by-predicate
mb
(lambda (mr)
(eq? (dict-ref mr 'id) id))))
;; Returns member record found by id
(define (find-member-by-nick mb nick)
(find-member-by-predicate
mb
(lambda (mr)
(string-ci=?
(dict-ref
(dict-ref mr 'info)
'nick)
nick))))
;; Returns a list of members whose nick contains pat
(define (find-members-by-nick mb pat)
(filter-members-by-predicate
mb
(lambda (mr)
(substring-index pat (member-nick mr)))))
;; Returns all ids found in the database
(define (list-members-ids mb)
(map (lambda (mr) (dict-ref mr 'id))
(members-base-members mb)))
;; Returns a list of members which match given predicate.
(define (filter-members-by-predicate mb pred)
(let loop ((mb (members-base-members mb))
(res '()))
(if (null? mb)
res
(let ((mr (car mb)))
(loop (cdr mb)
(if (pred mr)
(cons mr res)
res))))))
;; Returns all nicks found in the database
(define (list-members-nicks mb)
(map (lambda (mr) (dict-ref (dict-ref mr 'info) 'nick))
(members-base-members mb)))
;; Returns dictionary with statistics about the members base.
(define (members-base-info mb-arg)
(let* ((members (filter-members-by-predicate mb-arg member-record-usable?))
(di0 (make-dict))
(di1 (dict-set di0 'invalid
(filter (compose not is-4digit-prime? member-id) members)))
(di2 (dict-set di1 'active
(filter member-active? members)))
(di3 (dict-set di2 'suspended
(filter member-suspended? members)))
(di4 (dict-set di3 'students
(filter member-student? members)))
(di5 (dict-set di4 'destroyed
(filter member-destroyed? members)))
(di6 (dict-set di5 'month (*current-month*)))
(di7 (dict-set di6 'total members)))
di7))
(define (members-base-oldest-month mb)
(make-month 2015 1))
;; Returns a list two lists: keys, data.
;; Each data record contains values for all keys.
(define (members-base-stats mb)
(let ((keys '(month total active suspended students destroyed invalid)))
(let mloop ((data '())
(month (members-base-oldest-month mb)))
(if (month<? month (*current-month*))
(let ((bi (parameterize ((*current-month* month))
(members-base-info mb))))
(let kloop ((row (list (dict-ref bi 'month)))
(keys (cdr keys)))
(if (null? keys)
(mloop (cons (reverse row) data)
(month-add month 1))
(kloop (cons (length (dict-ref bi (car keys))) row)
(cdr keys)))))
(list keys (reverse data))))))
;; Returns all free ids
(define (get-free-members-ids mb)
(let ((ids (list-members-ids mb)))
(filter
(lambda (id)
(not (member id ids)))
(gen-all-4digit-primes))))
;; Generates random vector id.
(define (gen-member-id mb)
(let* ((fids (get-free-members-ids mb))
(vfids (list->vector fids)))
(vector-ref vfids (pseudo-random-integer (vector-length vfids)))))
;; Returns new members base with member records matching the
;; predicate processed by proc.
(define (members-base-update mb pred? proc)
(dict-set mb
'members
(map (lambda (mr)
(if (pred? mr)
(proc mr)
mr))
(members-base-members mb))))
;; Adds unpaired transaction to given members-base
(define (members-base-add-unpaired mb tr)
(dict-set mb 'unpaired
(cons tr
(dict-ref mb 'unpaired '()))))
;; Returns known unpaired transactions
(define (members-base-unpaired mb)
(dict-ref mb 'unpaired '()))
;; Returns the list of emails of all active members sorted
;; alphabetically
(define (members-base-active-emails mb)
(sort
(filter
string?
(map
(lambda (mr)
(member-record-info mr 'mail))
(filter member-active?
(members-base-members mb))))
string-ci<?))
;; Merges given ML members into members base
(define (members-base-merge-mailman mb ml)
(let ((listname (car ml))
(emails (cdr ml)))
(foldl (lambda (mb email)
(members-base-update mb
(lambda (mr)
(equal? (member-record-info mr 'mail #f)
email))
(lambda (mr)
(member-add-mailman mr listname))))
mb
emails)))
)