#!/usr/bin/python # poll_server_linear.py # --copyright-- Copyright 2010 (C) Tranzoa, Co. All rights reserved. Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality. # --url-- http://www.tranzoa.net/tzpython/ # --email-- pycode is the name to send to. tranzoa.com is the place to send to. # --bodstamps-- # December 25, 2010 bar # December 26, 2010 bar # December 27, 2010 bar client name, not # December 28, 2010 bar # March 23, 2011 bar a_logger is spun off to tz_server_logger # November 7, 2011 bar close log in main # November 29, 2011 bar pyflake cleanup # May 27, 2012 bar doxygen namespace # August 19, 2015 bar pyflakes # November 7, 2017 bar maxint->maxsize # March 2, 2023 bar python3 # --eodstamps-- ## \file # \namespace tzpython.poll_server_linear # # # This is a web server for polling people about some linearly related items. # That is, the items can be rated against each other on a linear, 1-dimension scale. Better, worse or the same. # # from __future__ import print_function import random import re import sys import threading import poll_server import strip_files import tz_chooser import tz_server_logger import tz_timer import tzlib LOG_FILE_NAME = "poll_server.log" STATE_FILE_NAME = "poll_server.pickle" # # template for the form contents with choices and buttons # A_FORM = """
%s |
%s
""" class a_query(object) : def __init__(me, qn, west, east) : me.qn = qn me.west = west me.east = east # a_query class a_choice(object) : def __init__(me, choice, htm, btn) : me.choice = choice me.htm = htm me.btn = btn # a_choice class a_pair(object) : def __init__(me, west, equal, east) : me.west = west me.equal = equal me.east = east me.qn = str(random.randint(1, sys.maxsize)) me.qn_htm = poll_server.A_FORM_QN % me.qn me.pre_form_htm = "" me.post_form_htm = "" # a_pair class a_poll_server(poll_server.a_poll_server) : view_em_re = re.compile(r"^/?(top|bottom)_*(\d+)$", re.DOTALL) def __init__(me, chooser = None, *kargs, **kwargs) : super(a_poll_server, me).__init__(*kargs, **kwargs) me.chooser = chooser me.queries = {} me.hit_cnt = 0 me.lock = threading.RLock() def generic_hit(me, hit) : if not super(a_poll_server, me).generic_hit(hit) : fn = me.possible_file(hit) fd = me.load_file(fn, ".png") if fd : if hit.handler.send_http_headers(hit.is_head, content_type = "image/png") : hit.handler.write_data(fd) return(True) fd = me.load_file(fn, ".jpg") if not fd : fd = me.load_file(fn, ".jpeg") if fd : if hit.handler.send_http_headers(hit.is_head, content_type = "image/jpeg") : hit.handler.write_data(fd) return(True) if me.chooser : if hit.handler.file_only_lc == "relearn" : me.chooser.relearn_order() hit.handler.file_only_lc = "top10" g = me.view_em_re.search(hit.handler.file_only_lc) if g : me.lock.acquire() me.chooser.sort_as_best_we_can() if g.group(1) == 'bottom' : ca = me.chooser[:int(g.group(2))] tt = "Bottom %d" % len(ca) if g.group(1) == 'top' : ca = me.chooser[max(0, len(me.chooser) - int(g.group(2))):] ca.reverse() tt = "Top %d" % len(ca) me.lock.release() if len(ca) : if hit.server.send_http_headers(hit.handler, is_head = hit.is_head) : lst = "\n
\n".join([ c.data for c in ca ]) pg = VIEW_EM_PAGE % ( tt, tt, lst ) hit.handler.wfile.write(pg) return(True) pass pass pass return(False) def pre_main_page(me, hit) : return("Pick one or the other or call it a toss-up.") def poll_form(me, hit) : me.handle_hit(hit) p = me.get_pair(hit) if p != None : frm = A_FORM % ( p.west.btn, p.equal.btn, p.east.btn, p.west.htm, p.equal.htm, p.east.htm, ) else : frm = A_FORM % ( "Oooo", "I'm", "lost!", "This", "is", "bad.", ) if False : if not hit.cn : sf = poll_server.A_FORM % ( "%s", "%s", poll_server.YOUR_NAME + "%s" ) else : sf = poll_server.A_FORM % ( "%s", "%s", (poll_server.YOUR_NAME_KNOWN % hit.cn) + "%s" ) pass else : sf = poll_server.A_FORM frm = sf % ( hit.ci, hit.fn, (p.qn_htm if p else "") + frm ) frm = (p.pre_form_htm if p else "") + frm + (p.post_form_htm if p else "") + KEYBOARD_JS return(frm) def handle_hit(me, hit) : if not hit.qn : return(False) me.lock.acquire() if hit.qn in me.queries : p = me.queries[hit.qn] del(me.queries[hit.qn]) r = None if 'equal' in hit.args : r = 0 elif 'west' in hit.args : r = 1 elif 'east' in hit.args : r = -1 if (r != None) and me.chooser : me.chooser.learn( p.west.choice, p.east.choice, r) me.chooser.make_old(p.west.choice, p.east.choice) me.hit_cnt += 1 me.lock.release() me.server.log('Choice %s#%s %d "%s" "%s"' % ( hit.handler.client_address[0], hit.ci, r, p.west.choice.name, p.east.choice.name, ) ) return(True) pass me.lock.release() return(False) def get_pair(me, hit) : p = None me.lock.acquire() if me.chooser : ( r, a, b ) = me.chooser.find_pair() if random.random() >= 0.5 : ( b, a ) = ( a, b ) w = a_choice(a, (a.data if a and a.data else "West choice"), "This one") eq = a_choice(None, "", "Toss up") e = a_choice(b, (b.data if b and b.data else "East choice"), "This one") p = a_pair(w, eq, e) me.queries[p.qn] = p me.hit_cnt += 1 me.lock.release() return(p) log_file_choice_re = re.compile(r'^Choice\s+\S+\s*(-?\d+)\s+"([^"]+)"\s+"([^"]+)"\s*$', re.MULTILINE) def learn_log(me, fn) : try : if me.chooser : fd = tzlib.read_whole_text_file(fn) lns = me.log_file_choice_re.findall(fd) me.lock.acquire() cd = {} for c in me.chooser : cd[c.name] = c da = [] li = 0 for r, a, b in lns : li += 1 if (a in cd) and (b in cd) : # print("@@@@", a, b, r, li, len(lns))) if me.chooser.learn(cd[a], cd[b], int(r), extend = False) : me.chooser.make_old(cd[a], cd[b]) da.append((int(r), cd[a], cd[b])) pass pass if tz_chooser.EXTEND_LEARNING : for r, a, b in da : a.extend_learning(b, r) b.extend_learning(a, -r) pass me.lock.release() return(len(da)) pass except ( IOError, OSError ) : pass return(-1) # a_poll_server if __name__ == '__main__' : import os import time import TZKeyReady import TZCommandLineAtFile import tz_os_priority program_name = os.path.basename(sys.argv.pop(0)) TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv) http_port = poll_server.HTTP_PORT log_file = None chooser = tz_chooser.a_chooser() query_htm = "Which picture is prettier?" root_dir = None state_file = None learn_logs = [] how_long = float(sys.maxsize) help_str = tzlib.multiline_flush_left(""" %s (options) (root_dir|state_file) I am a web server that asks people to compare images. Options: --http_port port_number Set the HTTP port number (default: %u) --log_file file_name Set the log file name. (default: none) --learn_log file_name Learn the choices in the given log file. --query html Set the query html. --choice name html Add a choice to the possibilities. --root directory Set the server root file directory. (default: ".") --state_file file_name Set the state file name. (default: none) --how_long seconds Only run for this long. (default: forever) """) % ( program_name, http_port, ) oi = tzlib.array_find(sys.argv, [ "--help", "-?", "?", "-h", "/h", "/?", "?" ] ) if oi >= 0 : print(help_str) sys.exit(254) while True : oi = tzlib.array_find(sys.argv, [ "--http_port", "--http-port", "--httpport", "--port", "-p", ] ) if oi < 0 : break del sys.argv[oi] http_port = int(sys.argv.pop(oi)) while True : oi = tzlib.array_find(sys.argv, [ "--log_file", "--log-file", "--logfile", "--log_file_name", "--log-file-name", "--logfilename", "-l", ] ) if oi < 0 : break del sys.argv[oi] log_file = sys.argv.pop(oi) while True : oi = tzlib.array_find(sys.argv, [ "--state_file", "--state-file", "--statefile", "-f", "-s", ] ) if oi < 0 : break del sys.argv[oi] state_file = sys.argv.pop(oi) while True : oi = tzlib.array_find(sys.argv, [ "--root", "--root_dir", "--root-dir", "--rootdir", "--root_directory", "--root-directory", "--rootdirectory", "-r", ] ) if oi < 0 : break del sys.argv[oi] root_dir = sys.argv.pop(oi) while True : oi = tzlib.array_find(sys.argv, [ "--query", "-q" ] ) if oi < 0 : break del sys.argv[oi] query_htm = sys.argv.pop(oi) while True : oi = tzlib.array_find(sys.argv, [ "--choice", "-c" ] ) if oi < 0 : break del sys.argv[oi] name = sys.argv.pop(oi) htm = sys.argv.pop(oi) chooser.append(tz_chooser.a_choice(name, htm)) while True : oi = tzlib.array_find(sys.argv, [ "--learn_log", "--learn-log", "--learnlog", ] ) if oi < 0 : break del sys.argv[oi] learn_logs.append(sys.argv.pop(oi)) while True : oi = tzlib.array_find(sys.argv, [ "--how_long", "--how-long", "--howlong", ] ) if oi < 0 : break del sys.argv[oi] how_long = float(sys.argv.pop(oi)) fn = sys.argv.pop(0) if len(sys.argv) else None if fn : if os.path.isdir(fn) : root_dir = fn if not log_file : log_file = os.path.join(root_dir, LOG_FILE_NAME) if not state_file : state_file = os.path.join(root_dir, STATE_FILE_NAME) state_file = sys.argv.pop(0) if len(sys.argv) else state_file else : state_file = fn pass if state_file : ch = tzlib.unpickle_file(state_file) if ch : try : ch.find_pair() except : ch = None pass if ch : chd = ch.make_choice_dict() for c in chooser : if c.name not in chd : ch.append(c) elif chd[c.name].data != c.data : chd[c.name].data = c.data # update the html from the cmd line in case the config file changed pass if len(chooser) : chd = chooser.make_choice_dict() for ci in range(len(ch) - 1, -1, -1) : cc = ch[ci] if cc.name not in chd : del(ch[ci]) # whack any choices that have gone away from the cmd line file pass pass chooser.choices = ch.choices chooser.learn_cnt = ch.learn_cnt chooser.found_cnt = ch.found_cnt chooser.hurry_up = 0 pass chooser.set_chunk_size(sys.maxsize) cw = max([ len(c.name) for c in chooser ] + [ 0 ]) + 3 class a_test_server(a_poll_server) : def pre_main_page(me, hit) : return(query_htm) # a_test_server server = a_test_server(root_dir = root_dir, chooser = chooser) logger = tz_server_logger.a_logger(file_name = log_file, http_port = http_port) me = poll_server.a_server(http_port = http_port, server = server, logger = logger, show_exceptions = True) cnt = 0 for fn in learn_logs : cc = server.learn_log(fn) if cc <= 0 : print("Learned nothing from %s!" % cc) else : cnt += cc pass if cnt : print("Learned %d choices." % cnt) me.start() print("%d choices" % len(chooser)) sys.setrecursionlimit((len(chooser) * 100) or 100) if tz_chooser.EXTEND_COMPARE or (not tz_chooser.EXTEND_LEARNING) : tz_os_priority.set_proc_to_idle_priority(0) work_queue = [] if how_long < 0 : how_long = sys.maxsize start_time = tz_timer.tick() while tz_timer.tick() - start_time < how_long : k = TZKeyReady.key_ready() if k : print("key", k) if k == '?' : server.lock.acquire() chooser.sort_as_best_we_can() print("results", chooser.all_done, chooser.learn_cnt, chooser.age(), "\n " + "\n ".join([ "%-*s %7s" % ( cw, ccc.name, str(round(ccc.order(), 1)) ) for ccc in chooser ]), "\n", chooser.age()) server.lock.release() elif k.lower() == 'r' : server.lock.acquire() chooser.relearn_order() print("relearn", chooser.all_done, chooser.learn_cnt, chooser.age(), "\n " + "\n ".join([ "%-*s %7s" % ( cw, ccc.name, str(round(ccc.order(), 1)) ) for ccc in chooser ]), "\n", chooser.age()) server.lock.release() elif k.lower() == 'a' : server.lock.acquire() pa = chooser.age() ap = chooser.age(tz_chooser.AGE) chooser.relearn_order() print("age ", chooser.all_done, chooser.learn_cnt, pa, ap, pa - ap, "\n " + "\n ".join([ "%-*s %7s" % ( cw, ccc.name, str(round(ccc.order(), 1)) ) for ccc in chooser ]), "\n", pa, ap) server.lock.release() elif k.lower() == 'q' : break elif k == chr(27) : break logger.flush() if len(work_queue) : ( cnm, onm, r ) = work_queue[0] hc = server.hit_cnt ca = chooser.find_tweens(cnm, onm, r) server.lock.acquire() if server.hit_cnt == hc : work_queue.pop(0) # print("@@@@ ca", cnm, onm, r, [ ccc.name for ccc in ca ]) chooser.put_choice_to_extend_learning(ca) server.lock.release() elif tz_chooser.EXTEND_COMPARE : while chooser.get_choice_to_extend_learning() : pass hc = server.hit_cnt ( r, a, b ) = chooser.find_distant_pair() # doing this outside the lock is asking for trouble, but it takes many seconds to run, at the least. Very slow. if r != None : server.lock.acquire() if (server.hit_cnt == hc) and (not a.knows(b)) : a.learn( b, r, extend = False) if r : b.learn(a, -r, extend = False) # print("@@@@ extended", a.name, b.name, r, chooser.age()) pass server.lock.release() pass elif not tz_chooser.EXTEND_LEARNING : hc = server.hit_cnt server.lock.acquire() c = chooser.get_choice_to_extend_learning() server.lock.release() if c : ( m, z, p ) = c.find_branches() server.lock.acquire() if server.hit_cnt == hc : ( m, z, p ) = chooser.learn_only_if_new(c, m, z, p) else : chooser.put_choice_to_extend_learning(c) m = {} z = {} p = {} server.lock.release() # if m : print("@@@@ xl", c.name, -1, m) # if z : print("@@@@ xl", c.name, 0, z) # if p : print("@@@@ xl", c.name, 1, p) for n in m : work_queue.append(( c.name, n, -1 )) for n in z : work_queue.append(( c.name, n, 0 )) for n in p : work_queue.append(( c.name, n, 1 )) pass else : while chooser.get_choice_to_extend_learning() : pass time.sleep(0.1) pass chooser.set_hurry_up(sys.maxsize // 2) me.stop() logger.close() if state_file : server.lock.acquire() tzlib.pickle_file(state_file, chooser, tzlib.BEST_PICKLE_PROTOCOL) server.lock.release() pass # # # # eof