Exemple #1
0
 def __init__(self):
     self.persister = Persister()
 def __init__(self):
   self.persister = Persister()
Exemple #3
0
class DaemonMobWrite(mobwrite_core.MobWrite):
    def __init__(self):
        self.persister = Persister()

    def handleRequest(self, text):
        try:
            mobwrite_core.LOG.debug("Incoming: " + text)
            actions = self.parseRequest(text)
            reply = self.doActions(actions)
            mobwrite_core.LOG.debug("Reply: " + reply)
            return reply
        except:
            mobwrite_core.LOG.exception("Error handling request: " + text)
            return "E:all:Processing error"

    def doActions(self, actions):
        output = []
        last_username = None
        last_filename = None

        for action_index in xrange(len(actions)):
            action = actions[action_index]
            mobwrite_core.LOG.debug("action %s = %s", action_index, action)

        for action_index in xrange(len(actions)):
            # Use an indexed loop in order to peek ahead one step to detect
            # username/filename boundaries.
            action = actions[action_index]

            # Close mode doesn't need a filename or handle for the 'close all' case
            # If killing a specific view, then the id is in the 'data'
            if action["mode"] == "close":
                to_close = action.get("data")
                if to_close == "all":
                    kill_views_for_user(action["username"])
                elif to_close is not None:
                    kill_view(action["username"], to_close)
                continue

            viewobj = fetch_viewobj(action["username"],
                                    action["filename"],
                                    handle=action["handle"],
                                    metadata=action["metadata"],
                                    persister=self.persister)
            if viewobj is None:
                # Too many views connected at once.
                # Send back nothing.  Pretend the return packet was lost.
                return ""

            delta_ok = True
            mobwrite_core.LOG.debug("view.lock.acquire on %s@%s",
                                    viewobj.username, viewobj.filename)
            viewobj.lock.acquire()
            textobj = viewobj.textobj

            try:
                access = self.persister.check_access(action["filename"],
                                                     action["handle"])
                if access == Access.Denied:
                    name = get_username_from_handle(action["handle"])
                    message = "%s does not have access to %s" % (
                        name, action["filename"])
                    mobwrite_core.LOG.warning(message)
                    output.append("E:" + action["filename"] + ":" + message +
                                  "\n")
                    continue

                if action["mode"] == "null":
                    if access == Access.ReadOnly:
                        output.append("O:" + action["filename"] + "\n")
                    else:
                        # Nullify the text.
                        mobwrite_core.LOG.debug(
                            "Nullifying: '%s@%s'" %
                            (viewobj.username, viewobj.filename))
                        mobwrite_core.LOG.debug("text.lock.acquire on %s",
                                                textobj.name)
                        textobj.lock.acquire()
                        try:
                            textobj.setText(None)
                        finally:
                            mobwrite_core.LOG.debug("text.lock.release on %s",
                                                    textobj.name)
                            textobj.lock.release()
                        viewobj.nullify()
                    continue

                if (action["server_version"] != viewobj.shadow_server_version
                        and action["server_version"]
                        == viewobj.backup_shadow_server_version):
                    # Client did not receive the last response.  Roll back the shadow.
                    mobwrite_core.LOG.warning(
                        "Rollback from shadow %d to backup shadow %d" %
                        (viewobj.shadow_server_version,
                         viewobj.backup_shadow_server_version))
                    viewobj.shadow = viewobj.backup_shadow
                    viewobj.shadow_server_version = viewobj.backup_shadow_server_version
                    viewobj.edit_stack = []

                # Remove any elements from the edit stack with low version numbers which
                # have been acked by the client.
                x = 0
                while x < len(viewobj.edit_stack):
                    if viewobj.edit_stack[x][0] <= action["server_version"]:
                        del viewobj.edit_stack[x]
                    else:
                        x += 1

                if action["mode"] == "raw":
                    # It's a raw text dump.
                    data = urllib.unquote(action["data"]).decode("utf-8")
                    mobwrite_core.LOG.info(
                        "Got %db raw text: '%s@%s'" %
                        (len(data), viewobj.username, viewobj.filename))
                    delta_ok = True
                    # First, update the client's shadow.
                    viewobj.shadow = data
                    viewobj.shadow_client_version = action["client_version"]
                    viewobj.shadow_server_version = action["server_version"]
                    viewobj.backup_shadow = viewobj.shadow
                    viewobj.backup_shadow_server_version = viewobj.shadow_server_version
                    viewobj.edit_stack = []
                    if access == Access.ReadOnly:
                        output.append("O:" + action["filename"] + "\n")
                    elif action["force"] or textobj.text == None:
                        # Clobber the server's text.
                        mobwrite_core.LOG.debug("text.lock.acquire on %s",
                                                textobj.name)
                        textobj.lock.acquire()
                        try:
                            if textobj.text != data:
                                textobj.setText(data)
                                mobwrite_core.LOG.debug(
                                    "Overwrote content: '%s@%s'" %
                                    (viewobj.username, viewobj.filename))
                        finally:
                            mobwrite_core.LOG.debug("text.lock.release on %s",
                                                    textobj.name)
                            textobj.lock.release()

                elif action["mode"] == "delta":
                    # It's a delta.
                    mobwrite_core.LOG.debug("Got delta: %s@%s",
                                            viewobj.username, viewobj.filename)
                    # mobwrite_core.LOG.debug("Got '%s' delta: '%s@%s'" %
                    #     (action["data"], viewobj.username, viewobj.filename))
                    if action[
                            "server_version"] != viewobj.shadow_server_version:
                        # Can't apply a delta on a mismatched shadow version.
                        delta_ok = False
                        mobwrite_core.LOG.warning(
                            "Shadow version mismatch: %d != %d" %
                            (action["server_version"],
                             viewobj.shadow_server_version))
                    elif action[
                            "client_version"] > viewobj.shadow_client_version:
                        # Client has a version in the future?
                        delta_ok = False
                        mobwrite_core.LOG.warning(
                            "Future delta: %d > %d" %
                            (action["client_version"],
                             viewobj.shadow_client_version))
                    elif action[
                            "client_version"] < viewobj.shadow_client_version:
                        # We've already seen this diff.
                        pass
                        mobwrite_core.LOG.warning(
                            "Repeated delta: %d < %d" %
                            (action["client_version"],
                             viewobj.shadow_client_version))
                    else:
                        # Expand the delta into a diff using the client shadow.
                        try:
                            diffs = mobwrite_core.DMP.diff_fromDelta(
                                viewobj.shadow, action["data"])
                        except ValueError:
                            diffs = None
                            delta_ok = False
                            mobwrite_core.LOG.warning(
                                "Delta failure, expected %d length: '%s@%s'" %
                                (len(viewobj.shadow), viewobj.username,
                                 viewobj.filename))
                        viewobj.shadow_client_version += 1
                        if diffs != None:
                            if access == Access.ReadOnly:
                                output.append("O:" + action["filename"] + "\n")
                            else:
                                # Textobj lock required for read/patch/write cycle.
                                mobwrite_core.LOG.debug(
                                    "text.lock.acquire on %s", textobj.name)
                                textobj.lock.acquire()
                                try:
                                    self.applyPatches(viewobj, diffs, action)
                                finally:
                                    mobwrite_core.LOG.debug(
                                        "text.lock.release on %s",
                                        textobj.name)
                                    textobj.lock.release()

                # Generate output if this is the last action or the username/filename
                # will change in the next iteration.
                if ((action_index + 1 == len(actions))
                        or actions[action_index + 1]["username"] !=
                        viewobj.username
                        or actions[action_index + 1]["filename"] !=
                        viewobj.filename):
                    echo_collaborators = "echo_collaborators" in action
                    output.append(
                        self.generateDiffs(viewobj, last_username,
                                           last_filename,
                                           action["echo_username"],
                                           action["force"], delta_ok,
                                           echo_collaborators))
                    last_username = viewobj.username
                    last_filename = viewobj.filename

            finally:
                mobwrite_core.LOG.debug("view.lock.release on %s@%s",
                                        viewobj.username, viewobj.filename)
                viewobj.lock.release()

        answer = "".join(output)

        return answer

    def generateDiffs(self, viewobj, last_username, last_filename,
                      echo_username, force, delta_ok, echo_collaborators):
        output = []
        if (echo_username and last_username != viewobj.username):
            output.append("u:%s\n" % viewobj.username)
        if (last_filename != viewobj.filename
                or last_username != viewobj.username):
            output.append("F:%d:%s\n" %
                          (viewobj.shadow_client_version, viewobj.filename))

        textobj = viewobj.textobj
        mastertext = textobj.text

        if delta_ok:
            if mastertext is None:
                mastertext = ""
            # Create the diff between the view's text and the master text.
            diffs = mobwrite_core.DMP.diff_main(viewobj.shadow, mastertext)
            mobwrite_core.DMP.diff_cleanupEfficiency(diffs)
            text = mobwrite_core.DMP.diff_toDelta(diffs)
            if force:
                # Client sending 'D' means number, no error.
                # Client sending 'R' means number, client error.
                # Both cases involve numbers, so send back an overwrite delta.
                viewobj.edit_stack.append(
                    (viewobj.shadow_server_version,
                     "D:%d:%s\n" % (viewobj.shadow_server_version, text)))
            else:
                # Client sending 'd' means text, no error.
                # Client sending 'r' means text, client error.
                # Both cases involve text, so send back a merge delta.
                viewobj.edit_stack.append(
                    (viewobj.shadow_server_version,
                     "d:%d:%s\n" % (viewobj.shadow_server_version, text)))
            viewobj.shadow_server_version += 1
            mobwrite_core.LOG.debug("Sent delta for %s@%s", viewobj.username,
                                    viewobj.filename)
            # mobwrite_core.LOG.debug("Sent '%s' delta: '%s@%s'" %
            #     (text, viewobj.username, viewobj.filename))
        else:
            # Error; server could not parse client's delta.
            # Send a raw dump of the text.
            viewobj.shadow_client_version += 1
            if mastertext is None:
                mastertext = ""
                viewobj.edit_stack.append(
                    (viewobj.shadow_server_version,
                     "r:%d:\n" % viewobj.shadow_server_version))
                mobwrite_core.LOG.info("Sent empty raw text: '%s@%s'" %
                                       (viewobj.username, viewobj.filename))
            else:
                # Force overwrite of client.
                text = mastertext
                text = text.encode("utf-8")
                text = urllib.quote(text, "!~*'();/?:@&=+$,# ")
                viewobj.edit_stack.append(
                    (viewobj.shadow_server_version,
                     "R:%d:%s\n" % (viewobj.shadow_server_version, text)))
                mobwrite_core.LOG.info(
                    "Sent %db raw text: '%s@%s'" %
                    (len(text), viewobj.username, viewobj.filename))

        viewobj.shadow = mastertext

        for edit in viewobj.edit_stack:
            output.append(edit[1])

        # Mozilla: We're passing on the first 4 chars of the username here, but
        # it's worth checking if there is still value in doing that
        if echo_collaborators:
            for view in viewobj.textobj.views:
                view.metadata["id"] = view.username[0:4]
                line = "C:" + view.handle + ":" + simplejson.dumps(
                    view.metadata) + "\n"
                output.append(line)

        return "".join(output)
class DaemonMobWrite(mobwrite_core.MobWrite):
  def __init__(self):
    self.persister = Persister()

  def handleRequest(self, text):
    try:
      mobwrite_core.LOG.debug("Incoming: " + text)
      actions = self.parseRequest(text)
      reply = self.doActions(actions)
      mobwrite_core.LOG.debug("Reply: " + reply)
      return reply
    except:
      mobwrite_core.LOG.exception("Error handling request: " + text)
      return "E:all:Processing error"

  def doActions(self, actions):
    output = []
    last_username = None
    last_filename = None

    for action_index in xrange(len(actions)):
      action = actions[action_index]
      mobwrite_core.LOG.debug("action %s = %s", action_index, action)

    for action_index in xrange(len(actions)):
      # Use an indexed loop in order to peek ahead one step to detect
      # username/filename boundaries.
      action = actions[action_index]

      # Close mode doesn't need a filename or handle for the 'close all' case
      # If killing a specific view, then the id is in the 'data'
      if action["mode"] == "close":
        to_close = action.get("data")
        if to_close == "all":
          kill_views_for_user(action["username"])
        elif to_close is not None:
          kill_view(action["username"], to_close)
        continue

      viewobj = fetch_viewobj(action["username"], action["filename"], handle=action["handle"], metadata=action["metadata"], persister=self.persister)
      if viewobj is None:
        # Too many views connected at once.
        # Send back nothing.  Pretend the return packet was lost.
        return ""

      delta_ok = True
      mobwrite_core.LOG.debug("view.lock.acquire on %s@%s", viewobj.username, viewobj.filename)
      viewobj.lock.acquire()
      textobj = viewobj.textobj

      try:
        access = self.persister.check_access(action["filename"], action["handle"])
        if access == Access.Denied:
          name = get_username_from_handle(action["handle"])
          message = "%s does not have access to %s" % (name, action["filename"])
          mobwrite_core.LOG.warning(message)
          output.append("E:" + action["filename"] + ":" + message + "\n")
          continue

        if action["mode"] == "null":
          if access == Access.ReadOnly:
            output.append("O:" + action["filename"] + "\n")
          else:
            # Nullify the text.
            mobwrite_core.LOG.debug("Nullifying: '%s@%s'" %
                (viewobj.username, viewobj.filename))
            mobwrite_core.LOG.debug("text.lock.acquire on %s", textobj.name)
            textobj.lock.acquire()
            try:
              textobj.setText(None)
            finally:
              mobwrite_core.LOG.debug("text.lock.release on %s", textobj.name)
              textobj.lock.release()
            viewobj.nullify()
          continue

        if (action["server_version"] != viewobj.shadow_server_version and
            action["server_version"] == viewobj.backup_shadow_server_version):
          # Client did not receive the last response.  Roll back the shadow.
          mobwrite_core.LOG.warning("Rollback from shadow %d to backup shadow %d" %
              (viewobj.shadow_server_version, viewobj.backup_shadow_server_version))
          viewobj.shadow = viewobj.backup_shadow
          viewobj.shadow_server_version = viewobj.backup_shadow_server_version
          viewobj.edit_stack = []

        # Remove any elements from the edit stack with low version numbers which
        # have been acked by the client.
        x = 0
        while x < len(viewobj.edit_stack):
          if viewobj.edit_stack[x][0] <= action["server_version"]:
            del viewobj.edit_stack[x]
          else:
            x += 1

        if action["mode"] == "raw":
          # It's a raw text dump.
          data = urllib.unquote(action["data"]).decode("utf-8")
          mobwrite_core.LOG.info("Got %db raw text: '%s@%s'" %
              (len(data), viewobj.username, viewobj.filename))
          delta_ok = True
          # First, update the client's shadow.
          viewobj.shadow = data
          viewobj.shadow_client_version = action["client_version"]
          viewobj.shadow_server_version = action["server_version"]
          viewobj.backup_shadow = viewobj.shadow
          viewobj.backup_shadow_server_version = viewobj.shadow_server_version
          viewobj.edit_stack = []
          if access == Access.ReadOnly:
            output.append("O:" + action["filename"] + "\n")
          elif action["force"] or textobj.text == None:
            # Clobber the server's text.
            mobwrite_core.LOG.debug("text.lock.acquire on %s", textobj.name)
            textobj.lock.acquire()
            try:
              if textobj.text != data:
                textobj.setText(data)
                mobwrite_core.LOG.debug("Overwrote content: '%s@%s'" %
                    (viewobj.username, viewobj.filename))
            finally:
              mobwrite_core.LOG.debug("text.lock.release on %s", textobj.name)
              textobj.lock.release()

        elif action["mode"] == "delta":
          # It's a delta.
          mobwrite_core.LOG.debug("Got delta: %s@%s",
              viewobj.username, viewobj.filename)
          # mobwrite_core.LOG.debug("Got '%s' delta: '%s@%s'" %
          #     (action["data"], viewobj.username, viewobj.filename))
          if action["server_version"] != viewobj.shadow_server_version:
            # Can't apply a delta on a mismatched shadow version.
            delta_ok = False
            mobwrite_core.LOG.warning("Shadow version mismatch: %d != %d" %
                (action["server_version"], viewobj.shadow_server_version))
          elif action["client_version"] > viewobj.shadow_client_version:
            # Client has a version in the future?
            delta_ok = False
            mobwrite_core.LOG.warning("Future delta: %d > %d" %
                (action["client_version"], viewobj.shadow_client_version))
          elif action["client_version"] < viewobj.shadow_client_version:
            # We've already seen this diff.
            pass
            mobwrite_core.LOG.warning("Repeated delta: %d < %d" %
                (action["client_version"], viewobj.shadow_client_version))
          else:
            # Expand the delta into a diff using the client shadow.
            try:
              diffs = mobwrite_core.DMP.diff_fromDelta(viewobj.shadow, action["data"])
            except ValueError:
              diffs = None
              delta_ok = False
              mobwrite_core.LOG.warning("Delta failure, expected %d length: '%s@%s'" %
                  (len(viewobj.shadow), viewobj.username, viewobj.filename))
            viewobj.shadow_client_version += 1
            if diffs != None:
              if access == Access.ReadOnly:
                output.append("O:" + action["filename"] + "\n")
              else:
                # Textobj lock required for read/patch/write cycle.
                mobwrite_core.LOG.debug("text.lock.acquire on %s", textobj.name)
                textobj.lock.acquire()
                try:
                  self.applyPatches(viewobj, diffs, action)
                finally:
                  mobwrite_core.LOG.debug("text.lock.release on %s", textobj.name)
                  textobj.lock.release()

        # Generate output if this is the last action or the username/filename
        # will change in the next iteration.
        if ((action_index + 1 == len(actions)) or
            actions[action_index + 1]["username"] != viewobj.username or
            actions[action_index + 1]["filename"] != viewobj.filename):
          echo_collaborators = "echo_collaborators" in action
          output.append(self.generateDiffs(viewobj,
                                           last_username, last_filename,
                                           action["echo_username"], action["force"],
                                           delta_ok, echo_collaborators))
          last_username = viewobj.username
          last_filename = viewobj.filename

      finally:
        mobwrite_core.LOG.debug("view.lock.release on %s@%s", viewobj.username, viewobj.filename)
        viewobj.lock.release()

    answer = "".join(output)

    return answer


  def generateDiffs(self, viewobj, last_username, last_filename,
                    echo_username, force, delta_ok, echo_collaborators):
    output = []
    if (echo_username and last_username != viewobj.username):
      output.append("u:%s\n" %  viewobj.username)
    if (last_filename != viewobj.filename or last_username != viewobj.username):
      output.append("F:%d:%s\n" %
          (viewobj.shadow_client_version, viewobj.filename))

    textobj = viewobj.textobj
    mastertext = textobj.text

    if delta_ok:
      if mastertext is None:
        mastertext = ""
      # Create the diff between the view's text and the master text.
      diffs = mobwrite_core.DMP.diff_main(viewobj.shadow, mastertext)
      mobwrite_core.DMP.diff_cleanupEfficiency(diffs)
      text = mobwrite_core.DMP.diff_toDelta(diffs)
      if force:
        # Client sending 'D' means number, no error.
        # Client sending 'R' means number, client error.
        # Both cases involve numbers, so send back an overwrite delta.
        viewobj.edit_stack.append((viewobj.shadow_server_version,
            "D:%d:%s\n" % (viewobj.shadow_server_version, text)))
      else:
        # Client sending 'd' means text, no error.
        # Client sending 'r' means text, client error.
        # Both cases involve text, so send back a merge delta.
        viewobj.edit_stack.append((viewobj.shadow_server_version,
            "d:%d:%s\n" % (viewobj.shadow_server_version, text)))
      viewobj.shadow_server_version += 1
      mobwrite_core.LOG.debug("Sent delta for %s@%s",
          viewobj.username, viewobj.filename)
      # mobwrite_core.LOG.debug("Sent '%s' delta: '%s@%s'" %
      #     (text, viewobj.username, viewobj.filename))
    else:
      # Error; server could not parse client's delta.
      # Send a raw dump of the text.
      viewobj.shadow_client_version += 1
      if mastertext is None:
        mastertext = ""
        viewobj.edit_stack.append((viewobj.shadow_server_version,
            "r:%d:\n" % viewobj.shadow_server_version))
        mobwrite_core.LOG.info("Sent empty raw text: '%s@%s'" %
            (viewobj.username, viewobj.filename))
      else:
        # Force overwrite of client.
        text = mastertext
        text = text.encode("utf-8")
        text = urllib.quote(text, "!~*'();/?:@&=+$,# ")
        viewobj.edit_stack.append((viewobj.shadow_server_version,
            "R:%d:%s\n" % (viewobj.shadow_server_version, text)))
        mobwrite_core.LOG.info("Sent %db raw text: '%s@%s'" %
            (len(text), viewobj.username, viewobj.filename))

    viewobj.shadow = mastertext

    for edit in viewobj.edit_stack:
      output.append(edit[1])

    # Mozilla: We're passing on the first 4 chars of the username here, but
    # it's worth checking if there is still value in doing that
    if echo_collaborators:
      for view in viewobj.textobj.views:
        view.metadata["id"] = view.username[0:4]
        line = "C:" + view.handle + ":" + simplejson.dumps(view.metadata) + "\n"
        output.append(line)

    return "".join(output)