Skip to content

Commit 87e83a2

Browse files
committedMay 8, 2015
initial draft of single mode (#36)
Signed-off-by: Chris Warrick <kwpolska@gmail.com>
1 parent dcff05f commit 87e83a2

File tree

4 files changed

+205
-43
lines changed

4 files changed

+205
-43
lines changed
 

Diff for: ‎coil/data/templates/jinja/coil_rebuild_single.tmpl

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{# -*- coding: utf-8 -*- #}
2+
{% extends 'base.tmpl' %}
3+
{% block content %}
4+
<div class="page-header">
5+
<h1 class="build-status-caption"><i class="build-status-icon fa fa-fw fa-cog"></i> Rebuild</h1>
6+
</div>
7+
8+
<div class="panel panel-default">
9+
<div class="panel-heading"><h3 class="panel-title">
10+
<a data-toggle="collapse" href="#collapseOutput" aria-expanded="false" aria-controls="collapseOutput"><i class="fa fa-bars"></i> Advanced information</a>
11+
</h3></div>
12+
<div class="panel-body collapse" id="collapseOutput">
13+
<h4>Build</h4>
14+
<pre><code id="outputb">{{ outputb }}</code></pre>
15+
16+
<h4>Unused files removed</h4>
17+
<pre><code id="outputo">{{ outputo }}</code></pre>
18+
</div>
19+
</div>
20+
21+
{% endblock %}
22+
23+
{% block extra_js %}
24+
<script>
25+
$(document).ready(function() {
26+
fs = $('.build-status-icon');
27+
fsc = $('.build-status-caption');
28+
if ({{ bstatus }} == 1) {
29+
fs.removeClass('fa-cog');
30+
fs.addClass('fa-check');
31+
fsc.addClass('text-success');
32+
clearInterval(intID);
33+
} else {
34+
pb.addClass('progress-bar-danger');
35+
fs.removeClass('fa-cog');
36+
fs.addClass('fa-times');
37+
fsc.addClass('text-danger');
38+
$("#collapseOutput").collapse({
39+
show: true
40+
});
41+
}
42+
});
43+
</script>
44+
{% endblock %}

Diff for: ‎coil/data/templates/mako/coil_rebuild_single.tmpl

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
## -*- coding: utf-8 -*-
2+
<%inherit file="base.tmpl"/>
3+
<%block name="content">
4+
<div class="page-header">
5+
<h1 class="build-status-caption"><i class="build-status-icon fa fa-fw fa-cog"></i> Rebuild</h1>
6+
</div>
7+
8+
<div class="panel panel-default">
9+
<div class="panel-heading"><h3 class="panel-title">
10+
<a data-toggle="collapse" href="#collapseOutput" aria-expanded="false" aria-controls="collapseOutput"><i class="fa fa-bars"></i> Advanced information</a>
11+
</h3></div>
12+
<div class="panel-body collapse" id="collapseOutput">
13+
<h4>Build</h4>
14+
<pre><code id="outputb">${outputb}</code></pre>
15+
16+
<h4>Unused files removed</h4>
17+
<pre><code id="outputo">${outputo}</code></pre>
18+
</div>
19+
</div>
20+
21+
</%block>
22+
23+
<%block name="extra_js">
24+
<script>
25+
$(document).ready(function() {
26+
fs = $('.build-status-icon');
27+
fsc = $('.build-status-caption');
28+
if (${bstatus} == 1) {
29+
fs.removeClass('fa-cog');
30+
fs.addClass('fa-check');
31+
fsc.addClass('text-success');
32+
clearInterval(intID);
33+
} else {
34+
pb.addClass('progress-bar-danger');
35+
fs.removeClass('fa-cog');
36+
fs.addClass('fa-times');
37+
fsc.addClass('text-danger');
38+
$("#collapseOutput").collapse({
39+
show: true
40+
});
41+
}
42+
});
43+
</script>
44+
</%block>

Diff for: ‎coil/tasks.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ def orphans(dburl, sitedir):
8888
job = get_current_job(db)
8989
job.meta.update({'out': '', 'return': None, 'status': None})
9090
job.save()
91+
returncode, out = orphans_single()
92+
93+
job.meta.update({'out': out, 'return': returncode, 'status':
94+
returncode == 0})
95+
job.save()
96+
os.chdir(oldcwd)
97+
return returncode
98+
99+
def build_single(mode):
100+
"""Build, in the single-user mode."""
101+
if mode == 'force':
102+
amode = ['-a']
103+
else:
104+
amode = []
105+
p = subprocess.Popen([executable, '-m', 'nikola', 'build'] + amode,
106+
stderr=subprocess.PIPE)
107+
p.wait()
108+
out = ''.join(p.stderr.readlines())
109+
return (p.returncode == 0), out
110+
111+
def orphans_single():
112+
"""Remove all orphans in the site, in the single user-mode."""
91113
p = subprocess.Popen([executable, '-m', 'nikola', 'orphans'],
92114
stdout=subprocess.PIPE)
93115
p.wait()
@@ -97,8 +119,4 @@ def orphans(dburl, sitedir):
97119
os.unlink(f)
98120

99121
out = '\n'.join(files)
100-
job.meta.update({'out': ''.join(out), 'return': p.returncode, 'status':
101-
p.returncode == 0})
102-
job.save()
103-
os.chdir(oldcwd)
104-
return p.returncode
122+
return p.returncode, out

Diff for: ‎coil/web.py

+94-38
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,14 @@ def configure_site():
118118

119119
app.secret_key = _site.config.get('COIL_SECRET_KEY')
120120
app.config['COIL_URL'] = _site.config.get('COIL_URL')
121+
app.config['COIL_SINGLE'] = _site.config.get('COIL_SINGLE', False)
121122
app.config['REDIS_URL'] = _site.config.get('COIL_REDIS_URL',
122123
'redis://localhost:6379/0')
123-
db = redis.StrictRedis.from_url(app.config['REDIS_URL'])
124-
q = rq.Queue(name='coil', connection=db)
124+
if app.config['COIL_SINGLE']:
125+
app.config['COIL_USERS'] = _site.config.get('COIL_USERS', {})
126+
else:
127+
db = redis.StrictRedis.from_url(app.config['REDIS_URL'])
128+
q = rq.Queue(name='coil', connection=db)
125129

126130
_site.template_hooks['menu'].append(generate_menu)
127131
_site.template_hooks['menu_alt'].append(generate_menu_alt)
@@ -177,7 +181,11 @@ def configure_site():
177181
_site.template_system.inject_directory(tmpl_dir)
178182

179183
# Site proxy
180-
site = SiteProxy(db, _site, app.logger)
184+
if app.config['COIL_SINGLE']:
185+
site = _site
186+
else:
187+
site = SiteProxy(db, _site, app.logger)
188+
181189
configure_url(app.config['COIL_URL'])
182190

183191

@@ -227,7 +235,11 @@ def generate_menu():
227235
:return: HTML fragment
228236
:rtype: str
229237
"""
230-
if db.get('site:needs_rebuild') not in ('0', '-1'):
238+
if db is not None:
239+
needs_rebuild = db.get('site:needs_rebuild')
240+
else:
241+
needs_rebuild = site.coil_needs_rebuild
242+
if needs_rebuild not in ('0', '-1'):
231243
return ('</li><li><a href="{0}"><i class="fa fa-fw '
232244
'fa-warning"></i> <strong>Rebuild</strong></a></li>'.format(
233245
url_for('rebuild')))
@@ -245,7 +257,7 @@ def generate_menu_alt():
245257
if not current_user.is_authenticated():
246258
return ('<li><a href="{0}"><i class="fa fa-fw fa-sign-in"></i> '
247259
'Log in</a></li>'.format(url_for('login')))
248-
if current_user.is_admin:
260+
if db is not None and current_user.is_admin:
249261
edit_entry = (
250262
'<li><a href="{0}"><i class="fa fa-fw fa-users"></i> '
251263
'Manage users</a></li>'
@@ -434,13 +446,20 @@ def get_user(uid):
434446
:raises ValueError: uid is not an integer
435447
:raises KeyError: if user does not exist
436448
"""
437-
d = db.hgetall('user:{0}'.format(uid))
438-
if d:
439-
for p in PERMISSIONS:
440-
d[p] = d.get(p) == '1'
441-
return User(uid=uid, **d)
449+
if db is not None:
450+
d = db.hgetall('user:{0}'.format(uid))
451+
if d:
452+
for p in PERMISSIONS:
453+
d[p] = d.get(p) == '1'
454+
return User(uid=uid, **d)
455+
else:
456+
return None
442457
else:
443-
return None
458+
d = app.config['COIL_USERS'].get(uid)
459+
if d:
460+
return User(uid=uid, **d)
461+
else:
462+
return None
444463

445464

446465
def find_user_by_name(username):
@@ -450,11 +469,14 @@ def find_user_by_name(username):
450469
:return: the user
451470
:rtype: User object or None
452471
"""
453-
uid = db.hget('users', username)
454-
if uid:
455-
return get_user(uid)
472+
if db is not None:
473+
uid = db.hget('users', username)
474+
if uid:
475+
return get_user(uid)
456476
else:
457-
return None
477+
for u in app.config['COIL_USERS']:
478+
if u['username'] == username:
479+
return get_user(uid)
458480

459481

460482
def write_user(user):
@@ -638,7 +660,10 @@ def edit(path):
638660
with io.open(meta_path, 'w+', encoding='utf-8') as fh:
639661
fh.write(write_metadata(meta))
640662
scan_site()
641-
db.set('site:needs_rebuild', '1')
663+
if db is not None:
664+
db.set('site:needs_rebuild', '1')
665+
else:
666+
site.coil_needs_rebuild = '1'
642667
post = find_post(path)
643668
context['action'] = 'save'
644669
else:
@@ -651,11 +676,16 @@ def edit(path):
651676

652677
context['post'] = post
653678
users = []
654-
uids = db.hgetall('users').values()
655-
for u in uids:
656-
realname, active = db.hmget('user:{0}'.format(u), 'realname', 'active')
657-
if active == '1':
658-
users.append((u, realname))
679+
if db is not None:
680+
uids = db.hgetall('users').values()
681+
for u in uids:
682+
realname, active = db.hmget('user:{0}'.format(u), 'realname', 'active')
683+
if active == '1':
684+
users.append((u, realname))
685+
else:
686+
for u, d in app.config['COIL_USERS'].values():
687+
if d['active']:
688+
users.append((u, d['realname']))
659689
context['users'] = sorted(users)
660690
context['current_auid'] = current_auid
661691
context['title'] = 'Editing {0}'.format(post.title())
@@ -689,14 +719,19 @@ def delete():
689719
meta_path = os.path.splitext(path)[0] + '.meta'
690720
os.unlink(meta_path)
691721
scan_site()
692-
db.set('site:needs_rebuild', '1')
722+
if db is not None:
723+
db.set('site:needs_rebuild', '1')
724+
else:
725+
site.coil_needs_rebuild = '1'
693726
return redirect(url_for('index'))
694727

695728

696729
@app.route('/api/rebuild/')
697730
@login_required
698731
def api_rebuild():
699732
"""Rebuild the site (internally)."""
733+
if db is None:
734+
return '{"error": "single-user mode"}'
700735
build_job = q.fetch_job('build')
701736
orphans_job = q.fetch_job('orphans')
702737

@@ -737,17 +772,25 @@ def rebuild(mode=''):
737772
return error('You are not permitted to rebuild the site.</p>'
738773
'<p class="lead">Contact an administartor for '
739774
'more information.', 401)
740-
db.set('site:needs_rebuild', '-1')
741-
if not q.fetch_job('build') and not q.fetch_job('orphans'):
742-
b = q.enqueue_call(func=coil.tasks.build,
743-
args=(app.config['REDIS_URL'],
744-
app.config['NIKOLA_ROOT'], mode), job_id='build')
745-
q.enqueue_call(func=coil.tasks.orphans,
746-
args=(app.config['REDIS_URL'],
747-
app.config['NIKOLA_ROOT']), job_id='orphans',
748-
depends_on=b)
775+
if db is not None:
776+
db.set('site:needs_rebuild', '-1')
777+
if not q.fetch_job('build') and not q.fetch_job('orphans'):
778+
b = q.enqueue_call(func=coil.tasks.build,
779+
args=(app.config['REDIS_URL'],
780+
app.config['NIKOLA_ROOT'], mode), job_id='build')
781+
q.enqueue_call(func=coil.tasks.orphans,
782+
args=(app.config['REDIS_URL'],
783+
app.config['NIKOLA_ROOT']), job_id='orphans',
784+
depends_on=b)
785+
return render('coil_rebuild.tmpl', {'title': 'Rebuild'})
786+
else:
787+
status, outputb = coil.tasks.build_single(mode)
788+
_, outputo = coil.tasks.orphans_single()
789+
return render('coil_rebuild_single.tmpl', {'title': 'Rebuild',
790+
'status': '1' if status else '0',
791+
'outputb': outputb,
792+
'outputo': outputo})
749793

750-
return render('coil_rebuild.tmpl', {'title': 'Rebuild'})
751794

752795

753796
@app.route('/new/<obj>/', methods=['POST'])
@@ -791,7 +834,10 @@ def new(obj):
791834
del _site.config['ADDITIONAL_METADATA']['author.uid']
792835
# reload post list and go to index
793836
scan_site()
794-
db.set('site:needs_rebuild', '1')
837+
if db is not None:
838+
db.set('site:needs_rebuild', '1')
839+
else:
840+
site.coil_needs_rebuild = '1'
795841
return redirect(url_for('index'))
796842

797843

@@ -892,8 +938,10 @@ def acp_users():
892938
"""List all users."""
893939
if current_user.must_change_password:
894940
return redirect(url_for('acp_account') + '?status=pwdchange')
895-
elif not current_user.is_admin:
941+
if not current_user.is_admin:
896942
return error("Not authorized to edit users.", 401)
943+
if not db:
944+
return error('The ACP is not available in single-user mode.', 500)
897945

898946
alert = ''
899947
alert_status = ''
@@ -923,8 +971,10 @@ def acp_users_edit():
923971
global current_user
924972
if current_user.must_change_password:
925973
return redirect(url_for('acp_account') + '?status=pwdchange')
926-
elif not current_user.is_admin:
974+
if not current_user.is_admin:
927975
return error("Not authorized to edit users.", 401)
976+
if not db:
977+
return error('The ACP is not available in single-user mode.', 500)
928978
data = request.form
929979

930980
form = UserEditForm()
@@ -997,8 +1047,10 @@ def acp_users_import():
9971047
"""Import users from a TSV file."""
9981048
if current_user.must_change_password:
9991049
return redirect(url_for('acp_account') + '?status=pwdchange')
1000-
elif not current_user.is_admin:
1050+
if not current_user.is_admin:
10011051
return error("Not authorized to edit users.", 401)
1052+
if not db:
1053+
return error('The ACP is not available in single-user mode.', 500)
10021054

10031055
form = UserImportForm()
10041056
if not form.validate():
@@ -1015,8 +1067,10 @@ def acp_users_delete():
10151067
"""Delete or undelete an user account."""
10161068
if current_user.must_change_password:
10171069
return redirect(url_for('acp_account') + '?status=pwdchange')
1018-
elif not current_user.is_admin:
1070+
if not current_user.is_admin:
10191071
return error("Not authorized to edit users.", 401)
1072+
if not db:
1073+
return error('The ACP is not available in single-user mode.', 500)
10201074

10211075
form = UserDeleteForm()
10221076
if not form.validate():
@@ -1040,8 +1094,10 @@ def acp_users_permissions():
10401094
"""Change user permissions."""
10411095
if current_user.must_change_password:
10421096
return redirect(url_for('acp_account') + '?status=pwdchange')
1043-
elif not current_user.is_admin:
1097+
if not current_user.is_admin:
10441098
return error("Not authorized to edit users.", 401)
1099+
if not db:
1100+
return error('The ACP is not available in single-user mode.', 500)
10451101

10461102
form = PermissionsForm()
10471103
users = []

0 commit comments

Comments
 (0)
Please sign in to comment.