class AgendavMv

Handle moving (renaming) Agendav users in its database. Agendav has no concept of domains.

Public Instance Methods

mv_user(src, dst) click to toggle source

Move the user src to dst within the Agendav database. This should “rename” him in every table where he is referenced.

This can fail if dst already exists before the move. It should also be an error if the destination domain doesn't exist. But Agendav doesn't know about domains, so we let that slide.

If the source user doesn't exist, we do our best. AgenDAV has a “shares” table that isn't keyed on the username, but rather the principal URL. And its “prefs” table doesn't contain entries for users who have default preferences. As a result, we may need to perform some find/replaces in the “shares” table even if no corresponding user exists in the “prefs” table (which is how we tell if a user exists in AgenDAV). Thus it's not a fatal error if the src user doesn't exist.

@param src [User] the source user to be moved.

@param dst [User] the destination user being moved to.

# File lib/mv/plugins/agendav.rb, line 35
def mv_user(src, dst)
  raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst)

  connection = PG::Connection.new(@db_hash)
  begin
    # The "prefs" table uses the normal username as a key...
    # This should be harmless if the source user does not exist.
    sql_query0 = 'UPDATE prefs SET username = $1 WHERE username = $2;'
    connection.sync_exec_params(sql_query0, [dst.to_s(), src.to_s()])

    # But the "shares" table uses encoded principal URLs. For the
    # "shares" table, we need to do a find/replace on the username
    # with its "@" symbol translated to a "%40".
    encoded_src = src.to_s()['@'] = '%40'
    encoded_dst = dst.to_s()['@'] = '%40'

    # Unlike in the "rm" plugin, we do modify the "calendar" field
    # here. That's because in the usual legitimate use case, the
    # calendar URL will change when a user moves. This will ALSO
    # affect people who name their calendars something like
    # "user%40example.com", but screw those people.
    sql_queries = ['UPDATE shares SET owner=REPLACE(owner, $2, $1);']
    sql_queries << 'UPDATE shares SET calendar=REPLACE(calendar, $2, $1);'
    sql_queries << 'UPDATE shares SET "with"=REPLACE("with", $2, $1);'

    sql_queries.each do |sql_query|
      connection.sync_exec_params(sql_query, [encoded_dst, encoded_src])
    end
  ensure
    # Make sure the connection gets closed even if a query explodes.
    connection.close()
  end
end