from django.core.urlresolvers import reverse
-from wlrepo import RevisionNotFound, DocumentAlreadyExists
+from wlrepo import RevisionNotFound, LibraryException, DocumentAlreadyExists
from librarian import dcparser
import api.response as response
from api.utils import validate_form, hglibrary
+from explorer.models import PullRequest
+
#
# Document List Handlers
#
"""Create a new document."""
if form.cleaned_data['ocr_data']:
- data = form.cleaned_data['ocr_data'].encode('utf-8')
+ data = form.cleaned_data['ocr_data']
else:
data = request.FILES['ocr_file'].read().decode('utf-8')
data = librarian.wrap_text(data, unicode(date.today()))
docid = form.cleaned_data['bookname']
+
try:
- doc = lib.document_create(docid)
- doc = doc.quickwrite('xml', data, '$AUTO$ XML data uploaded.',
- user=request.user.username)
-
- url = reverse('document_view', args=[doc.id])
-
- return response.EntityCreated().django_response(\
- body = {
- 'url': url,
- 'name': doc.id,
- 'revision': doc.revision },
- url = url )
-
+ lock = lib.lock()
+ try:
+ doc = lib.document_create(docid)
+ # document created, but no content yet
+
+ try:
+ doc = doc.quickwrite('xml', data.encode('utf-8'),
+ '$AUTO$ XML data uploaded.', user=request.user.username)
+ except Exception,e:
+ # rollback branch creation
+ lib._rollback()
+ raise LibraryException("Exception occured:" + repr(e))
+
+ url = reverse('document_view', args=[doc.id])
+
+ return response.EntityCreated().django_response(\
+ body = {
+ 'url': url,
+ 'name': doc.id,
+ 'revision': doc.revision },
+ url = url )
+ finally:
+ lock.release()
+ except LibraryException, e:
+ return response.InternalError().django_response(\
+ {'exception': repr(e) })
except DocumentAlreadyExists:
# Document is already there
return response.EntityConflict().django_response(\
def create(self, request, form, docid, lib):
"""Create a new document revision from the information provided by user"""
- pass
+ target_rev = form.cleaned_data['target_revision']
-
-
-
+ doc = lib.document(docid)
+ udoc = doc.take(request.user.username)
+
+ if target_rev == 'latest':
+ target_rev = udoc.revision
+
+ if udoc.revision != target_rev:
+ # user think doesn't know he has an old version
+ # of his own branch.
+
+ # Updating is teorericly ok, but we need would
+ # have to force a refresh. Sharing may be not safe,
+ # 'cause it doesn't always result in update.
+
+ # In other words, we can't lie about the resource's state
+ # So we should just yield and 'out-of-date' conflict
+ # and let the client ask again with updated info.
+
+ # NOTE: this could result in a race condition, when there
+ # are 2 instances of the same user editing the same document.
+ # Instance "A" trying to update, and instance "B" always changing
+ # the document right before "A". The anwser to this problem is
+ # for the "A" to request a merge from 'latest' and then
+ # check the parent revisions in response, if he actually
+ # merge from where he thinks he should. If not, the client SHOULD
+ # update his internal state.
+ return response.EntityConflict().django_response({
+ "reason": "out-of-date",
+ "provided": target_revision,
+ "latest": udoc.revision })
+
+ if not request.user.has_permission('explorer.pull_request.can_add'):
+ # User is not permitted to make a merge, right away
+ # So we instead create a pull request in the database
+ prq = PullRequest(
+ commiter=request.uset.username,
+ document=docid,
+ source_revision = udoc.revision,
+ status="N",
+ comment = form.cleaned_data['comment']
+ )
+
+ prq.save()
+ return response.RequestAccepted()
+
+ if form.cleanded_data['type'] == 'update':
+ # update is always performed from the file branch
+ # to the user branch
+ success, changed = udoc.update(request.user.username)
+
+ if form.cleanded_data['type'] == 'share':
+ success, changed = udoc.share(form.cleaned_data['comment'])
+
+ if not success:
+ return response.EntityConflict().django_response()
+
+ if not changed:
+ return response.SuccessNoContent().django_response()
+
+ new_udoc = udoc.latest()
+
+ return response.SuccessAllOk().django_response({
+ "name": udoc.id,
+ "parent_user_resivion": udoc.revision,
+ "parent_revision": doc.revision,
+ "revision": udoc.revision,
+ })
\ No newline at end of file
--- /dev/null
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+
--- /dev/null
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+
--- /dev/null
+#!/usr/bin/env python
+# -*- conding: utf-8 -*-
+__author__="lreqc"
+__date__ ="$2009-09-08 14:31:26$"
+
+from django.core.management.base import BaseCommand
+from django.utils import simplejson as json
+from django.test.client import Client
+from django.core.urlresolvers import reverse
+
+from optparse import make_option
+
+class Command(BaseCommand):
+
+ option_list = BaseCommand.option_list + (
+ make_option('-u', '--user', action='store', dest='username'),
+ make_option('-p', '--password', action='store', dest='password'),
+ make_option('-d', '--dublin-core', action='store_true', dest='dc'),
+ )
+
+ def handle(self, *args, **options):
+ client = Client()
+ if not options['username'] or not options['password']:
+ raise CommandError("You must provide login data")
+
+ client.login(username=options['username'], \
+ password=options['password'])
+
+ print options['username'], options['password']
+
+ filename = args[0]
+ bookname = args[1]
+
+ print "Uploading '%s' as document '%s'" % (filename, bookname)
+ print "Wth DC template" if options['dc'] else ""
+
+ print client.post( reverse("document_list_view"),\
+ {
+ 'bookname': bookname,
+ 'ocr_file': open(filename),
+ 'generate_dc': options['dc'] } )
+
#
# Server side errors
#
-class NotImplemented(ResponseObject):
+class InternalError(ResponseObject):
def __init__(self, **kwargs):
- ResponseObject.__init__(self, 501, **kwargs)
+ ResponseObject.__init__(self, 500, **kwargs)
+class NotImplemented(ResponseObject):
+ def __init__(self, **kwargs):
+ ResponseObject.__init__(self, 501, **kwargs)
\ No newline at end of file
def decorator(func):
+
@wraps(func)
def decorated(self, *args, **kwargs):
clean = False
import explorer.models
admin.site.register(explorer.models.EditorSettings)
-admin.site.register(explorer.models.EditorPanel)
\ No newline at end of file
+admin.site.register(explorer.models.EditorPanel)
+admin.site.register(explorer.models.PullRequest)
\ No newline at end of file
class PullRequest(models.Model):
- comitter = models.ForeignKey(User) # the user who request the pull
- file = models.CharField(max_length=256) # the file to request
- source_rev = models.CharField(max_length=40) # revision number of the commiter
+ REQUEST_STATUSES = (
+ ("N", "Pending for resolution"),
+ ("R", "Rejected"),
+ ("A", "Accepted & merged"),
+ )
+
+ comitter = models.ForeignKey(User) # the user who request the pull
comment = models.TextField() # addtional comments to the request
- # revision number in which the changes were merged (if any)
- merged_rev = models.CharField(max_length=40, null=True)
-
- def __unicode__(self):
- return u"Pull request from %s, source: %s %s, status: %s." % \
- (self.commiter, self.file, self.source_rev, \
- (("merged into "+self.merged_rev) if self.merged_rev else "pending") )
+ # document to merge
+ document = models.CharField(max_length=255)
+ # revision to be merged into the main branch
+ source_revision = models.CharField(max_length=40)
+
+ # current status
+ status = models.CharField(max_length=1, choices=REQUEST_STATUSES)
+
+ # comment to the status change of request (if applicable)
+ response_comment = models.TextField(blank=True)
+
+ # revision number in which the changes were merged (if any)
+ merged_rev = models.CharField(max_length=40, blank=True, null=True)
def get_image_folders():
return sorted(fn for fn in os.listdir(os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR)) if not fn.startswith('.'))
import wlrepo
-
-
class MercurialRevision(wlrepo.Revision):
def __init__(self, lib, changectx):
lock = self._library._lock(True)
try:
self._library._checkout(self._changectx.node())
- self._library._merge(other._changectx.node())
- self._library._commit(user=user, message=message)
+ status = self._library._merge(other._changectx.node())
+ if status.is_clean():
+ self._library._commit(user=user, message=message)
+ return (True, True)
+ else:
+ return (False, False)
finally:
lock.release()
f = l._fileopen(r(entry), "w+")
f.write(data)
f.close()
- l._fileadd(r(entry))
+ l._fileadd(r(entry))
return self.invoke_and_commit(write, lambda d: (msg, user))
- def invoke_and_commit(self, ops, before_commit):
- lock = self._library._lock()
+ def invoke_and_commit(self, ops,
+ before_commit, rollback=False):
+ lock = self._library.lock()
try:
self._library._checkout(self._revision.hgrev())
ops(self._library, entry_path)
message, user = before_commit(self)
self._library._commit(message, user)
-
- return self._library.document(docid=self.id, user=self.owner)
+ return self._library.document(docid=self.id, user=self.owner)
finally:
- lock.release()
-
+ lock.release()
+
# def commit(self, message, user):
# """Make a new commit."""
# self.invoke_and_commit(message, user, lambda *a: True)
def shared(self):
if self.ismain():
return self
- return self._library.document(docid=self._revision.document_name())
+
+ return self._library.document(docid=self.id)
+
+ def latest(self):
+ return self._library.document(docid=self.id, user=self.owner)
def take(self, user):
fullid = self._library.fulldocid(self.id, user)
def update(self, user):
"""Update parts of the document."""
- lock = self.library._lock()
+ lock = self.library.lock()
try:
if self.ismain():
# main revision of the document
- return True
+ return (True, False)
if self._revision.has_children():
# can't update non-latest revision
- return False
+ return (False, False)
sv = self.shared()
if not sv.ancestorof(self) and not self.parentof(sv):
- self._revision.merge_with(sv._revision, user=user)
+ return self._revision.merge_with(sv._revision, user=user)
- return True
+ return (False, False)
finally:
lock.release()
def share(self, message):
- lock = self.library._lock()
+ lock = self.library.lock()
try:
if self.ismain():
- return True # always shared
+ return (True, False) # always shared
user = self._revision.user_name()
-
main = self.shared()._revision
- local = self._revision
-
- no_changes = True
+ local = self._revision
# Case 1:
# * local
if main.ancestorof(local):
print "case 1"
- main.merge_with(local, user=user, message=message)
- no_changes = False
+ success, changed = main.merge_with(local, user=user, message=message)
# Case 2:
#
# main * * local
elif local.has_common_ancestor(main):
print "case 2"
if not local.parentof(main):
- main.merge_with(local, user=user, message=message)
- no_changes = False
+ success, changed = main.merge_with(local, user=user, message=message)
# Case 3:
# main *
elif local.ancestorof(main):
print "case 3"
if not local.parentof(main):
- local.merge_with(main, user=user, message='Local branch update.')
- no_changes = False
+ success, changed = local.merge_with(main, user=user, \
+ message='$AUTO$ Local branch update during share.')
+
else:
print "case 4"
- local.merge_with(main, user=user, message='Local branch update.')
- local = self.shelf()
- main.merge_with(local, user=user, message=message)
+ success, changed = local.merge_with(main, user=user, \
+ message='$AUTO$ Local branch update during share.')
- print "no_changes: ", no_changes
- return no_changes
+ if not success:
+ return False
+
+ if changed:
+ local = local.latest()
+
+ success, changed = main.merge_with(local, user=user,\
+ message=message)
+
+ return success, changed
finally:
lock.release()
# Locking
#
- def _lock(self, write_mode=False):
+ def lock(self, write_mode=False):
return self._hgrepo.wlock() # no support for read/write mode yet
def _transaction(self, write_mode, action):
- lock = self._lock(write_mode)
+ lock = self.lock(write_mode)
try:
return action(self)
finally:
def _changectx(self, nodeid):
return self._hgrepo.changectx(nodeid)
+ def _rollback(self):
+ return self._hgrepo.rollback()
+
#
# BASIC BRANCH routines
#