diff --git a/out/static/style.css b/out/static/style.css
index 74e5f91..357f551 100644
--- a/out/static/style.css
+++ b/out/static/style.css
@@ -128,6 +128,12 @@ footer .links {
.pagination a { margin: .5em; padding: .2em; }
.pagination a.active { border: 1pt solid black; border-radius: .2em; }
+/* Lists */
+.title-sub h4, h2.title { margin-bottom: 0; }
+.title-sub span, p.subtitle { margin-top: .2em; }
+.title-sub p { margin: .2em .5em 1.5em; font-style: italic; }
+.title-sub span, .found-in span, .snd { color: #586472; font-size: .85em; }
+
#categories h3 { margin-top: 2em; }
@@ -136,7 +142,6 @@ footer .links {
#dom-top10>div { margin: .4em; }
#dom-top10 a, #dom-toc a { word-wrap: break-word; }
#dom-toc span { display: table; }
-.found-in span, .snd { color: #586472; font-size: .85em; }
.fillbar {
display: block;
background: #DDD;
@@ -159,8 +164,6 @@ footer .links {
/* app bundle */
.squeeze { max-width: 700px; }
-h2.title { margin-bottom: 0; }
-p.subtitle { margin-top: .2em; }
.mg_lr { margin: 0 .4em; }
.mg_top { margin-top: 2em; }
.right { text-align: right; }
@@ -172,9 +175,11 @@ p.subtitle { margin-top: .2em; }
.xscroll { overflow-x: scroll; max-width: 100%; }
.yscroll { overflow-y: scroll; max-height: 80vh; }
.floatr, #main-nav { float: right; }
+table { border-collapse: collapse; }
table.alternate td, table.alternate th { padding: .5em; }
table.alternate tr:nth-child(even) { background: #DDD; }
table.alternate tr:nth-child(odd) { background: #F9F9F9; }
+table.stick-first td:nth-child(1) { position: sticky; left: 0; background: inherit; }
/*#meta { margin-bottom: 2em; }*/
#meta .icons { margin-bottom: 2em; }
@@ -282,14 +287,16 @@ p.trckr { font-size: .9em; margin-left: .5em; }
#rank-list th:not(:first-child) { min-width: 12ex; }
#rank-list img { width: 25px; height: 25px; vertical-align: bottom; }
-#rank-list td { text-align: center; min-width: max-content; }
-#rank-list td:nth-child(1) {
- position: sticky; left: 0;
- background: inherit;
- text-align: right;
-}
+#rank-list td { text-align: center; }
+#rank-list td:nth-child(1) { text-align: right; white-space: nowrap; }
#rank-list td:nth-child(2) { text-align: left; }
+/* rank-cluster */
+table.cluster tr { font-size: 0.8em; }
+table.cluster td { padding: 0.75em; }
+table.cluster .sep { border-left: 1pt solid #000; }
+table.cluster td:not(:first-child) { text-align: center; }
+
/* responsive */
@media(max-width: 900px) {
#stats { grid-template-columns: repeat(2, max-content); }
diff --git a/src/README.md b/src/README.md
index d7c706c..0b26357 100644
--- a/src/README.md
+++ b/src/README.md
@@ -40,11 +40,11 @@ Given A → B, B depends on A
```
digraph Dependency {
+ "." -> download_tracker
"." -> download_itunes
"." -> bundle_combine
download_itunes -> index_app_names
index_app_names -> index_categories
- index_app_names -> index_rank
index_app_names -> html_index_domains
index_categories -> html_categories
index_categories -> html_index_apps
@@ -55,7 +55,8 @@ digraph Dependency {
html_index_apps -> html_ranking
html_categories -> html_ranking
html_index_domains -> html_root
- "." -> download_tracker
+ index_categories -> index_rank
+ index_rank -> html_group_compare
}
```
[graphviz](http://www.webgraphviz.com/)
\ No newline at end of file
diff --git a/src/html_bundle.py b/src/html_bundle.py
index 7386b82..13cb7bf 100755
--- a/src/html_bundle.py
+++ b/src/html_bundle.py
@@ -47,21 +47,6 @@ def stat(col, title, ident, value, optional=None):
def gen_page(bundle_id, obj):
- def round_num(num):
- return format(num, '.1f') # .rstrip('0').rstrip('.')
-
- def as_pm(value):
- return round_num(value) + '/min'
-
- def as_percent(value):
- return round_num(value * 100) + '%'
-
- def seconds_to_time(seconds):
- seconds = int(seconds)
- minutes, seconds = divmod(seconds, 60)
- hours, minutes = divmod(minutes, 60)
- return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds)
-
name = index_app_names.get_name(bundle_id)
gernes = index_categories.get_categories(bundle_id)
obj['tracker'] = list(filter(lambda x: x[2], obj['subdom']))
@@ -83,13 +68,13 @@ def gen_page(bundle_id, obj):
{ stat(1, 'Number of recordings:', 'sum_rec', obj['sum_rec']) }
- { stat(1, 'Average recording time:', 'avg_time', seconds_to_time(obj['avg_time'])) }
- { stat(2, 'Cumulative recording time:', 'sum_time', seconds_to_time(obj['sum_time'])) }
- { stat(1, 'Average number of requests:', 'avg_logs_pm', round_num(obj['avg_logs']), as_pm(obj['avg_logs_pm'])) }
- { stat(2, 'Total number of requests:', 'sum_logs_pm', str(obj['sum_logs']), as_pm(obj['sum_logs_pm'])) }
+ { stat(1, 'Average recording time:', 'avg_time', HTML.seconds_to_time(obj['avg_time'])) }
+ { stat(2, 'Cumulative recording time:', 'sum_time', HTML.seconds_to_time(obj['sum_time'])) }
+ { stat(1, 'Average number of requests:', 'avg_logs_pm', HTML.fmt_round_num(obj['avg_logs']), HTML.fmt_as_pm(obj['avg_logs_pm'])) }
+ { stat(2, 'Total number of requests:', 'sum_logs_pm', str(obj['sum_logs']), HTML.fmt_as_pm(obj['sum_logs_pm'])) }
{ stat(1, 'Number of domains:', 'pardom', len(obj['pardom'])) }
{ stat(2, 'Number of subdomains:', 'subdom', len(obj['subdom'])) }
- { stat(3, 'Tracker percentage:', 'tracker_percent', as_percent(obj['tracker_percent'])) }
+ { stat(3, 'Tracker percentage:', 'tracker_percent', HTML.fmt_as_percent(obj['tracker_percent'])) }
diff --git a/src/html_categories.py b/src/html_categories.py
index aa55776..762bac3 100755
--- a/src/html_categories.py
+++ b/src/html_categories.py
@@ -2,6 +2,7 @@
import lib_common as mylib
import lib_html as HTML
+import html_group_compare
def write_overview_page(base_dir, category_tuples, title):
@@ -12,7 +13,8 @@ def write_overview_page(base_dir, category_tuples, title):
cluster[i].append(x)
except KeyError:
cluster[i] = [x]
- src = '
{} '.format(title)
+ src = HTML.h2_path_n_rank(title, [], 'compare/', 'Compare')
+ src += '
'
for i, arr in sorted(cluster.items()):
mylib.sort_by_name(arr, 1)
kind = 'Apps' if i == 6 else 'Games' if i == 7 else 'Other'
@@ -21,6 +23,12 @@ def write_overview_page(base_dir, category_tuples, title):
src += ''.join([HTML.a_category(*x) for x in arr]) + '
'
HTML.write(base_dir, src + '
', title)
+ # make groupby compare html
+ base_dir = mylib.path_add(base_dir, 'compare')
+ html_group_compare.write_groupby_multi(
+ base_dir, [('/category', 'All Categories')], 'Compare', 'Categories',
+ [('category-6', 'Category: Apps'), ('category-7', 'Category: Games')])
+
def process(affected=None, per_page=60):
print('generating html: category-index ...')
@@ -34,7 +42,7 @@ def process(affected=None, per_page=60):
continue
out_dir = mylib.path_add(base, cid)
# full url since categories can have page 2, 3, etc.
- A = HTML.h2_path_n_rank(cname, [(parent, '/category/')], 'ranking/')
+ A = HTML.h2_path_n_rank(cname, [('/category/', parent)], 'ranking/')
Z = HTML.p_download_json('data.json', 'category-{}.json'.format(cid))
_, a = HTML.write_app_pages(out_dir, json['apps'],
cname, per_page, pre=A, post=Z)
diff --git a/src/html_group_compare.py b/src/html_group_compare.py
new file mode 100755
index 0000000..05c0872
--- /dev/null
+++ b/src/html_group_compare.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+
+import lib_common as mylib
+import lib_html as HTML
+import index_rank # fname_rank_list
+
+
+def make_groupby_table_html(json, struct):
+ sep = ' class="sep"'
+ par_tr = ''
+ sub_tr = ''
+ for par_col, cols in struct:
+ par_tr += f'
{par_col} '
+ for i, x in enumerate(cols):
+ sub_tr += '
{} '.format(sep if i == 0 else '', x[0])
+ src = f'''
+
\n'
+
+
+def prefill_groupby_table(cluster_file):
+ return make_groupby_table_html(mylib.json_read(cluster_file), [
+ ['Apps', [
+ (' ', 1, int)]],
+ ['Number of Recordings', [
+ ('avg', 2, HTML.fmt_round_num),
+ ('total', 3, int)]],
+ ['Number of Requests', [
+ ('avg', 4, HTML.fmt_round_num),
+ ('total', 5, int)]],
+ ['Recording Time', [
+ ('avg', 6, HTML.seconds_to_time),
+ ('total', 7, HTML.seconds_to_time)]],
+ ['Requests per Minute', [
+ ('min', 9, HTML.fmt_as_pm),
+ ('avg', 8, HTML.fmt_as_pm),
+ ('max', 10, HTML.fmt_as_pm)]],
+ ['Number of Domains', [
+ ('min', 12, int),
+ ('avg', 11, HTML.fmt_round_num),
+ ('max', 13, int)]],
+ ['Number of Subdomains', [
+ ('min', 15, int),
+ ('avg', 14, HTML.fmt_round_num),
+ ('max', 16, int)]],
+ ['Tracker Percentage', [
+ ('min', 18, HTML.fmt_as_percent),
+ ('avg', 17, HTML.fmt_as_percent),
+ ('max', 19, HTML.fmt_as_percent)]],
+ ])
+
+
+def gen_and_link_groupby(cluster_id, base, raw_fname='data'):
+ json_file = index_rank.fname_rank_list('groupby', cluster_id)
+ raw_fname += '.json'
+ mylib.symlink(json_file, mylib.path_add(base, raw_fname))
+ named_download = 'compare-{}.json'.format(cluster_id)
+ return prefill_groupby_table(json_file) + HTML.p_download_json(
+ raw_fname, named_download)
+
+
+def write_groupby_multi(base_dir, h2_paths, h2_title, page_title, groups_arr):
+ mylib.mkdir(base_dir)
+ src = '
{} '.format(HTML.a_path(h2_paths, h2_title))
+ for gid, name in groups_arr:
+ raw_name = gid if len(groups_arr) > 1 else 'data'
+ src += f'\n
{name} '
+ src += gen_and_link_groupby(gid, base_dir, raw_name)
+ HTML.write(base_dir, src, title='Compare: ' + page_title)
+
+
+def write_groupby_single(base_dir, gid, name, parent):
+ base = mylib.path_add(base_dir, gid)
+ write_groupby_multi(base,
+ [('/results/', 'Results'), ('/compare/', parent)],
+ name, name, [(gid, '')])
+
+
+def process():
+ print('generating html: group compare ...')
+ print(' lists')
+ base_custom = mylib.path_out('compare')
+ title_custom = 'Compare'
+ arr = []
+ for list_id, json in mylib.enum_custom_lists('groupby_'):
+ try:
+ if json['hidden']:
+ continue
+ except KeyError:
+ pass
+ list_name = json['name']
+ print(' ' + list_name)
+ grp_count = len(json['groups'])
+ app_count = sum([len(x['apps']) for x in json['groups']])
+ try:
+ desc = json['desc']
+ except KeyError:
+ desc = None
+ arr.append((list_id, list_name, grp_count, app_count, desc))
+ write_groupby_single(base_custom, list_id, list_name, title_custom)
+
+ print(' index page')
+ mylib.sort_by_name(arr, 1)
+ src = f'''
+
{HTML.a_path([('/results/', 'Results')], title_custom)}
+
+ Comparison groups do not compare a single application against another.
+ Instead comparison groups cluster multiple applications into a single, logical group.
+ For example, paid-vs-free apps could be a comparison group.
+
+
'''
+ for x in arr:
+ src += HTML.h4_a_title_sub_desc(
+ x[0], x[1], f'has {x[2]} groups with {x[3]} apps', x[4])
+ HTML.write(base_custom, src + '
', title=title_custom)
+ print('')
+
+
+if __name__ == '__main__':
+ process()
diff --git a/src/html_index_apps.py b/src/html_index_apps.py
index c63dab6..b2035ff 100755
--- a/src/html_index_apps.py
+++ b/src/html_index_apps.py
@@ -8,7 +8,7 @@ import index_app_names # get_sorted_app_names
def process(per_page=60):
print('generating html: app-index ...')
title = 'Apps (A–Z)'
- header = HTML.h2_path_n_rank(title, [('Results', '/results/')], 'ranking/')
+ header = HTML.h2_path_n_rank(title, [('/results/', 'Results')], 'ranking/')
p, a = HTML.write_app_pages(mylib.path_out('index', 'apps'),
index_app_names.get_sorted_app_names(),
title, per_page=per_page, pre=header)
diff --git a/src/html_index_domains.py b/src/html_index_domains.py
index df80898..82a37f3 100755
--- a/src/html_index_domains.py
+++ b/src/html_index_domains.py
@@ -70,7 +70,7 @@ def gen_html_trinity(idx_dir, app_count, json, title, symlink):
def write_index(fname, title, button):
HTML.write(idx_dir, '
{} {}{}'.format(
- HTML.a_path([('Results', '/results/')], title),
+ HTML.a_path([('/results/', 'Results')], title),
dropdown_choose(button), duo_list(list1, list2)
), title=title, fname=fname)
@@ -91,7 +91,7 @@ def gen_html_trinity(idx_dir, app_count, json, title, symlink):
def gen_lookup(html_dir, doms_dict, flag, title):
HTML.write(html_dir, f'''
-
{ HTML.a_path([('All Domains', '/index/domains/all/')],
+{ HTML.a_path([('/index/domains/all/', 'All Domains')],
' ') }
Known Tracker: ?
Present in: … applications
diff --git a/src/html_ranking.py b/src/html_ranking.py
index cdcffec..4156ee9 100755
--- a/src/html_ranking.py
+++ b/src/html_ranking.py
@@ -20,7 +20,7 @@ def html_default_description():
def html_table():
return '''
'''
@@ -35,8 +35,8 @@ def html_script_chunk(fname, sort_col, sort_order):
def write_ranking_all(title, base_dir):
# full urls since app index can have page 2, 3, etc.
- src = html_h2_path([('Results', '/results/'),
- ('Apps (A–Z)', '/index/apps/')])
+ src = html_h2_path([('/results/', 'Results'),
+ ('/index/apps/', 'Apps (A–Z)')])
src += html_default_description()
src += html_table()
src += HTML.p_download_json('data.json', 'raw-apps.json')
@@ -49,8 +49,8 @@ def write_ranking_all(title, base_dir):
def write_ranking_category(cid, category_name):
base = mylib.path_out('category', cid, 'ranking')
# full urls since categories can have page 2, 3, etc.
- src = html_h2_path([('All Categories', '/category/'),
- (category_name, '/category/{}/'.format(cid))])
+ src = html_h2_path([('/category/', 'All Categories'),
+ ('/category/{}/'.format(cid), category_name)])
src += html_default_description()
src += html_table()
src += HTML.p_download_json('data.json',
@@ -63,8 +63,8 @@ def write_ranking_category(cid, category_name):
def write_ranking_custom_lists(base_dir, list_id, list_name, parent_title):
base = mylib.path_add(base_dir, list_id)
- src = html_h2_path([('Results', '/results/'),
- (parent_title, '/lists/')], list_name)
+ src = html_h2_path([('/results/', 'Results'),
+ ('/lists/', parent_title)], list_name)
src += html_table()
src += HTML.p_download_json('data.json',
'raw-list-{}.json'.format(list_id))
@@ -88,22 +88,28 @@ def process():
base_custom = mylib.path_out('lists')
title_custom = 'Lists'
arr = []
- for list_id, json in mylib.enum_custom_lists():
+ for list_id, json in mylib.enum_custom_lists('list_'):
list_name = json['name']
- arr.append((list_id, list_name, len(json['apps'])))
+ try:
+ desc = json['desc']
+ except KeyError:
+ desc = None
+ arr.append((list_id, list_name, len(json['apps']), desc))
write_ranking_custom_lists(base_custom, list_id, list_name,
parent_title=title_custom)
print(' index page')
mylib.sort_by_name(arr, 1)
- src = html_h2_path([('Results', '/results/')], title_custom)
+ src = html_h2_path([('/results/', 'Results')], title_custom)
src += '''
- We present selected lists of apps that have been added to AppCheck.
+ App lists compare individual applications against each other.
+ These curated lists group together, at least in some aspect, similar applications.
+ This could be all instant messengers, health related apps, or apps from a common developer or country.
'''
for x in arr:
- src += '
{} contains {} apps
\n'.format(*x)
+ src += HTML.h4_a_title_sub_desc(x[0], x[1], f'contains {x[2]} apps', x[3])
HTML.write(base_custom, src + '
', title=title_custom)
print('')
diff --git a/src/html_root.py b/src/html_root.py
index 52cbe30..81c310e 100755
--- a/src/html_root.py
+++ b/src/html_root.py
@@ -92,9 +92,12 @@ def gen_results(base_dir, c_apps, c_domains, title):
List of Categories
List of All Domains ,
only Trackers ,
- or Highly-used Domains which appear in at least 5 apps but are not considered tracker yet .
+ or
Highly-used Domains which appear in at least 5 apps but are not considered tracker
yet .
+
+
-
Or compare similar application via custom comparison Lists .
'''.format(title, c_apps, c_domains, c_recs, c_logs), title=title)
mylib.symlink(index_rank.fname_app_rank(),
mylib.path_add(base_dir, 'rank.json')) # after HTML.write
diff --git a/src/index_categories.py b/src/index_categories.py
index 8a9e221..b7ad461 100755
--- a/src/index_categories.py
+++ b/src/index_categories.py
@@ -4,6 +4,7 @@ import sys
import lib_common as mylib
import download_itunes # get_genres
import index_app_names # get_name
+import index_rank # save_groupby_list
_dict_apps = None
_dict_names = None
@@ -96,9 +97,23 @@ def persist_individual_files():
index = sorted_reverse_index()
make_dir_individuals()
+ cat_groups = {}
for cid, cname in _dict_names.items():
+ # write individual
mylib.json_write(fname_cat_individual(cid),
{'meta': [cid, cname], 'apps': index[cid]})
+ # prep groupby compare
+ obj = {'name': cname, 'apps': [x for x, _ in index[cid]]}
+ cat_g_id = int(int(cid) / 1000)
+ try:
+ cat_groups[cat_g_id].append(obj)
+ except KeyError:
+ cat_groups[cat_g_id] = [obj]
+
+ for key, vals in cat_groups.items():
+ mylib.sort_by_name(vals, 'name')
+ index_rank.save_groupby_list(
+ f'category-{key}', f'Category {key}xxx', vals, hidden=True)
def get_categories(bundle_id):
diff --git a/src/index_rank.py b/src/index_rank.py
index 0299ae9..de9af52 100755
--- a/src/index_rank.py
+++ b/src/index_rank.py
@@ -48,6 +48,39 @@ def json_to_list(json):
]
+def group_multiple_apps(values_array):
+ c = len(values_array)
+ asum = [0] * 10
+ amin = [float('inf')] * 10
+ amax = [0] * 10
+ for values in values_array:
+ for i in range(10):
+ asum[i] += values[i]
+ amin[i] = min(amin[i], values[i])
+ amax[i] = max(amax[i], values[i])
+
+ def flt(val):
+ return int(val * 1000) / 1000
+ return [
+ # app-count
+ c,
+ # rec-count (avg, sum)
+ flt(asum[0] / c), asum[0],
+ # req-count (avg, sum)
+ flt(asum[9] / c), asum[8],
+ # rec-time (avg, sum)
+ int(asum[1] / c), asum[2],
+ # req-pm (avg, min, max)
+ flt(asum[3] / c), flt(amin[3]), flt(amax[3]),
+ # pardom-count (avg, min, max)
+ flt(asum[5] / c), amin[5], amax[5],
+ # subdom-count (avg, min, max)
+ flt(asum[6] / c), amin[6], amax[6],
+ # tracker-percent (avg, min, max)
+ flt(asum[7] / c), flt(amin[7]), flt(amax[7]),
+ ]
+
+
def update_summary_index(index, bundle_ids, deleteOnly=False):
did_change = False
if deleteOnly:
@@ -102,18 +135,45 @@ def write_ranking_category_list(index, affected_ids):
def write_ranking_custom_lists(index, affected_ids):
make_rank_list_dir('custom', reset=affected_ids == ['*'])
- for list_id, json in mylib.enum_custom_lists():
+ for list_id, json in mylib.enum_custom_lists('list_'):
ret = list(filter_by_list(index, json['apps'], affected_ids))
if len(ret) == 0:
continue
mylib.json_write(fname_rank_list('custom', list_id), ret, pretty=False)
+def write_ranking_groupby_lists(index, affected_ids):
+ make_rank_list_dir('groupby', reset=affected_ids == ['*'])
+ for listid, json in mylib.enum_custom_lists('groupby_'):
+ ret = []
+ anyone = affected_ids == ['*']
+ for group in json['groups']:
+ arr = []
+ for x in index:
+ if not anyone and x[0] in affected_ids:
+ anyone = True
+ if x[0] in group['apps']:
+ arr.append(x[2:])
+ if len(arr) > 0:
+ ident = group['name']
+ try:
+ url = group['url']
+ ident = [ident, url]
+ except KeyError:
+ pass
+ ret.append([ident] + group_multiple_apps(arr))
+ if len(ret) == 0 or not anyone:
+ continue
+ mylib.json_write(fname_rank_list('groupby', listid), ret, pretty=False)
+
+
def write_ranking_list(index, affected_ids):
- ret = []
- for bid, values in index.items():
- ret.append([bid, index_app_names.get_name(bid)] + values)
- del(values[8:]) # prepare for write_rank_index
+ # prepend bundle-id and app name
+ ret = [[b, index_app_names.get_name(b)] + v for b, v in index.items()]
+
+ # TODO: return list of updated files
+ print(' write group-by lists')
+ write_ranking_groupby_lists(ret, affected_ids)
print(' write custom lists')
# sort by %-tracker asc, #-pardom asc, avg-req-per-min asc
@@ -166,6 +226,14 @@ def get_total_counts():
return [0, 0]
+def save_groupby_list(gid, title, groups, hidden=False):
+ base = mylib.path_data('_lists')
+ mylib.mkdir(base)
+ fname = mylib.path_add(base, f'groupby_{gid}.json')
+ json = {'name': title, 'hidden': hidden, 'groups': groups}
+ mylib.json_write(fname, json, pretty=False)
+
+
def process(bundle_ids, deleteOnly=False):
print('writing index: ranking ...')
fname = fname_app_summary()
@@ -177,6 +245,8 @@ def process(bundle_ids, deleteOnly=False):
ids = mylib.appids_in_data(bundle_ids)
if update_summary_index(index, ids, deleteOnly=deleteOnly):
write_ranking_list(index, bundle_ids)
+ for values in index.values():
+ del(values[8:])
write_rank_index(index)
print('')
diff --git a/src/lib_common.py b/src/lib_common.py
index fa2a1b9..63af419 100755
--- a/src/lib_common.py
+++ b/src/lib_common.py
@@ -247,10 +247,10 @@ def enum_newly_added():
yield fname, os.path.basename(fname)[3:] # del prefix 'in_'
-def enum_custom_lists():
- for fname in glob.glob(path_data('_lists', 'list_*.json')):
+def enum_custom_lists(prefix):
+ for fname in glob.glob(path_data('_lists', prefix + '*.json')):
with open(fname, 'r') as fp:
- yield os.path.basename(fname)[5:-5], json.load(fp)
+ yield os.path.basename(fname)[len(prefix):-5], json.load(fp)
def enum_jsons(bundle_id):
diff --git a/src/lib_html.py b/src/lib_html.py
index d035c8b..1738622 100755
--- a/src/lib_html.py
+++ b/src/lib_html.py
@@ -23,9 +23,9 @@ def a_subdomain(x, inner=None, attr_str=''):
return '
{} '.format(attr_str, x, inner or x)
-def h2_path_n_rank(title, path_parts, rank_href):
- return '
'.format(
- a_path(path_parts, title), rank_href)
+def h2_path_n_rank(title, path_parts, rank_href, rank_title='Ranking'):
+ return '
'.format(
+ a_path(path_parts, title), rank_href, rank_title)
def p_download_json(href, download_name):
@@ -65,7 +65,7 @@ def div(inner, attr=None):
def a_path(parts, suffix):
''' expects (name, url) tuples '''
return ' / '.join(['
{} '.format(url, title)
- for title, url in parts] + [suffix])
+ for url, title in parts] + [suffix])
# Simple constructs
@@ -81,6 +81,36 @@ def date_utc(ctime):
time.strftime('%Y-%m-%d, %H:%M', time.gmtime(ctime)))
+def seconds_to_time(seconds):
+ seconds = int(seconds)
+ minutes, seconds = divmod(seconds, 60)
+ hours, minutes = divmod(minutes, 60)
+ return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds)
+
+
+def fmt_round_num(num):
+ return format(num, '.1f') # .rstrip('0').rstrip('.')
+
+
+def fmt_as_pm(value):
+ return fmt_round_num(value) + '/min'
+
+
+def fmt_as_percent(value):
+ return fmt_round_num(value * 100) + '%'
+
+
+def h4_a_title_sub_desc(href, title, subtitle, desc):
+ if desc:
+ desc = f'
{desc}
'
+ return f'''
+
+
+
{subtitle}
+ {desc or ''}
+
'''
+
+
# Higher level constructs
def pagination(current, total):
diff --git a/src/main.py b/src/main.py
index 9b14a8c..afe3a31 100755
--- a/src/main.py
+++ b/src/main.py
@@ -8,6 +8,7 @@ import download_itunes
import download_tracker
import html_bundle
import html_categories
+import html_group_compare
import html_index_apps
import html_index_domains
import html_ranking
@@ -41,6 +42,7 @@ def rebuild_html(bundle_ids=None, cat_ids=None, inclIApp=True):
else:
print('no new bundle, not rebuilding index')
html_ranking.process() # after html_categories & html_index_apps
+ html_group_compare.process() # after index_rank
app_count, dom_count = html_index_domains.process() # after index_domains
html_root.process(app_count, dom_count, inclStatic=True)
diff --git a/src/z_dependency.svg b/src/z_dependency.svg
index 5a58206..476aaa1 100644
--- a/src/z_dependency.svg
+++ b/src/z_dependency.svg
@@ -1,156 +1,166 @@
-
+
Dependency
-
+
.
-
-.
-
-
-download_itunes
-
-download_itunes
-
-
-.->download_itunes
-
-
-
-
-bundle_combine
-
-bundle_combine
-
-
-.->bundle_combine
-
-
+
+.
-download_tracker
-
-download_tracker
+download_tracker
+
+download_tracker
-.->download_tracker
-
-
+.->download_tracker
+
+
+
+
+download_itunes
+
+download_itunes
+
+
+.->download_itunes
+
+
+
+
+bundle_combine
+
+bundle_combine
+
+
+.->bundle_combine
+
+
-index_app_names
-
-index_app_names
+index_app_names
+
+index_app_names
-download_itunes->index_app_names
-
-
+download_itunes->index_app_names
+
+
-index_rank
-
-index_rank
+index_rank
+
+index_rank
bundle_combine->index_rank
-
-
+
+
-index_domains
-
-index_domains
+index_domains
+
+index_domains
bundle_combine->index_domains
-
-
+
+
-index_categories
-
-index_categories
+index_categories
+
+index_categories
-index_app_names->index_categories
-
-
-
-
-index_app_names->index_rank
-
-
+index_app_names->index_categories
+
+
html_index_domains
-
-html_index_domains
+
+html_index_domains
index_app_names->html_index_domains
-
-
+
+
html_categories
-
-html_categories
+
+html_categories
index_categories->html_categories
-
-
+
+
html_index_apps
-
-html_index_apps
+
+html_index_apps
index_categories->html_index_apps
-
-
+
+
-
-html_bundle
-
-html_bundle
-
-
-index_rank->html_bundle
-
-
+
+index_categories->index_rank
+
+
-html_root
-
-html_root
+html_root
+
+html_root
html_index_domains->html_root
-
-
+
+
-html_ranking
-
-html_ranking
+html_ranking
+
+html_ranking
html_categories->html_ranking
-
-
+
+
html_index_apps->html_ranking
-
-
+
+
+
+
+html_bundle
+
+html_bundle
+
+
+index_rank->html_bundle
+
+
+
+
+html_group_compare
+
+html_group_compare
+
+
+index_rank->html_group_compare
+
+
index_domains->html_index_domains
-
-
+
+
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 86df014..1df8ba1 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -3,9 +3,9 @@
-
+
#_TITLE_#AppCheck: Privacy Monitor
-
+