Permalink
Browse files

Added permission detection, improved gui workflow, cleanup some redun…

…dant codes, added package info
  • Loading branch information...
1 parent 41881b2 commit c5918ade3c4a059ee0c3e4566c792d26fd299355 Ivan Choo committed Mar 6, 2012
Showing with 92 additions and 111 deletions.
  1. +24 −15 README.md
  2. +12 −3 setup.py
  3. +3 −0 wikisync/htdocs/wikisync.css
  4. +33 −10 wikisync/htdocs/wikisync.js
  5. +1 −34 wikisync/model.py
  6. +13 −48 wikisync/plugin.py
  7. +6 −1 wikisync/templates/wikisync.html
View
@@ -1,13 +1,12 @@
TracWikiSync
============
-Introduction
-------------
This [Trac](http://trac.edgewall.org/) plugin allows you to synchronize wiki entries between to separate Trac installations.
Common use case is be to install a local Trac project on your workstation and synchronizing the wiki entires from your remote Trac server. This is especially useful when working over slow VPN connections and/or working offline, editing the wikies and doing batch synchronizing when you're ready to commit.
-Some features are:
+Features
+------------
- Supports the following type of synchronization:
@@ -33,43 +32,53 @@ Some features are:
TODO
----
+ - Fix GUI to solve screen lock ups and show better progress status
+
+ - Implement quick filter for batch synchronization screen
+
+ - Implement refresh from last sync for faster status detection
+
- Implement single wiki synchronization
- Implement attachment synchronization
+
+ - Implement IWikiChangeListener to catch deleted and renamed wiki changes
Installation and Requirements
-----------------------------
+TODO
+
Minimum requirements:
- Trac 0.12 >=
- Python 2.6 >=
-
-### Per Trac Project ###
- 1. Via the Trac Admin > Plugins web interface or
-
- 1. See http://trac.edgewall.org/wiki/TracPlugins#Forasingleproject
-
-### For All Trac Projects ###
-
- 1. http://trac.edgewall.org/wiki/TracPlugins#Forallprojects
-
- 1. Enabling the plugin in `trac.ini`:<blockquote>
+ - Enabling the plugin in `trac.ini`:<pre>
[components]
wikisync.* = enabled`
-</blockquote>
+</pre>
Usage
-----
+Trac users require the following permissions:
+
+ - `TRAC_ADMIN`: To configure the remote server information in the admin panels
+
+ - `WIKI_ADMIN`: To perform synchronization
+
Develop
-------
+TODO
+
Bugs
----
+Please use [Issues](https://github.com/ivanchoo/TracWikiSync/issues)
+
Version History
---------------
View
@@ -3,12 +3,21 @@
setup(
name="TracWikiSync",
- version="0.1",
+ version="0.1.0",
+ keywords="trac wiki sync synchronization",
+ author="Ivan Choo",
+ author_email="hello@ivanchoo.com",
+ url="https://github.com/ivanchoo/TracWikiSync",
+ description="Synchronize wiki entries between to separate Trac installations",
packages=find_packages(exclude=["*.tests*"]),
package_data={
- "wikisync": ["templates/*.html"]
+ "wikisync": [
+ "templates/*.html",
+ "htdocs/*.*",
+ "htdocs/images/*.*"
+ ]
},
entry_points={
- "trac.plugins": ["wikisync = wikisync"]
+ "trac.plugins": ["wikisync=wikisync"]
}
)
@@ -30,6 +30,9 @@
line-height: 1.4em;
overflow: hidden;
}
+.wikisync i {
+ color: #999;
+}
.wikisync .nested {
margin-left: -80px;
}
@@ -12,7 +12,7 @@
var TREE_NODE_TEMPLATE =
'<li id="<%- cid %>" class="<%- status %>">' +
' <div class="item-wrapper">' +
- ' <a href="<%- localUrl %>"><%- name %></a>' +
+ ' <a href="<%- localUrl %>" target="_blank"><%= name %></a>' +
' <% if (status == "conflict") { %>' +
' <span class="resolve-wrapper">' +
' <i>Resolve as:</i>' +
@@ -39,7 +39,7 @@
var TREE_NESTED_NODE_TEMPLATE =
'<li id="<%- cid %>" class="grouped">' +
' <div class="item-wrapper">' +
- ' <strong><%- name %></strong>' +
+ ' <strong><%= name %></strong>' +
' <span class="controls"> ' +
' <a href="#" class="unignore-all">✔✔ Unignore All</a>' +
' <i> or </i>' +
@@ -122,7 +122,7 @@
if (!strEndsWith(base, '/')) {
base += '/';
}
- return base + _.rest(arguments).join('/');
+ return base + encodeURI(_.rest(arguments).join('/'));
};
var WikiSyncModel = Backbone.Model.extend({
@@ -215,22 +215,27 @@
});
},
ignore: function(models, isIgnore) {
- var data = _.map(models, function(model) {
+ var self = this;
+ var batch = models.splice(0, 10);
+ var data = _.map(batch, function(model) {
return { name:'name', value:model.get('name') };
});
data.push({ name:'action', value:'resolve' });
data.push({ name:'status', value:isIgnore ? 'ignore' : 'unignore' });
this._post({
data:data,
beforeSend: function() {
- _.each(models, function(model) {
+ _.each(batch, function(model) {
model.trigger('progress', model);
});
},
complete: function(xhr, status) {
- _.each(models, function(model) {
+ _.each(batch, function(model) {
model.trigger('complete', model, status, isIgnore ? 'ignore' : 'unignore');
});
+ if (models.length) {
+ self.ignore(models, isIgnore);
+ }
}
});
},
@@ -276,7 +281,7 @@
'click button.submit': 'onSync',
'change input.resolve': 'onResolve',
'change #filter-conflict-resolve': 'onGlobalResolve',
- 'change input.filter': 'renderTree'
+ 'change input.filter': 'onFilter'
},
initialize: function(opts) {
_.bindAll(this);
@@ -426,6 +431,16 @@
}
}
},
+ onFilter: function(evt) {
+ var el = $(evt.target);
+ var li = el.parents('li');
+ if (el.is(':checked')) {
+ li.addClass('selected');
+ } else {
+ li.removeClass('selected');
+ }
+ this.renderTree();
+ },
onSync: function(evt) {
evt.preventDefault();
this.sync(!this.syncPending);
@@ -473,8 +488,9 @@
onIgnore: function(evt) {
evt.preventDefault();
var target = $(evt.target);
- var li = target.parents('li:first');
var isIgnore = true;
+ var isGlobal = !target.parent().is('.controls');
+ var li = isGlobal ? this.$list : target.parents('li:first');
if (target.hasClass('ignore-all')) {
li = li.find('li');
} else if (target.hasClass('unignore-all')) {
@@ -492,10 +508,17 @@
else if (!isIgnore && !el.hasClass('ignored')) return;
models.push(this.collection.getByCid(el.attr('id')));
}, this);
- if (models.length) {
+ var num = models.length;
+ var verb = isIgnore ? 'ignored' : 'unignored';
+ if (num) {
+ if (num > 30) {
+ if (!confirm('Do you want to ' + verb + ' ' + num + ' wikies?')) {
+ return;
+ }
+ }
this.collection.ignore(models, isIgnore);
} else {
- alert('All wikies are already ' + (isIgnore ? 'ignored' : 'unignored'));
+ alert('All wikies are already ' + verb);
}
}
});
View
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import time
-from wikisync.util import safe_int as safe_int
+from wikisync.util import safe_int
from collections import namedtuple
class WikiSync(namedtuple("WikiSync", [
@@ -11,39 +11,6 @@ class WikiSync(namedtuple("WikiSync", [
__slots__ = ()
- """
- @property
- def status(self):
- rv = safe_int(self.remote_version)
- lv = safe_int(self.local_version)
- srv = safe_int(self.sync_remote_version)
- slv = safe_int(self.sync_local_version)
- st = safe_int(self.sync_time)
- if self.ignore:
- return "ignored"
- elif not st:
- # no known sync time
- return "unknown"
- elif rv and not lv:
- # can't find local copy
- return "missing"
- elif lv and not rv:
- # can't find remote copy
- return "new"
- elif rv > srv and lv > slv:
- # both remote and local are out of sync
- return "conflict"
- elif rv > srv:
- # local in-sync, but remote out of sync
- return "outdated"
- elif lv > slv:
- # local out of sync, but remote in sync
- return "modified"
- elif rv == srv and lv == slv:
- return "synced"
- return "unknown"
- """
-
def merge(self, **kwargs):
return self._replace(**kwargs)
View
@@ -34,46 +34,8 @@
Inter*
(?!^WikiStart$)Wiki.*"""
-def wikisync_group_by_name(items, minsize_group=2):
- flattened = []
- for item in items:
- path = [elt.strip() for elt in RE_SPLIT.split(
- RE_NUM_SPLIT.sub(r" \1 ",
- RE_SPLIT_CAMELCASE.sub(lambda m: m.group()[:1] + " " + m.group()[1:], item.name)
- )
- )]
- flattened.append(([elt for elt in path if elt], item))
- def tree_group(entries):
- groups = []
- for key, grouper in groupby(entries,
- lambda (elts, item): elts and elts[0] or ""):
- # remove key from path_elements in grouped entries for further
- # grouping
- grouped_entries = [(path_elements[1:], item)
- for path_elements, item in grouper]
- if key and len(grouped_entries) >= minsize_group:
- subnodes = tree_group(sorted(grouped_entries))
- if len(subnodes) == 1:
- subkey, subnodes = subnodes[0]
- node = (key + subkey, subnodes)
- groups.append(node)
- elif RE_SPLIT.match(key):
- for elt in subnodes:
- if isinstance(elt, tuple):
- subkey, subnodes = elt
- elt = (key + subkey, subnodes)
- groups.append(elt)
- else:
- node = (key, subnodes)
- groups.append(node)
- else:
- for path_elements, item in grouped_entries:
- groups.append(item)
- return groups
- return tree_group(flattened)
-
class WikiSyncMixin(object):
-
+
def _get_dao(self):
return WikiSyncDao(self.env)
@@ -149,7 +111,7 @@ def upgrade_environment(self, db):
dao.sync_wiki_data()
if not self._get_config("ignorelist"):
self._set_config("ignorelist", DEFAULT_IGNORELIST)
- self._save_config(req)
+ self._save_config()
def get_db_version(self, db=None):
if not db:
@@ -208,9 +170,9 @@ def get_active_navigation_item(self, req):
return "wikisync"
def get_navigation_items(self, req):
- # TODO: permission
- yield ("mainnav", "wikisync",
- tag.a("Wiki Sync", href=req.href.wikisync()))
+ if "WIKI_ADMIN" in req.perm:
+ yield ("mainnav", "wikisync",
+ tag.a("Wiki Sync", href=req.href.wikisync()))
# IRequestHandler methods
def match_request(self, req):
@@ -225,14 +187,14 @@ def get_htdocs_dirs(self):
# ITemplateStreamFilter
def filter_stream(self, req, method, filename, stream, data):
+ if "WIKI_ADMIN" in req.perm:
+ # TODO: Render individual page controls via add_ctxtnav
+ pass
return stream
- # TODO
- """
- if filename == "wiki_view.html":
- add_ctxtnav(req, "WikiSync Page", req.href.wiki("WikiSync"))
- return stream"""
def process_request(self, req):
+ req.perm.require("WIKI_ADMIN")
+ redirect = False
dao = self._get_dao()
action = req.args.get("action")
names = req.args.get("name")
@@ -254,6 +216,7 @@ def process_request(self, req):
ignore = self._get_ignore_filter()
results = rpc.get_remote_list()
dao.sync_remote_data(results, ignore)
+ redirect = True
else:
assert items, "Missing items '%s'" % names
for item in items:
@@ -311,6 +274,8 @@ def process_request(self, req):
rpc.close()
if items:
self._render_json(req, [dao.find(item.name) for item in items])
+ elif redirect:
+ req.redirect(req.href.wikisync())
else:
add_stylesheet(req, "wikisync/wikisync.css")
add_script(req, "wikisync/underscore.js")
Oops, something went wrong.

0 comments on commit c5918ad

Please sign in to comment.