Unnamed Fossil Project

Check-in [5a4ba6c3f5]
Login

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

Overview
Comment:Implement item deletion. Use dict for List's storage of Item's, since we often look up by Item id.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:5a4ba6c3f50a13e75ea6a4fdfade146d0d9c667716eca1d3728e749ccdcf5e98
User & Date: vandys 2019-06-23 22:24:03
Context
2019-06-23
22:32
Get "+RGY" (show all colors) working. check-in: 84801ee99c user: vandys tags: trunk
22:24
Implement item deletion. Use dict for List's storage of Item's, since we often look up by Item id. check-in: 5a4ba6c3f5 user: vandys tags: trunk
21:44
Ignore certs, if present check-in: c86da22d43 user: vandys tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to items.py.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
61
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
...
109
110
111
112
113
114
115
116
117
118






















119
120
121
122
123
124
125

class List(object):
    def __init__(self, approot, lid, isglob, name):
	self.approot = approot
	self.lid = lid
	self.isglob = isglob
	self.name = name
	self.items = []
	self.gen = 0
	self.waiters = set()

	# Load any existing state
	db = self.get_db()
	c = db.cursor()
	c.execute("select iid,name,idx from items where list=?", (lid,))
................................................................................
	    iid,iname,idx = tup

	    # Deleted items shouldn't be in the DB
	    assert idx >= 0

	    # New item, with the saved generation value
	    i = Item(self, iid, idx, iname)
	    self.items.append(i)

	c.close()
	db.close()

    # Return a DB connection
    def get_db(self):
	return sqlite3.connect(self.approot.dbname)
................................................................................
	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, key=lambda _i: _i.name):
	    # Don't share deleted
	    if i.deleted():
		continue
	    items.append(i.for_json())
	return res

    # Update idx for the given item
    def set_idx(self, iid, col):

	# Find this item ID
	for l in self.items:
	    if l.iid == iid:
		break
	else:
	    return False

	# Update item, which flags change to list
	l.assign(col)

	return True

    # Add an item to this list
    #
    # Return None on failure, else new Item
    def new_item(self, name):
	# No dups, please
	ln = name.lower()
	for i in self.items:
	    if i.name.lower() == ln:
		return None

	# Put in DB
	db = self.get_db()
	c = db.cursor()
	c.execute("insert or replace into items values(NULL,?,?,?)",
................................................................................
	c.close()
	db.close()

	# Create data locally
	i = Item(self, iid, 0, name)

	# Ok
	self.items.append(i)
	self.next_gen()
	return i























class Item(object):
    def __init__(self, l, iid, idx, name):

	# Our parent list
	self.parent = l








|







 







|







 







|








>

|
<
|
<













|







 







|


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







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
61
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
...
108
109
110
111
112
113
114
115
116
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

class List(object):
    def __init__(self, approot, lid, isglob, name):
	self.approot = approot
	self.lid = lid
	self.isglob = isglob
	self.name = name
	self.items = {}
	self.gen = 0
	self.waiters = set()

	# Load any existing state
	db = self.get_db()
	c = db.cursor()
	c.execute("select iid,name,idx from items where list=?", (lid,))
................................................................................
	    iid,iname,idx = tup

	    # Deleted items shouldn't be in the DB
	    assert idx >= 0

	    # New item, with the saved generation value
	    i = Item(self, iid, idx, iname)
	    self.items[iid] = i

	c.close()
	db.close()

    # Return a DB connection
    def get_db(self):
	return sqlite3.connect(self.approot.dbname)
................................................................................
	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):
	    # Don't share deleted
	    if i.deleted():
		continue
	    items.append(i.for_json())
	return res

    # Update idx for the given item
    def set_idx(self, iid, col):

	# Find this item ID
	l = self.items.get(iid)

	if l is None:

	    return False

	# Update item, which flags change to list
	l.assign(col)

	return True

    # Add an item to this list
    #
    # Return None on failure, else new Item
    def new_item(self, name):
	# No dups, please
	ln = name.lower()
	for i in self.items.itervalues():
	    if i.name.lower() == ln:
		return None

	# Put in DB
	db = self.get_db()
	c = db.cursor()
	c.execute("insert or replace into items values(NULL,?,?,?)",
................................................................................
	c.close()
	db.close()

	# Create data locally
	i = Item(self, iid, 0, name)

	# Ok
	self.items[iid] = i
	self.next_gen()
	return i

    # Remove this item
    def del_item(self, iid):
	# Get item
	i = self.items.get(iid)
	if i is None:
	    return False

	# Leave DB
	db = self.get_db()
	c = db.cursor()
	c.execute("delete from items where list=? and iid=?",
	    (self.lid, iid))
	db.commit()
	c.close()
	db.close()

	# Remove local data structure
	del self.items[iid]
	self.next_gen()

	return True

class Item(object):
    def __init__(self, l, iid, idx, name):

	# Our parent list
	self.parent = l

Changes to js/shopr.js.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
277
278
279
280
281
282
283
284
285
286

287
288
289
290
291
292
293
...
300
301
302
303
304
305
306

307
308
309
310
311
312
313
314

315
316
317
318
319
320
321
322









323
324
325
326
327
328
329
...
454
455
456
457
458
459
460






























461
462
463
464
465
466
467
var cur_list = null;

// Current generation of cur_list we hold
var curgen = null;

// List of elems as last delivered by the server
// {name: "listname", items: [item1, ...], gen: <int>}
//  item: {name: "item name", idx: <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
................................................................................
    }

    // Walk the old and new in parallel, noting new/changed/deleted
    //  as we progress, and placing a visual cue as appropriate.
    // i1 walks the new/latest values, i2 points at our
    //  older counterpart as currently displayed.
    let changed = false;
    let oidx = 0;
    let i2 = null, e2 = null;
    for (let i1 of ls.items) {


	// Adding beyond last, so make bottom
	//  of last elem a green bar
	if (oidx == cur_names.items.length) {
	    i2 = cur_names.items[oidx-1];
	    e2 = i2.elems[i2.idx];
	    e2.style.background = "green";
................................................................................
	e2 = i2.elems[i2.idx];
	if (i1.name == i2.name) {
	    // Changing idx/color
	    if (i1.idx != i2.idx) {
		e2.style.background = "white";
		changed = true;
	    }

	    oidx += 1;
	    continue;
	}

	// Inserted cell?
	if (i1.name < i2.name) {
	    e2.style.background = "green";
	    changed = true;

	    continue;
	}

	// Deleted cell
	e2.style.background = "red";
	oidx += 1;
	changed = true;
    }










    // Let our highlights show for a bit, then switch
    //  to the new list.
    if (changed) {
	setTimeout( () => { show_new_list(ls); }, 1000 );
    } else {
	// Didn't spot anything new, so paint now.
................................................................................
	".json" + authURL;

    // Requesting URL, including authentication
    req.open("POST", url);
    const op = {"op": "add", "name": ifv};
    req.send(JSON.stringify(op));
}































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







|







 







|
|
|
>







 







>








>








>
>
>
>
>
>
>
>
>







 







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







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
...
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
...
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
var cur_list = null;

// Current generation of cur_list we hold
var curgen = null;

// 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
................................................................................
    }

    // Walk the old and new in parallel, noting new/changed/deleted
    //  as we progress, and placing a visual cue as appropriate.
    // i1 walks the new/latest values, i2 points at our
    //  older counterpart as currently displayed.
    let changed = false;
    let oidx = 0, nidx = 0;
    let i1 = null, i2 = null, e2 = null;
    while (nidx < ls.items.length) {
	i1 = ls.items[nidx];

	// Adding beyond last, so make bottom
	//  of last elem a green bar
	if (oidx == cur_names.items.length) {
	    i2 = cur_names.items[oidx-1];
	    e2 = i2.elems[i2.idx];
	    e2.style.background = "green";
................................................................................
	e2 = i2.elems[i2.idx];
	if (i1.name == i2.name) {
	    // Changing idx/color
	    if (i1.idx != i2.idx) {
		e2.style.background = "white";
		changed = true;
	    }
	    nidx += 1;
	    oidx += 1;
	    continue;
	}

	// Inserted cell?
	if (i1.name < i2.name) {
	    e2.style.background = "green";
	    changed = true;
	    nidx += 1;
	    continue;
	}

	// Deleted cell
	e2.style.background = "red";
	oidx += 1;
	changed = true;
    }

    // If old has stuff past end of new, flag tail deleted
    while (oidx < cur_names.items.length) {
	i2 = cur_names.items[oidx];
	e2 = i2.elems[i2.idx];
	e2.style.background = "red";
	oidx += 1;
	changed = true;
    }

    // Let our highlights show for a bit, then switch
    //  to the new list.
    if (changed) {
	setTimeout( () => { show_new_list(ls); }, 1000 );
    } else {
	// Didn't spot anything new, so paint now.
................................................................................
	".json" + authURL;

    // Requesting URL, including authentication
    req.open("POST", url);
    const op = {"op": "add", "name": ifv};
    req.send(JSON.stringify(op));
}

// Remove an item
//
// It's the one which was long held to get this menu
function del_iname() {
    // Name to iid
    let iid = null;
    for (let i of cur_names.items) {
	if (i.name == itname.value) {
	    iid = i.iid;
	}
    }
    if (iid == null) {
	// WTF?
	return;
    }

    // Construct URL; our list, authen
    let url = "/l/" + encodeURIComponent(cur_list) +
	".json" + authURL;

    // PUT w. authen, op == "delete this iid"
    const req = new XMLHttpRequest();
    req.open("PUT", url);
    const op = {"op": "del", "iid": iid};
    req.send(JSON.stringify(op));

    // Leave menu for item (which should be Going Away)
    cancel_scritem();
}

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

Changes to put.py.

38
39
40
41
42
43
44


45
46
47
48
49
50
51
52






53
54
55
56
57
	# Decode op
	try:
	    d = json.loads(buf)
	    op = str(d["op"])
	    if op == "idx":
		iid = int(d["iid"])
		col = int(d["col"])


	    else:
		return True,self.send_error(400, "No such op")
	except:
	    return True,self.send_error(400, "Malformed JSON")

	# Update item, and churn the list
	if op == "idx":
	    if l.set_idx(iid, col):






		return True,self.send_json({"ok": True})
	    return True,self.send_error(400, "Bad item")

	# "Can't Happen"
	return True,self.send_error(500)







>
>








>
>
>
>
>
>





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
	# Decode op
	try:
	    d = json.loads(buf)
	    op = str(d["op"])
	    if op == "idx":
		iid = int(d["iid"])
		col = int(d["col"])
	    elif op == "del":
		iid = int(d["iid"])
	    else:
		return True,self.send_error(400, "No such op")
	except:
	    return True,self.send_error(400, "Malformed JSON")

	# Update item, and churn the list
	if op == "idx":
	    if l.set_idx(iid, col):
		return True,self.send_json({"ok": True})
	    return True,self.send_error(400, "Bad item")

	# Delete item from this list
	if op == "del":
	    if l.del_item(iid):
		return True,self.send_json({"ok": True})
	    return True,self.send_error(400, "Bad item")

	# "Can't Happen"
	return True,self.send_error(500)