shopr

Check-in [9a6f41336a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Keep old state of R/G/Y toggles. Wake and refresh XHR once/minute to keep NAT alive.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:9a6f41336a2477a8fd189a383375b7dc2504e0eb36821f08d57fb9c9fd97cccf
User & Date: vandys 2019-07-13 16:35:16
Context
2019-07-24
16:49
When returning to not being filtered by typing, go back to honoring the RGY selector buttons at the top. check-in: f120275739 user: vandys tags: trunk
2019-07-13
16:35
Keep old state of R/G/Y toggles. Wake and refresh XHR once/minute to keep NAT alive. check-in: 9a6f41336a user: vandys tags: trunk
2019-06-27
17:20
Add some logging check-in: 4a0c02edd2 user: vandys tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to get.py.

78
79
80
81
82
83
84






85
86
	try:
	    gen = int(self.vals["gen"])
	except:
	    gen = -1
	if l.gen == gen:
	    l.await_gen()







	# How the list looks now
	return True,self.send_json(l.for_json())







>
>
>
>
>
>


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
	try:
	    gen = int(self.vals["gen"])
	except:
	    gen = -1
	if l.gen == gen:
	    l.await_gen()

	# Timeout
	if l.gen == gen:
	    # Keep this small, since all they need to do
	    #  is post a new request.
	    return True,self.send_json({"gen": gen})

	# How the list looks now
	return True,self.send_json(l.for_json())

Changes to items.py.

1
2
3
4
5
6
7
8




9
10
11
12
13
14
15
..
58
59
60
61
62
63
64
65

66
67
68
69
70
71
72
73
74
75





















76
77
78
79
80
81
82
#
# items.py
#	Represent the items on a shopping list
#
import json, threading, time
import sqlite3
import pdb






class List(object):
    def __init__(self, approot, lid, isglob, name):
	self.approot = approot
	self.lid = lid
	self.isglob = isglob
	self.name = name
................................................................................
    def next_gen(self):
	self.gen += 1
	res = self.gen

	# Release anybody waiting for new changes
	waiters = frozenset(self.waiters)
	self.waiters.clear()
	for sema in waiters:

	    sema.release()

	return res

    # Wait for new change
    def await_gen(self):
	sema = threading.Semaphore(0)
	self.waiters.add(sema)
	sema.acquire()
	# Resume here after next_gen()






















    # A JSON wrap-up of our content
    def for_json(self):
	items = []
	res = {"name": self.name, "items": items, "gen": self.gen}
	for i in sorted(self.items.itervalues(),
		key=lambda _i: _i.name.lower()):




|



>
>
>
>







 







|
>







|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#
# items.py
#	Represent the items on a shopping list
#
import sys, json, threading, time
import sqlite3
import pdb


# How many seconds before we wake a long poll back up to
#  freshen NAT
IDLETM = 60.0

class List(object):
    def __init__(self, approot, lid, isglob, name):
	self.approot = approot
	self.lid = lid
	self.isglob = isglob
	self.name = name
................................................................................
    def next_gen(self):
	self.gen += 1
	res = self.gen

	# Release anybody waiting for new changes
	waiters = frozenset(self.waiters)
	self.waiters.clear()
	for tup in waiters:
	    sema = tup[0]
	    sema.release()

	return res

    # Wait for new change
    def await_gen(self):
	sema = threading.Semaphore(0)
	self.waiters.add( (sema, time.time()) )
	sema.acquire()
	# Resume here after next_gen()

    # See if anybody should wake up just to freshen their NAT state
    def timeouts(self):

	# Gather any timeouts
	done = set()
	tm = time.time() - IDLETM
	for tup in self.waiters:
	    if tup[1] < tm:
		done.add(tup)

	# No timeouts, back to sleep
	if not done:
	    return

	# Wake them up
	sys.stderr.write("Timeout on %s: %s\n" % (self.name, done))
	for tup in done:
	    self.waiters.remove(tup)
	for tup in done:
	    tup[0].release()

    # A JSON wrap-up of our content
    def for_json(self):
	items = []
	res = {"name": self.name, "items": items, "gen": self.gen}
	for i in sorted(self.items.itervalues(),
		key=lambda _i: _i.name.lower()):

Changes to js/shopr.js.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
..
35
36
37
38
39
40
41

42
43
44

45
46
47
48
49
50

51
52
53

54
55
56
57
58
59

60
61
62

63
64
65
66
67
68
69
...
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

283
284
285
286
287
288
289
...
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
...
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
...
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
...
641
642
643
644
645
646
647


















648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
// List of elems as last delivered by the server
// {name: "listname", items: [item1, ...], gen: <int>}
//  item: {name: "item name", idx: <int>, iid: <int>}
var cur_names = null, show_names = null;

// Set of indices currently being displayed
// Initial state, show all
const cur_idxs = new Set([0, 1, 2]);

// Map from the Element ID to its Item ID in the DB
const iids = {};

// When itname is naming an item to edit, this is its Item ID
var itname_iid = null;

................................................................................
//
// Each toggles between darker version of color (showing it)
//  and its lighter counterpart.
//
function filtR() {
    if (cur_idxs.delete(0)) {
	spanR.style.background = "pink";

    } else {
	cur_idxs.add(0);
	spanR.style.background = "red";

    }
    show_new_list(cur_names);
}
function filtG() {
    if (cur_idxs.delete(1)) {
	spanG.style.background = "PaleGreen";

    } else {
	cur_idxs.add(1);
	spanG.style.background = "green";

    }
    show_new_list(cur_names);
}
function filtY() {
    if (cur_idxs.delete(2)) {
	spanY.style.background = "#F3E5AB";

    } else {
	cur_idxs.add(2);
	spanY.style.background = "gold";

    }
    show_new_list(cur_names);
}
function filtAll() {
    cur_idxs.add(0);
    cur_idxs.add(1);
    cur_idxs.add(2);
................................................................................
    }

    // Show list of items
    setcur(scrlist);

    // Hang a request to hear about subsequent changes
    curgen = ls.gen;
    paint_list();
}

// We're going to display a server's update of our item
//  list.  Paint some color at the change point(s).
function show_list(ls) {
    // Just a timeout, probably to keep NAT happy
    if (ls.gen == curgen) {

	return;
    }

    // If there's no current content, just paint the new stuff now
    if ((cur_names == null) || (cur_names.items.length == 0)) {
	show_new_list(ls);
	return;
................................................................................
	// Didn't spot anything new, so paint now.
	show_new_list(ls);
    }
}

// Display the current list
var paint_req = null;
function paint_list() {
    const req = new XMLHttpRequest();
    req.onreadystatechange = () => {
	if (req.readyState != 4) {
	    return;
	}

	// Pending display op now resolved
................................................................................
}

// Choose a list
function sel_list(e) {
    cur_list = e.currentTarget.textContent;
    localStorage.setItem("curlist", cur_list);
    cur_names = null;
    paint_list();
    return false;
}

// Display choice of lists
//
// scrlists is the outer div, whose display we toggle.
// There's some helpful text, then scrlists2 is the actual
................................................................................
}

// Notice when we're in front of the user or not
function updateVisibility() {
    if (document.hidden == false) {
	// We care about screen updates again
	if (paint_req == null) {
	    paint_list()
	}
    } else {
	// Losing screen, don't worry about list changes
	if (paint_req != null) {
	    paint_req.abort();
	    paint_req = null;
	}
................................................................................
    if (u != null) {
	uname = user.value = u;
    }
    const p = localStorage.getItem("password");
    if (p != null) {
	pw = password.value = p;
    }



















    // Log in?
    if ((u == null) || (p == null)) {
	setcur(scrlogin);
	return;
    }
    calc_authURL();

    // See if we can continue where they left off
    const cl = localStorage.getItem("curlist");
    if (cl != null) {
	cur_list = cl;
	paint_list();
	return;
    }

    // Nope, start them in the list-of-lists
    get_lists();
}







|







 







>



>






>



>






>



>







 







|







>







 







|







 







|







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>












|






18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
..
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
...
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
...
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
...
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
...
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
// List of elems as last delivered by the server
// {name: "listname", items: [item1, ...], gen: <int>}
//  item: {name: "item name", idx: <int>, iid: <int>}
var cur_names = null, show_names = null;

// Set of indices currently being displayed
// Initial state, show all
const cur_idxs = new Set();

// Map from the Element ID to its Item ID in the DB
const iids = {};

// When itname is naming an item to edit, this is its Item ID
var itname_iid = null;

................................................................................
//
// Each toggles between darker version of color (showing it)
//  and its lighter counterpart.
//
function filtR() {
    if (cur_idxs.delete(0)) {
	spanR.style.background = "pink";
	localStorage.setItem("itemsRed", "0");
    } else {
	cur_idxs.add(0);
	spanR.style.background = "red";
	localStorage.setItem("itemsRed", "1");
    }
    show_new_list(cur_names);
}
function filtG() {
    if (cur_idxs.delete(1)) {
	spanG.style.background = "PaleGreen";
	localStorage.setItem("itemsGreen", "0");
    } else {
	cur_idxs.add(1);
	spanG.style.background = "green";
	localStorage.setItem("itemsGreen", "1");
    }
    show_new_list(cur_names);
}
function filtY() {
    if (cur_idxs.delete(2)) {
	spanY.style.background = "#F3E5AB";
	localStorage.setItem("itemsYellow", "0");
    } else {
	cur_idxs.add(2);
	spanY.style.background = "gold";
	localStorage.setItem("itemsYellow", "1");
    }
    show_new_list(cur_names);
}
function filtAll() {
    cur_idxs.add(0);
    cur_idxs.add(1);
    cur_idxs.add(2);
................................................................................
    }

    // Show list of items
    setcur(scrlist);

    // Hang a request to hear about subsequent changes
    curgen = ls.gen;
    request_next();
}

// We're going to display a server's update of our item
//  list.  Paint some color at the change point(s).
function show_list(ls) {
    // Just a timeout, probably to keep NAT happy
    if (ls.gen == curgen) {
	request_next();
	return;
    }

    // If there's no current content, just paint the new stuff now
    if ((cur_names == null) || (cur_names.items.length == 0)) {
	show_new_list(ls);
	return;
................................................................................
	// Didn't spot anything new, so paint now.
	show_new_list(ls);
    }
}

// Display the current list
var paint_req = null;
function request_next() {
    const req = new XMLHttpRequest();
    req.onreadystatechange = () => {
	if (req.readyState != 4) {
	    return;
	}

	// Pending display op now resolved
................................................................................
}

// Choose a list
function sel_list(e) {
    cur_list = e.currentTarget.textContent;
    localStorage.setItem("curlist", cur_list);
    cur_names = null;
    request_next();
    return false;
}

// Display choice of lists
//
// scrlists is the outer div, whose display we toggle.
// There's some helpful text, then scrlists2 is the actual
................................................................................
}

// Notice when we're in front of the user or not
function updateVisibility() {
    if (document.hidden == false) {
	// We care about screen updates again
	if (paint_req == null) {
	    request_next()
	}
    } else {
	// Losing screen, don't worry about list changes
	if (paint_req != null) {
	    paint_req.abort();
	    paint_req = null;
	}
................................................................................
    if (u != null) {
	uname = user.value = u;
    }
    const p = localStorage.getItem("password");
    if (p != null) {
	pw = password.value = p;
    }
    const ir = localStorage.getItem("itemsRed");
    if ((ir == null) || (ir == "1")) {
	cur_idxs.add(0);
    } else {
	spanR.style.background = "pink";
    }
    const ig = localStorage.getItem("itemsGreen");
    if ((ig == null) || (ig == "1")) {
	cur_idxs.add(1);
    } else {
	spanG.style.background = "PaleGreen";
    }
    const iy = localStorage.getItem("itemsYellow");
    if ((iy == null) || (iy == "1")) {
	cur_idxs.add(2);
    } else {
	spanY.style.background = "#F3E5AB";
    }

    // Log in?
    if ((u == null) || (p == null)) {
	setcur(scrlogin);
	return;
    }
    calc_authURL();

    // See if we can continue where they left off
    const cl = localStorage.getItem("curlist");
    if (cl != null) {
	cur_list = cl;
	request_next();
	return;
    }

    // Nope, start them in the list-of-lists
    get_lists();
}

Changes to main.py.

1
2
3
4
5
6
7
8
9
10
11
12
...
117
118
119
120
121
122
123









124
125
126
127
128
129
130
...
131
132
133
134
135
136
137
138
139
#
# main.py
#	Main driver for list/swiping service
#
import sys
import sqlite3
import chore
from get import GET_mixin
from post import POST_mixin
from put import PUT_mixin
import items
import pdb
................................................................................
	tup = c.fetchone()
	if tup is None:
	    return None
	lid = tup[0]
	res = self.lists[name] = \
	    items.List(self, lid, True, name)
	return res










if __name__ == "__main__":
    if len(sys.argv) != 2:
	sys.stderr.write("Usage is: %s <config>\n" %
	    (sys.argv[0],))
	sys.exit(1)

................................................................................
    # Create the server with config
    cfg = load_cfg(sys.argv[1])
    app = App(cfg)

    # It's an HTTP service
    app.start_http()

    # HTTP threads remain
    sys.exit(0)




|







 







>
>
>
>
>
>
>
>
>







 







|
|
1
2
3
4
5
6
7
8
9
10
11
12
...
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
...
140
141
142
143
144
145
146
147
148
#
# main.py
#	Main driver for list/swiping service
#
import sys, time
import sqlite3
import chore
from get import GET_mixin
from post import POST_mixin
from put import PUT_mixin
import items
import pdb
................................................................................
	tup = c.fetchone()
	if tup is None:
	    return None
	lid = tup[0]
	res = self.lists[name] = \
	    items.List(self, lid, True, name)
	return res

    # Does not return; implements a timeout service
    def run_timeouts(self):
	while True:
	    time.sleep(items.IDLETM / 4)
	    for l in self.lists.itervalues():
		if not l.waiters:
		    continue
		l.timeouts()

if __name__ == "__main__":
    if len(sys.argv) != 2:
	sys.stderr.write("Usage is: %s <config>\n" %
	    (sys.argv[0],))
	sys.exit(1)

................................................................................
    # Create the server with config
    cfg = load_cfg(sys.argv[1])
    app = App(cfg)

    # It's an HTTP service
    app.start_http()

    # Use this main thread as a Timeout service
    app.run_timeouts()