Changeset

6254:da4c04df90e3

Merge with daurnimator
author Matthew Wild <mwild1@gmail.com>
date Fri, 23 May 2014 20:37:16 +0100
parents 6233:f400a4cdf352 (current diff) 6253:2df9e7a67e56 (diff)
children 6258:8a01bce29834
files plugins/muc/muc.lib.lua
diffstat 5 files changed, 174 insertions(+), 187 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/muc/history.lib.lua	Sat May 17 18:17:34 2014 +0100
+++ b/plugins/muc/history.lib.lua	Fri May 23 20:37:16 2014 +0100
@@ -11,7 +11,8 @@
 local datetime = require "util.datetime";
 local st = require "util.stanza";
 
-local default_history_length, max_history_length = 20, math.huge;
+local default_history_length = 20;
+local max_history_length = module:get_option_number("max_history_messages", math.huge);
 
 local function set_max_history_length(_max_history_length)
 	max_history_length = _max_history_length or math.huge;
--- a/plugins/muc/lock.lib.lua	Sat May 17 18:17:34 2014 +0100
+++ b/plugins/muc/lock.lib.lua	Fri May 23 20:37:16 2014 +0100
@@ -23,7 +23,10 @@
 end
 
 if lock_rooms then
-	module:hook("muc-room-created", function(event)
+	module:hook("muc-room-pre-create", function(event)
+		-- Older groupchat protocol doesn't lock
+		if not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then return end
+		-- Lock room at creation
 		local room = event.room;
 		lock(room);
 		if lock_room_timeout and lock_room_timeout > 0 then
@@ -33,16 +36,9 @@
 				end
 			end);
 		end
-	end);
+	end, 10);
 end
 
--- Older groupchat protocol doesn't lock
-module:hook("muc-room-pre-create", function(event)
-	if is_locked(event.room) and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then
-		unlock(event.room);
-	end
-end, 10);
-
 -- Don't let users into room while it is locked
 module:hook("muc-occupant-pre-join", function(event)
 	if not event.is_new_room and is_locked(event.room) then -- Deny entry
--- a/plugins/muc/mod_muc.lua	Sat May 17 18:17:34 2014 +0100
+++ b/plugins/muc/mod_muc.lua	Fri May 23 20:37:16 2014 +0100
@@ -6,44 +6,23 @@
 -- COPYING file in the source package for more information.
 --
 
-local array = require "util.array";
-
 if module:get_host_type() ~= "component" then
 	error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0);
 end
 
-local muc_host = module:get_host();
-local muc_name = module:get_option("name");
-if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end
-local restrict_room_creation = module:get_option("restrict_room_creation");
-if restrict_room_creation then
-	if restrict_room_creation == true then
-		restrict_room_creation = "admin";
-	elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then
-		restrict_room_creation = nil;
-	end
-end
-
 local muclib = module:require "muc";
-local muc_new_room = muclib.new_room;
-local persistent = module:require "muc/persistent";
+room_mt = muclib.room_mt; -- Yes, global.
+local iterators = require "util.iterators";
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local st = require "util.stanza";
 local um_is_admin = require "core.usermanager".is_admin;
 local hosts = prosody.hosts;
 
-rooms = {};
-local rooms = rooms;
-local persistent_rooms_storage = module:open_store("persistent");
-local persistent_rooms = persistent_rooms_storage:get() or {};
-local room_configs = module:open_store("config");
-
--- Configurable options
-muclib.set_max_history_length(module:get_option_number("max_history_messages"));
+local rooms = module:shared "rooms";
 
 module:depends("disco");
-module:add_identity("conference", "text", muc_name);
+module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms"));
 module:add_feature("http://jabber.org/protocol/muc");
 module:depends "muc_unique"
 module:require "muc/lock";
@@ -52,47 +31,22 @@
 	return um_is_admin(jid, module.host);
 end
 
-room_mt = muclib.room_mt; -- Yes, global.
-local _set_affiliation = room_mt.set_affiliation;
-local _get_affiliation = room_mt.get_affiliation;
-function muclib.room_mt:get_affiliation(jid)
-	if is_admin(jid) then return "owner"; end
-	return _get_affiliation(self, jid);
-end
-function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reason)
-	if is_admin(jid) then return nil, "modify", "not-acceptable"; end
-	return _set_affiliation(self, actor, jid, affiliation, callback, reason);
+do -- Monkey patch to make server admins room owners
+	local _get_affiliation = room_mt.get_affiliation;
+	function room_mt:get_affiliation(jid)
+		if is_admin(jid) then return "owner"; end
+		return _get_affiliation(self, jid);
+	end
+
+	local _set_affiliation = room_mt.set_affiliation;
+	function room_mt:set_affiliation(actor, jid, ...)
+		if is_admin(jid) then return nil, "modify", "not-acceptable"; end
+		return _set_affiliation(self, actor, jid, ...);
+	end
 end
 
-local function room_save(room, forced)
-	local node = jid_split(room.jid);
-	local is_persistent = persistent.get(room);
-	persistent_rooms[room.jid] = is_persistent;
-	if is_persistent then
-		local history = room._data.history;
-		room._data.history = nil;
-		local data = {
-			jid = room.jid;
-			_data = room._data;
-			_affiliations = room._affiliations;
-		};
-		room_configs:set(node, data);
-		room._data.history = history;
-	elseif forced then
-		room_configs:set(node, nil);
-		if not next(room._occupants) then -- Room empty
-			rooms[room.jid] = nil;
-		end
-	end
-	if forced then persistent_rooms_storage:set(nil, persistent_rooms); end
-end
-
-function create_room(jid)
-	local room = muc_new_room(jid);
-	room.save = room_save;
-	rooms[jid] = room;
-	module:fire_event("muc-room-created", { room = room });
-	return room;
+function track_room(room)
+	rooms[room.jid] = room;
 end
 
 function forget_room(jid)
@@ -103,68 +57,108 @@
 	return rooms[room_jid]
 end
 
-local persistent_errors = false;
-for jid in pairs(persistent_rooms) do
-	local node = jid_split(jid);
-	local data = room_configs:get(node);
-	if data then
-		local room = create_room(jid);
-		room._data = data._data;
-		room._affiliations = data._affiliations;
-	else -- missing room data
-		persistent_rooms[jid] = nil;
-		module:log("error", "Missing data for room '%s', removing from persistent room list", jid);
-		persistent_errors = true;
+function each_room()
+	return iterators.values(rooms);
+end
+
+do -- Persistent rooms
+	local persistent = module:require "muc/persistent";
+	local persistent_rooms_storage = module:open_store("persistent");
+	local persistent_rooms = persistent_rooms_storage:get() or {};
+	local room_configs = module:open_store("config");
+
+	local function room_save(room, forced)
+		local node = jid_split(room.jid);
+		local is_persistent = persistent.get(room);
+		persistent_rooms[room.jid] = is_persistent;
+		if is_persistent then
+			local history = room._data.history;
+			room._data.history = nil;
+			local data = {
+				jid = room.jid;
+				_data = room._data;
+				_affiliations = room._affiliations;
+			};
+			room_configs:set(node, data);
+			room._data.history = history;
+		elseif forced then
+			room_configs:set(node, nil);
+			if not next(room._occupants) then -- Room empty
+				rooms[room.jid] = nil;
+			end
+		end
+		if forced then persistent_rooms_storage:set(nil, persistent_rooms); end
 	end
-end
-if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end
+
+	-- When room is created, over-ride 'save' method
+	module:hook("muc-occupant-pre-create", function(event)
+		event.room.save = room_save;
+	end, 1000);
+
+	-- Automatically destroy empty non-persistent rooms
+	module:hook("muc-occupant-left",function(event)
+		local room = event.room
+		if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room
+			module:fire_event("muc-room-destroyed", { room = room });
+		end
+	end);
 
-local host_room = muc_new_room(muc_host);
-host_room.save = room_save;
-rooms[muc_host] = host_room;
+	local persistent_errors = false;
+	for jid in pairs(persistent_rooms) do
+		local node = jid_split(jid);
+		local data = room_configs:get(node);
+		if data then
+			local room = muclib.new_room(jid);
+			room._data = data._data;
+			room._affiliations = data._affiliations;
+			track_room(room);
+		else -- missing room data
+			persistent_rooms[jid] = nil;
+			module:log("error", "Missing data for room '%s', removing from persistent room list", jid);
+			persistent_errors = true;
+		end
+	end
+	if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end
+end
 
 module:hook("host-disco-items", function(event)
 	local reply = event.reply;
 	module:log("debug", "host-disco-items called");
-	for jid, room in pairs(rooms) do
+	for room in each_room() do
 		if not room:get_hidden() then
-			reply:tag("item", {jid=jid, name=room:get_name()}):up();
+			reply:tag("item", {jid=room.jid, name=room:get_name()}):up();
 		end
 	end
 end);
 
+module:hook("muc-room-pre-create", function(event)
+	track_room(event.room);
+end, -1000);
+
 module:hook("muc-room-destroyed",function(event)
 	local room = event.room
 	forget_room(room.jid)
 end)
 
-module:hook("muc-occupant-left",function(event)
-	local room = event.room
-	if not next(room._occupants) and not persistent.get(room) then -- empty, non-persistent room
-		module:fire_event("muc-room-destroyed", { room = room });
+do
+	local restrict_room_creation = module:get_option("restrict_room_creation");
+	if restrict_room_creation == true then
+		restrict_room_creation = "admin";
 	end
-end);
-
--- Watch presence to create rooms
-local function attempt_room_creation(event)
-	local origin, stanza = event.origin, event.stanza;
-	local room_jid = jid_bare(stanza.attr.to);
-	if stanza.attr.type == nil and
-		get_room_from_jid(room_jid) == nil and
-		(
-			not(restrict_room_creation) or
-			is_admin(stanza.attr.from) or
-			(
+	if restrict_room_creation then
+		local host_suffix = module.host:gsub("^[^%.]+%.", "");
+		module:hook("muc-room-pre-create", function(event)
+			local user_jid = event.stanza.attr.from;
+			if not is_admin(user_jid) and not (
 				restrict_room_creation == "local" and
-				select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")
-			)
-		) then
-		create_room(room_jid);
+				select(2, jid_split(user_jid)) == host_suffix
+			) then
+				origin.send(st.error_reply(stanza, "cancel", "not-allowed"));
+				return true;
+			end
+		end);
 	end
 end
-module:hook("presence/full", attempt_room_creation, -1)
-module:hook("presence/bare", attempt_room_creation, -1)
-module:hook("presence/host", attempt_room_creation, -1)
 
 for event_name, method in pairs {
 	-- Normal room interactions
@@ -192,78 +186,62 @@
 } do
 	module:hook(event_name, function (event)
 		local origin, stanza = event.origin, event.stanza;
-		local room = get_room_from_jid(jid_bare(stanza.attr.to))
+		local room_jid = jid_bare(stanza.attr.to);
+		local room = get_room_from_jid(room_jid);
 		if room == nil then
-			origin.send(st.error_reply(stanza, "cancel", "not-allowed"));
-			return true;
+			-- Watch presence to create rooms
+			if stanza.attr.type == nil and stanza.name == "presence" then
+				room = muclib.new_room(room_jid);
+			else
+				origin.send(st.error_reply(stanza, "cancel", "not-allowed"));
+				return true;
+			end
 		end
 		return room[method](room, origin, stanza);
 	end, -2)
 end
 
-hosts[module:get_host()].muc = { rooms = rooms };
-
-local saved = false;
-module.save = function()
-	saved = true;
-	return {rooms = rooms};
-end
-module.restore = function(data)
-	for jid, oldroom in pairs(data.rooms or {}) do
-		local room = create_room(jid);
-		room._jid_nick = oldroom._jid_nick;
-		room._occupants = oldroom._occupants;
-		room._data = oldroom._data;
-		room._affiliations = oldroom._affiliations;
-	end
-	hosts[module:get_host()].muc = { rooms = rooms };
-end
-
 function shutdown_component()
-	if not saved then
-		local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
-				:tag("status", { code = "332"}):up();
-		for roomjid, room in pairs(rooms) do
-			room:clear(x);
-		end
-		host_room:clear(x);
+	local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+		:tag("status", { code = "332"}):up();
+	for room in each_room() do
+		room:clear(x);
 	end
 end
-module.unload = shutdown_component;
 module:hook_global("server-stopping", shutdown_component);
 
--- Ad-hoc commands
-module:depends("adhoc")
-local t_concat = table.concat;
-local keys = require "util.iterators".keys;
-local adhoc_new = module:require "adhoc".new;
-local adhoc_initial = require "util.adhoc".new_initial_data_form;
-local dataforms_new = require "util.dataforms".new;
+do -- Ad-hoc commands
+	module:depends "adhoc";
+	local t_concat = table.concat;
+	local adhoc_new = module:require "adhoc".new;
+	local adhoc_initial = require "util.adhoc".new_initial_data_form;
+	local array = require "util.array";
+	local dataforms_new = require "util.dataforms".new;
 
-local destroy_rooms_layout = dataforms_new {
-	title = "Destroy rooms";
-	instructions = "Select the rooms to destroy";
+	local destroy_rooms_layout = dataforms_new {
+		title = "Destroy rooms";
+		instructions = "Select the rooms to destroy";
 
-	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" };
-	{ name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"};
-};
+		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" };
+		{ name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"};
+	};
 
-local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function()
-	return { rooms = array.collect(keys(rooms)):sort() };
-end, function(fields, errors)
-	if errors then
-		local errmsg = {};
-		for name, err in pairs(errors) do
-			errmsg[#errmsg + 1] = name .. ": " .. err;
+	local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function()
+		return { rooms = array.collect(each_room):pluck("jid"):sort(); };
+	end, function(fields, errors)
+		if errors then
+			local errmsg = {};
+			for name, err in pairs(errors) do
+				errmsg[#errmsg + 1] = name .. ": " .. err;
+			end
+			return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
 		end
-		return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
-	end
-	for _, room in ipairs(fields.rooms) do
-		rooms[room]:destroy();
-		rooms[room] = nil;
-	end
-	return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") };
-end);
-local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin");
+		for _, room in ipairs(fields.rooms) do
+			get_room_from_jid(room):destroy();
+		end
+		return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") };
+	end);
+	local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin");
 
-module:provides("adhoc", destroy_rooms_desc);
+	module:provides("adhoc", destroy_rooms_desc);
+end
--- a/plugins/muc/muc.lib.lua	Sat May 17 18:17:34 2014 +0100
+++ b/plugins/muc/muc.lib.lua	Fri May 23 20:37:16 2014 +0100
@@ -79,6 +79,10 @@
 	end
 end
 
+function room_mt:has_occupant()
+	return next(self._occupants, nil) ~= nil
+end
+
 function room_mt:get_occupant_by_real_jid(real_jid)
 	local occupant_jid = self:get_occupant_jid(real_jid);
 	if occupant_jid == nil then return nil end
@@ -96,11 +100,21 @@
 			self._jid_nick[real_jid] = nil;
 		end
 	end
-	if occupant.role ~= nil and next(occupant.sessions) then
+
+	local has_live_session = false
+	if occupant.role ~= nil then
 		for real_jid, presence in occupant:each_session() do
-			self._jid_nick[real_jid] = occupant.nick;
+			if presence.attr.type == nil then
+				has_live_session = true
+				self._jid_nick[real_jid] = occupant.nick;
+			end
 		end
-	else
+		if not has_live_session then
+			-- Has no live sessions left; they have left the room.
+			occupant.role = nil
+		end
+	end
+	if not has_live_session then
 		occupant = nil
 	end
 	self._occupants[id] = occupant
@@ -109,10 +123,8 @@
 function room_mt:route_to_occupant(occupant, stanza)
 	local to = stanza.attr.to;
 	for jid, pr in occupant:each_session() do
-		if pr.attr.type ~= "unavailable" then
-			stanza.attr.to = jid;
-			self:route_stanza(stanza);
-		end
+		stanza.attr.to = jid;
+		self:route_stanza(stanza);
 	end
 	stanza.attr.to = to;
 end
@@ -396,7 +408,9 @@
 			local dest_nick;
 			if dest_occupant == nil then -- Session is leaving
 				log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick);
-				orig_occupant.role = nil;
+				if is_last_orig_session then
+					orig_occupant.role = nil;
+				end
 				orig_occupant:set_session(real_jid, stanza);
 			else
 				log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick);
@@ -449,7 +463,7 @@
 			end
 			self:save_occupant(dest_occupant);
 
-			if orig_occupant == nil and is_first_dest_session then
+			if orig_occupant == nil then
 				-- Send occupant list to newly joined user
 				self:send_occupant_list(real_jid, function(nick, occupant)
 					-- Don't include self
@@ -1110,8 +1124,6 @@
 
 local _M = {}; -- module "muc"
 
-_M.set_max_history_length = history.set_max_length;
-
 function _M.new_room(jid, config)
 	return setmetatable({
 		jid = jid;
--- a/plugins/muc/occupant.lib.lua	Sat May 17 18:17:34 2014 +0100
+++ b/plugins/muc/occupant.lib.lua	Fri May 23 20:37:16 2014 +0100
@@ -53,7 +53,7 @@
 -- finds another session to be the primary (there might not be one)
 function occupant_mt:choose_new_primary()
 	for jid, pr in self:each_session() do
-		if pr.attr.type ~= "unavailable" then
+		if pr.attr.type == nil then
 			return jid;
 		end
 	end