Compare commits

...

54 Commits

Author SHA1 Message Date
Badlop 0dbedb69a6 Recompile translations 2015-07-27 11:45:04 +02:00
Badlop 5788609e5c Updated Spanish translation 2015-07-27 11:43:31 +02:00
Badlop 5305b4a82c Updated Hebrew translation (thanks to Isratine Citizen) 2015-07-27 11:41:38 +02:00
Badlop d03d08539b Reuse some strings in WebAdmin's Listening Ports 2015-07-27 11:40:56 +02:00
Paweł Chmielowski 76104cd117 Fix problem with merging values from multiple config files 2015-07-24 15:46:08 +02:00
Christophe Romain e211bf7131 Ensure config snippet is loaded at module installation (EJAB-1741) 2015-07-24 15:09:57 +02:00
Christophe Romain 32fc586c08 Allow contribution to include .yml or .yaml config file 2015-07-24 14:30:31 +02:00
Christophe Romain 8fedc945bf Allow migration of old pubsub items with xmlelement in body (#479) 2015-07-24 13:44:01 +02:00
Christophe Romain d481017746 Merge pull request #642 from weiss/pep-on-caps-update
Also send PEP notifications when local contact updates CAPS
2015-07-24 12:07:22 +02:00
Badlop 19aad464da Provide command for ODBC's convert_to_scram 2015-07-23 13:16:15 +02:00
Paweł Chmielowski 8df134e025 Don't crash web admin when displaying info about websocket using users 2015-07-22 11:54:11 +02:00
Christophe Romain 9091fcb1a1 Let nodetree call default flat plugin now
this fix mistake from previous commit 311fedaa
2015-07-22 11:20:06 +02:00
Christophe Romain 9aa2d92d90 Allow include of simple dependencies (EJAB-1737)(#391)
Either contributed module include dependencies this way
  deps/
    dep1/
      src/
      include/
    dep1/
      src/
      include/

Or includes rebar.config or rebar.config.script:
In this case, only git is supported (if git command available)
and ext_mod checkout code in deps directory.

In both case, only basic built procedure is supported. ext_mod
does not do more than bare compilation like this:
erlc -I include src/*erl
2015-07-22 10:48:44 +02:00
Christophe Romain 311fedaa12 Let flat be default plugin (#609) 2015-07-22 10:37:26 +02:00
Christophe Romain 9c36a9df78 Do not init nodes from mod_pubsub (#609)
Let each plugin do its own initialisation in init_plugins
init_nodes was an old function to be removed.
2015-07-22 08:03:27 +02:00
Christophe Romain b47a27f3ac Merge pull request #608 from weiss/bare-jid-subscription
PubSub: Check for node subscription of bare JID
2015-07-22 07:58:34 +02:00
Evgeny Khramtsov 531638e946 Merge pull request #632 from weiss/increase-max-ack-queue
XEP-0198: Increase default value of "max_ack_queue" option
2015-07-21 12:36:01 +03:00
Evgeny Khramtsov a724893887 Merge pull request #618 from weiss/muc-message-hook
New hook: muc_filter_packet
2015-07-21 12:35:27 +03:00
Evgeniy Khramtsov 7f4c74dec9 Print content types more gracefully 2015-07-17 14:29:11 +03:00
Evgeniy Khramtsov 44cc99d616 Get rid of 'db_type' for mod_blocking in test suite 2015-07-16 13:32:46 +03:00
Badlop 7395cc910e Set direction of resource and connection (#650) 2015-07-15 21:39:13 +02:00
Badlop c53d764119 If local guide.html file not found, redirect to the online guide 2015-07-15 20:45:55 +02:00
Badlop 3eb0b161b2 Update links to Guide in WebAdmin 2015-07-15 20:40:13 +02:00
Badlop 65551afcba Support RTL page direction in WebAdmin for Hebrew (#650) 2015-07-15 19:01:32 +02:00
Badlop f4376671ac Set LTR direction to Raw links (#650) 2015-07-15 18:51:31 +02:00
Badlop c012d7555b Add links in WebAdmin to ejabberd and ProcessOne (#661) 2015-07-15 13:10:12 +02:00
Badlop 9ede414f01 Support more mod_muc_admin translation strings (#659) 2015-07-15 11:54:35 +02:00
Mickael Remond 062d2d696f Update dev section to show erl command-line start command 2015-07-11 11:20:38 +02:00
Evgeniy Khramtsov a53191fed5 Add "complete" attribute to MAM final response (EJABS-2484) 2015-07-10 14:05:47 +03:00
Evgeniy Khramtsov 8e27decdfd Fix mod_mam compatibility with RSM for other backends 2015-07-10 13:59:33 +03:00
Evgeniy Khramtsov 0dfc8ade68 Fix tests related to MAM 2015-07-10 13:57:27 +03:00
Jerome Sautret 1db65e3614 Fix mod_mam compatibility with RSM (only with odbc backend). 2015-07-10 13:56:33 +03:00
Evgeniy Khramtsov a168340838 During halt only shutdown MUC rooms on local node 2015-07-08 15:35:26 +03:00
Jerome Sautret 6604b9efbb Fix MAM tests (EJABS-2480). 2015-07-08 15:34:34 +03:00
Jerome Sautret 2598375051 Changed mam iq get to set as specified by XEP-0313 v0.3 2015-07-08 13:15:14 +03:00
Holger Weiss 0f96414279 Trigger PEP notifications on CAPS updates
Let mod_pubsub send last items whenever a contact updates the entity
capabilities.  This was already done for remote contacts and is now also
done for local contacts.
2015-07-07 09:33:00 +02:00
Holger Weiss edb18deb8f mod_pubsub: Explain caps_update usage in a comment
The reason to use the caps_update hook for sending last items to remote
contacts is probably not obvious.
2015-07-06 23:45:25 +02:00
Mickael Remond 0770252e9b Fix dialyzer warning
The function is call with binary format from ejabberd_c2s.
2015-07-05 13:33:34 +02:00
Evgeniy Khramtsov b0453ea2ce Remove ehyperloglog dependency 2015-07-03 13:18:35 +03:00
Holger Weiss b7d7dc5201 XEP-0198: Increase timeout during resumption
Give gen_fsm:sync_send_all_state_event/3 a little more time to transfer
the (possibly somewhat large) c2s #state during session resumption.
2015-07-03 11:35:22 +02:00
Holger Weiss 5856f6d06a XEP-0198: Increase default "max_ack_queue" value
During login, clients might receive a relatively large number of stanzas
in one go.  For some users, the default value of the "max_ack_queue"
option turned out to be too small in that situation.
2015-07-03 11:35:22 +02:00
Holger Weiss a25051fead New hook: muc_filter_packet
Add a hook that allows for modifying or dropping MUC room messages.

Closes #491.
2015-07-03 11:34:44 +02:00
Holger Weiss 95138864f4 Let Travis grab MySQL 5.6 from repo.mysql.com
We need MySQL 5.6 or newer in order to get support for InnoDB FULLTEXT
Indexes.

Closes #624.
2015-07-03 01:21:22 +02:00
Holger Weiss 4045c848c4 Send notifications on MUC configuration changes
Notify clients when the room configuration changes, as mandated by
XEP-0045, #10.2.1.

Closes #623.
2015-07-02 00:36:16 +02:00
Evgeny Khramtsov 3267d4c923 Merge pull request #628 from joudinet/master
configure.ac: add AC_CONFIG_MACRO_DIR
2015-06-30 12:06:49 +03:00
Johan Oudinet 0e7d2b73be configure.ac: add AC_CONFIG_MACRO_DIR
Otherwise, autoconf fails to find extra macros defined in the m4
directory.
2015-06-30 10:53:45 +02:00
Mickael Remond 0474804d4b Version number must be SemVersion in mix
Semantic version expects versioning with 3 components
2015-06-29 23:32:00 +02:00
Mickael Remond 56034e6ed5 Use https url for git dependencies for consistency 2015-06-29 23:23:31 +02:00
Mickael Remond df57a07dd5 Keep the version hardcoded in mix.exs file
Generated the mix.exs file through configure is not possible when using mix, as
it does not run configure after having downloaded the dependencies.
#621
2015-06-29 23:14:18 +02:00
Evgeniy Khramtsov 3c12d1a960 Do not rely on behaviour info when doing config validation 2015-06-29 22:50:34 +03:00
badlop 87d6ad395f Merge pull request #620 from weiss/omit-non-anon-msg
Omit warning message regarding non-anonymous MUC room
2015-06-29 15:18:04 +02:00
Holger Weiss 7c7e51b6e8 Omit warning message regarding non-anonymous room
Previous versions of XEP-0045 suggested sending a warning message to new
occupants of a non-anonymous MUC room.  The current revision (1.25) says
that a status code of "100" must be returned with the user's initial
presence, instead.  We already do this (in addition to generating the
warning message).

Receiving the warning message each time the client joins the room can
become annoying, especially when reconnections occur frequently (e.g.,
on mobile devices).  So, we omit it, now.
2015-06-28 14:05:55 +02:00
Badlop 4ae1b4db03 When passwords are scrammed, report check_password_hash cannot work (#559) 2015-06-25 13:39:45 +02:00
Holger Weiss 04038c01f7 PubSub: Check for node subscription of bare JID
Don't just check whether the full JID is subscribed when a node
subscription is required to list or publish items.  If the bare JID is
subscribed, these requests are now also accepted.
2015-06-19 00:50:08 +02:00
42 changed files with 3661 additions and 3458 deletions
-1
View File
@@ -41,4 +41,3 @@ XmppAddr.hrl
/test/*.beam
/logs/
/priv/sql
mix.exs
+11
View File
@@ -8,7 +8,18 @@ services:
- riak
before_install:
#
# We need MySQL 5.6 or newer in order to get support for FULLTEXT indexes
# with InnoDB. As soon as Travis ships that version, the following lines
# (except for the "apt-get update" call) can go away.
#
# See: https://github.com/travis-ci/travis-ci/issues/1986
#
- sudo sed -i -e s/table_cache/table_open_cache/ -e /log_slow_queries/d /etc/mysql/my.cnf
- sudo apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5
- sudo add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.6'
- sudo apt-get -qq update
- sudo apt-get -qq -o Dpkg::Options::=--force-confold install mysql-server-5.6
install:
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev
+4
View File
@@ -159,6 +159,10 @@ In order to assist in the development of ejabberd, and particularly the
execution of the test suite, a Vagrant environment is available at
https://github.com/processone/ejabberd-vagrant-dev.
To start ejabberd in development mode from the repository directory, you can
type a command like:
EJABBERD_CONFIG_PATH=ejabberd.yml erl -pa ebin -pa deps/*/ebin -pa test -s ejabberd
Links
-----
+3 -2
View File
@@ -6,6 +6,8 @@ AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0`
REQUIRE_ERLANG_MIN="5.9.1 (Erlang/OTP R15B01)"
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
# Checks for programs.
AC_PROG_MAKE_SET
AC_PROG_INSTALL
@@ -235,8 +237,7 @@ esac],[if test "x$lager" = "x"; then lager=true; fi])
AC_CONFIG_FILES([Makefile
vars.config
src/ejabberd.app.src
mix.exs])
src/ejabberd.app.src])
ENABLEUSER=""
AC_ARG_ENABLE(user,
+16 -16
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "@PACKAGE_VERSION@",
version: "15.06.0",
elixir: "~> 1.0",
elixirc_paths: ["lib"],
compile_path: ".",
@@ -25,21 +25,21 @@ defmodule Ejabberd.Mixfile do
defp deps do
[
{:p1_xml, github: "processone/xml"},
{:p1_logger, github: "processone/p1_logger"},
{:p1_yaml, github: "processone/p1_yaml"},
{:p1_tls, github: "processone/tls"},
{:p1_stringprep, github: "processone/stringprep"},
{:p1_zlib, github: "processone/zlib"},
{:p1_cache_tab, github: "processone/cache_tab"},
{:p1_utils, github: "processone/p1_utils"},
{:p1_iconv, github: "processone/eiconv"},
{:esip, github: "processone/p1_sip"},
{:p1_stun, github: "processone/stun"},
{:ehyperloglog, github: "vaxelfel/eHyperLogLog"},
{:p1_mysql, github: "processone/mysql"},
{:p1_pgsql, github: "processone/pgsql"},
{:eredis, github: "wooga/eredis"}
{:p1_xml, git: "https://github.com/processone/xml"},
{:p1_logger, git: "https://github.com/processone/p1_logger"},
{:p1_yaml, git: "https://github.com/processone/p1_yaml"},
{:p1_tls, git: "https://github.com/processone/tls"},
{:p1_stringprep, git: "https://github.com/processone/stringprep"},
{:p1_zlib, git: "https://github.com/processone/zlib"},
{:p1_cache_tab, git: "https://github.com/processone/cache_tab"},
{:p1_utils, git: "https://github.com/processone/p1_utils"},
{:p1_iconv, git: "https://github.com/processone/eiconv"},
{:esip, git: "https://github.com/processone/p1_sip"},
{:p1_stun, git: "https://github.com/processone/stun"},
{:ehyperloglog, git: "https://github.com/vaxelfel/eHyperLogLog"},
{:p1_mysql, git: "https://github.com/processone/mysql"},
{:p1_pgsql, git: "https://github.com/processone/pgsql"},
{:eredis, git: "https://github.com/wooga/eredis"}
]
end
end
+7 -2
View File
@@ -60,7 +60,6 @@
{"Database Tables Configuration at ","Configuración de tablas de la base de datos en "}.
{"December","diciembre"}.
{"Default users as participants","Los usuarios son participantes por defecto"}.
{"Delete","Eliminar"}.
{"Delete message of the day","Borrar mensaje del dia"}.
{"Delete message of the day on all hosts","Borrar el mensaje del día en todos los dominios"}.
{"Delete Selected","Eliminar los seleccionados"}.
@@ -77,6 +76,7 @@
{"Either approve or decline the voice request.","Aprueba o rechaza la petición de voz."}.
{"ejabberd IRC module","Módulo de IRC para ejabberd"}.
{"ejabberd MUC module","Módulo de MUC para ejabberd"}.
{"ejabberd Multicast service","Servicio Multicast de ejabberd"}.
{"ejabberd Publish-Subscribe module","Módulo de Publicar-Subscribir de ejabberd"}.
{"ejabberd SOCKS5 Bytestreams module","Módulo SOCKS5 Bytestreams para ejabberd"}.
{"ejabberd vCard module","Módulo vCard para ejabberd"}.
@@ -164,6 +164,7 @@
{"Listened Ports at ","Puertos de escucha en "}.
{"Listened Ports","Puertos de escucha"}.
{"List of modules to start","Lista de módulos a iniciar"}.
{"List of rooms","Lista de salas"}.
{"Low level update script","Script de actualización a bajo nivel"}.
{"Make participants list public","La lista de participantes es pública"}.
{"Make room CAPTCHA protected","Proteger la sala con CAPTCHA"}.
@@ -189,6 +190,8 @@
{"Modules at ~p","Módulos en ~p"}.
{"Modules","Módulos"}.
{"Monday","lunes"}.
{"Multicast","Multicast"}.
{"Multi-User Chat","Salas de Charla"}.
{"Name:","Nombre:"}.
{"Name","Nombre"}.
{"Never","Nunca"}.
@@ -241,6 +244,7 @@
{"Path to File","Ruta al fichero"}.
{"Pending","Pendiente"}.
{"Period: ","Periodo: "}.
{"Permanent rooms","Salas permanentes"}.
{"Persist items to storage","Persistir elementos al almacenar"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Ten en cuenta que estas opciones solo harán copia de seguridad de la base de datos Mnesia embebida. Si estás usando ODBC tendrás que hacer también copia de seguridad de tu base de datos SQL."}.
@@ -258,6 +262,7 @@
{"Raw","Crudo"}.
{"Really delete message of the day?","¿Está seguro de quere borrar el mensaje del dia?"}.
{"Register a Jabber account","Registrar una cuenta Jabber"}.
{"Registered nicknames","Apodos registrados"}.
{"Registered Users:","Usuarios registrados:"}.
{"Registered Users","Usuarios registrados"}.
{"Register","Registrar"}.
@@ -338,7 +343,6 @@
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","No importa si usas mayúsculas: macbeth es lo mismo que MacBeth y Macbeth."}.
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Esta página te permite crear una cuenta Jabber este servidor Jabber. Tu JID (Jabber IDentificador) será de la forma: nombredeusuario@servidor. Por favor lee detenidamente las instrucciones para rellenar correctamente los campos."}.
{"This page allows to unregister a Jabber account in this Jabber server.","Esta página te permite borrar tu cuenta Jabber en este servidor Jabber."}.
{"This room is not anonymous","Sala no anónima"}.
{"Thursday","jueves"}.
{"Time delay","Retraso temporal"}.
{"Time","Fecha"}.
@@ -346,6 +350,7 @@
{"Too many unacked stanzas","Demasiados mensajes sin haber reconocido recibirlos"}.
{"To","Para"}.
{"To ~s","A ~s"}.
{"Total rooms","Salas totales"}.
{"Transactions Aborted:","Transacciones abortadas:"}.
{"Transactions Committed:","Transacciones finalizadas:"}.
{"Transactions Logged:","Transacciones registradas:"}.
+435 -407
View File
File diff suppressed because it is too large Load Diff
+101 -68
View File
@@ -11,9 +11,9 @@
{"Add User","הוסף משתמש"}.
{"Administration of ","ניהול של "}.
{"Administration","הנהלה"}.
{"A friendly name for the node","שם ידידותי עבור הממסר"}.
{"A friendly name for the node","שם ידידותי עבור הצומת"}.
{"All activity","כל פעילות"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","להתיר לכתובת JID זו להירשם אל ממסר PubSub זה?"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","להתיר לכתובת JID זו להירשם לצומת PubSub זה?"}.
{"Allow users to change the subject","התר למשתמשים לשנות את הנושא"}.
{"Allow users to query other users","התר למשתמשים לתשאל משתמשים אחרים"}.
{"Allow users to send invites","התר למשתמשים לשלוח הזמנות"}.
@@ -23,13 +23,14 @@
{"Allow visitors to send status text in presence updates","התר למבקרים לשלוח טקסט מצב בעדכוני נוכחות"}.
{"Allow visitors to send voice requests","התר למבקרים לשלוח בקשות ביטוי"}.
{"All Users","כל המשתמשים"}.
{"Announcements","מודעות"}.
{"Announcements","בשורות"}.
{"anyone","לכל אחד"}.
{"April","אפריל"}.
{"August","אוגוסט"}.
{"Backup Management","ניהול גיבוי"}.
{"Backup to File at ","גבה אל קובץ אצל "}.
{"Backup","גבה"}.
{"Backup of ~p","גיבוי של ~p"}.
{"Backup to File at ","גבה לקובץ אצל "}.
{"Backup","גיבוי"}.
{"Bad format","פורמט רע"}.
{"Birthday","יום הולדת"}.
{"CAPTCHA web page","עמוד רשת CAPTCHA"}.
@@ -37,10 +38,10 @@
{"Change User Password","שנה סיסמת משתמש"}.
{"Characters not allowed:","תווים לא מורשים:"}.
{"Chatroom configuration modified","תצורת חדר שיחה שונתה"}.
{"Chatroom is created","חדר שיחה נוצר"}.
{"Chatroom is destroyed","חדר שיחה הרוס"}.
{"Chatroom is started","חדר שיחה מותחל"}.
{"Chatroom is stopped","חדר שיחה הופסק"}.
{"Chatroom is created","חדר שיחה הינו נוצר"}.
{"Chatroom is destroyed","חדר שיחה הינו הרוס"}.
{"Chatroom is started","חדר שיחה הינו מותחל"}.
{"Chatroom is stopped","חדר שיחה הינו מופסק"}.
{"Chatrooms","חדרי שיחה"}.
{"Choose a username and password to register with this server","בחר שם משתמש וסיסמה להירשם עם שרת זה"}.
{"Choose modules to stop","בחר מודולים להפסקה"}.
@@ -54,27 +55,28 @@
{"Connections parameters","פרמטרים של חיבור"}.
{"Country","ארץ"}.
{"CPU Time:","זמן מחשב (CPU):"}.
{"Database Tables at ~p","טבלאות מסד נתונים אצל ~p"}.
{"Database Tables Configuration at ","תצורת טבלאות מסד נתונים אצל "}.
{"Database","מסד נתונים"}.
{"December","דצמבר"}.
{"Default users as participants","משתמשים משתמטים כמשתתפים"}.
{"Delete message of the day on all hosts","מחק הודעת היום בכל המארחים"}.
{"Delete message of the day","מחק הודעת היום"}.
{"Delete message of the day on all hosts","מחק את בשורת היום בכל המארחים"}.
{"Delete message of the day","מחק את בשורת היום"}.
{"Delete Selected","מחק נבחרות"}.
{"Delete User","מחק משתמש"}.
{"Delete","מחק"}.
{"Deliver event notifications","מסירת התראות אירוע"}.
{"Deliver payloads with event notifications","מסירת מטען ייעוד (מטע״ד) יחד עם התראות אירוע"}.
{"Deliver payloads with event notifications","מסירת מטעני ייעוד (מטע״ד) יחד עם התראות אירוע"}.
{"Description:","תיאור:"}.
{"Disc only copy","העתק של תקליטור בלבד"}.
{"Displayed Groups:","קבוצות מוצגות:"}.
{"Don't tell your password to anybody, not even the administrators of the Jabber server.","אל תגלה את הסיסמה שלך לאף אחד, אפילו לא למנהלים של שרת Jabber"}.
{"Dump Backup to Text File at ","השלך גיבוי אל קובץ טקסט אצל "}.
{"Dump to Text File","השלך אל קובץ טקסט"}.
{"Don't tell your password to anybody, not even the administrators of the Jabber server.","אל תגלה את הסיסמה שלך לאף אחד, אפילו לא למנהלים של שרת Jabber."}.
{"Dump Backup to Text File at ","השלך גיבוי לקובץ טקסט אצל "}.
{"Dump to Text File","השלך לקובץ טקסט"}.
{"Edit Properties","ערוך מאפיינים"}.
{"Either approve or decline the voice request.","או שתאשר או שתדחה את בקשת הביטוי."}.
{"ejabberd IRC module","מודול IRC של ejabberd"}.
{"ejabberd MUC module","מודול MUC של ejabberd"}.
{"ejabberd Multicast service","שירות שידור מרובב של ejabberd"}.
{"ejabberd Publish-Subscribe module","מודול Publish-Subscribe של ejabberd"}.
{"ejabberd SOCKS5 Bytestreams module","מודול SOCKS5 Bytestreams של ejabberd"}.
{"ejabberd vCard module","מודול vCard של ejabberd"}.
@@ -86,25 +88,27 @@
{"End User Session","סיים סשן משתמש"}.
{"Enter list of {Module, [Options]}","הזן רשימה של {מודול, [אפשרויות]}"}.
{"Enter nickname you want to register","הזן שם כינוי אשר ברצונך לרושמו"}.
{"Enter path to backup file","הזן נתיב אל קובץ גיבוי"}.
{"Enter path to jabberd14 spool dir","הזן נתיב אל מדור סליל (spool dir) של jabberd14"}.
{"Enter path to jabberd14 spool file","הזן נתיב אל קובץ סליל (spool file) של jabberd14"}.
{"Enter path to text file","הזן נתיב אל קובץ טקסט"}.
{"Enter path to backup file","הזן נתיב לקובץ גיבוי"}.
{"Enter path to jabberd14 spool dir","הזן נתיב למדור סליל (spool dir) של jabberd14"}.
{"Enter path to jabberd14 spool file","הזן נתיב לקובץ סליל (spool file) של jabberd14"}.
{"Enter path to text file","הזן נתיב לקובץ טקסט"}.
{"Enter the text you see","הזן את הטקסט אותו הינך רואה"}.
{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","הזן שם משתמש וקידודים בהם ברצונך להשתמש לשם התחברות אל שרתים של IRC. לחץ 'הבא' כדי להשיג עוד שדות למילוי. לחץ 'סיים' כדי לשמור הגדרות."}.
{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","הזן שם משתמש, קידודים, פורטים וסיסמאות בהם ברצונך להשתמש לשם התחברות אל שרתים של IRC"}.
{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","הזן שם משתמש וקידודים בהם ברצונך להשתמש לצורך התחברות לשרתי IRC. לחץ 'הבא' כדי להשיג עוד שדות למילוי. לחץ 'סיים' כדי לשמור הגדרות."}.
{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","הזן שם משתמש, קידודים, פורטים וסיסמאות בהם ברצונך להשתמש לצורך התחברות לשרתי IRC"}.
{"Erlang Jabber Server","שרת ג׳אבּר Erlang"}.
{"Error","שגיאה"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","דוגמא: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}.
{"Exclude Jabber IDs from CAPTCHA challenge","הוצא כתובות של Jabber מתוך אתגר CAPTCHA"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","ייצוא מידע של כל המשתמשים אשר מצויים בשרת זה אל קבצי PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","ייצוא מידע של כל המשתמשים בתוך מארח אל קבצי PIEFXIS (XEP-0227):"}.
{"Export all tables as SQL queries to a file:","יצא את כל טבלאות בתור שאילתות SQL לתוך קובץ:"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","יצא מידע של כל המשתמשים שבתוך בשרת זה לתוך קבצי PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","יצא מידע של כל המשתמשים שבתוך מארח לתוך קבצי PIEFXIS (XEP-0227):"}.
{"Family Name","שם משפחה"}.
{"February","פברואר"}.
{"Fill in fields to search for any matching Jabber User","מלא את שדות אלו כדי לחפש עבור כל משתמש Jabber מבוקש"}.
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","מלא את התבניות כדי לחפש עבור כל משתמש Jabber מבוקש (באפשרותך להוסיף * בסוף שדה כדי להתאים אל מחרוזת-משנה)"}.
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","מלא את התבניות כדי לחפש עבור כל משתמש Jabber מבוקש (באפשרותך להוסיף * בסוף שדה כדי להתאים למחרוזת-משנה)"}.
{"Friday","יום שישי"}.
{"From ~s","מאת ~s"}.
{"From","מן"}.
{"From","מאת"}.
{"Full Name","שם מלא"}.
{"Get Number of Online Users","השג מספר של משתמשים מקוונים"}.
{"Get Number of Registered Users","השג מספר של משתמשים רשומים"}.
@@ -125,12 +129,12 @@
{"If you want to specify different ports, passwords, encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\", port, \"password\"}'. By default this service use \"~s\" encoding, port ~p, empty password.","אם ברצונך לציין פורטים, סיסמאות, קידודים אחרים עבור שרתים של IRC, מלא את רשימה זו עם ערכים בפורמט '{\"irc server\", \"encoding\", port, \"password\"}'. באופן משתמט שירות זה משתמש בקידוד \"~s\", פורט ~p, סיסמה ריקה."}.
{"Import Directory","ייבוא מדור"}.
{"Import File","ייבוא קובץ"}.
{"Import user data from jabberd14 spool file:","ייבוא נתוני משתמש מתוך קובץ סליל (spool file) של jabberd14:"}.
{"Import user data from jabberd14 spool file:","יבא נתוני משתמש מתוך קובץ סליל (spool file) של jabberd14:"}.
{"Import User from File at ","ייבוא משתמש מתוך קובץ אצל "}.
{"Import users data from a PIEFXIS file (XEP-0227):","ייבוא מידע משתמשים מתוך קובץ PIEFXIS (XEP-0227):"}.
{"Import users data from jabberd14 spool directory:","ייבוא נתוני משתמשים מתוך מדור סליל (spool directory) של jabberd14:"}.
{"Import users data from a PIEFXIS file (XEP-0227):","יבא מידע משתמשים מתוך קובץ PIEFXIS (XEP-0227):"}.
{"Import users data from jabberd14 spool directory:","יבא נתוני משתמשים מתוך מדור סליל (spool directory) של jabberd14:"}.
{"Import Users from Dir at ","ייבוא משתמשים מתוך מדור אצל "}.
{"Import Users From jabberd14 Spool Files","ייבוא משתמשים מתוך קבצי סליל (Spool Files) של jabberd14"}.
{"Import Users From jabberd14 Spool Files","יבא משתמשים מתוך קבצי סליל (Spool Files) של jabberd14"}.
{"Invalid affiliation: ~s","סינוף שגוי: ~s"}.
{"Invalid role: ~s","תפקיד שגוי: ~s"}.
{"IP addresses","כתובות IP"}.
@@ -146,18 +150,22 @@
{"Jabber ID ~s is invalid","מזהה Jabber ~s הינו שגוי"}.
{"Jabber ID","מזהה Jabber"}.
{"January","ינואר"}.
{"Join IRC channel","הצטרף אל ערוץ IRC"}.
{"Join IRC channel","הצטרף לערוץ IRC"}.
{"joins the room","נכנס/ת אל החדר"}.
{"Join the IRC channel here.","הצטרף אל ערוץ IRC כאן."}.
{"Join the IRC channel in this Jabber ID: ~s","הצטרף אל ערוץ IRC במזהה Jabber זה: ~s"}.
{"Join the IRC channel here.","הצטרף לערוץ IRC כאן."}.
{"Join the IRC channel in this Jabber ID: ~s","הצטרף לערוץ IRC במזהה Jabber זה: ~s"}.
{"July","יולי"}.
{"June","יוני"}.
{"Last Activity","פעילות אחרונה"}.
{"Last login","כניסה אחרונה"}.
{"Last month","חודש אחרון"}.
{"Last year","שנה אחרונה"}.
{"leaves the room","עוזב/ת אל החדר"}.
{"leaves the room","עוזב/ת את החדר"}.
{"Listened Ports at ","פורטים מואזנים אצל "}.
{"Listened Ports","פורטים מואזנים"}.
{"List of modules to start","רשימה של מודולים להפעלה"}.
{"List of rooms","רשימה של חדרים"}.
{"Low level update script","תסריט עדכון Low level"}.
{"Make participants list public","הפוך רשימת משתתפים אל פומבית"}.
{"Make room CAPTCHA protected","הפוך חדר אל מוגן CAPTCHA"}.
{"Make room members-only","הפוך חדר אל חברים-בלבד"}.
@@ -168,7 +176,7 @@
{"March","מרץ"}.
{"Maximum Number of Occupants","מספר מרבי של נוכחים"}.
{"Max # of items to persist","מספר מרבי של פריטים לקיבוע"}.
{"Max payload size in bytes","גודל מרבי של מטען הייעוד ביחידות מידה של byte"}.
{"Max payload size in bytes","גודל מרבי של מטען ייעוד (payload) ביחידות מידה של byte"}.
{"May","מאי"}.
{"Members:","חברים:"}.
{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","שנן את הסיסמה שלך, או רשום אותה בנייר שמור במקום בטוח. אצל Jabber אין דרך אוטומטית לשחזר את הסיסמה שלך במידה וזו תישמט מתוך זיכרונך."}.
@@ -178,9 +186,12 @@
{"Minimum interval between voice requests (in seconds)","תדירות מינימלית בין בקשות ביטוי (בשניות)"}.
{"moderators only","לאחראים בלבד"}.
{"Modified modules","מודולים שהותאמו"}.
{"Modules at ~p","מודולים אצל ~p"}.
{"Modules","מודולים"}.
{"Module","מודול"}.
{"Monday","יום שני"}.
{"Multicast","שידור מרובב"}.
{"Multi-User Chat","שיחה מרובת משתמשים"}.
{"Name:","שם:"}.
{"Name","שם"}.
{"Never","אף פעם"}.
@@ -188,17 +199,20 @@
{"Nickname Registration at ","רישום שם כינוי אצל "}.
{"Nickname ~s does not exist in the room","שם כינוי ~s לא קיים בחדר"}.
{"Nickname","שם כינוי"}.
{"No body provided for announce message","לא סופק גוף עבור הודעת בשורה"}.
{"nobody","אף אחד"}.
{"No Data","אין מידע"}.
{"Node ID","מזהה ממסר (NID)"}.
{"Node not found","ממסר לא נמצא"}.
{"Nodes","ממסרים"}.
{"Node ID","מזהה צומת (NID)"}.
{"Node not found","צומת לא נמצא"}.
{"Node ~p","צומת ~p"}.
{"Nodes","צמתים"}.
{"No limit","ללא הגבלה"}.
{"None","אין"}.
{"No resource provided","לא סופק משאב"}.
{"Not Found","לא נמצא"}.
{"Notify subscribers when items are removed from the node","הודע מנויים כאשר פריטים מוסרים מתוך הממסר"}.
{"Notify subscribers when the node configuration changes","הודע מנויים כאשר תצורת הממסר משתנה"}.
{"Notify subscribers when the node is deleted","הודע מנויים כאשר הממסר נמחק"}.
{"Notify subscribers when items are removed from the node","הודע מנויים כאשר פריטים מוסרים מתוך הצומת"}.
{"Notify subscribers when the node configuration changes","הודע מנויים כאשר תצורת הצומת משתנה"}.
{"Notify subscribers when the node is deleted","הודע מנויים כאשר הצומת נמחק"}.
{"November","נובמבר"}.
{"Number of occupants","מספר של נוכחים"}.
{"Number of online users","מספר של משתמשים מקוונים"}.
@@ -211,7 +225,7 @@
{"Online Users:","משתמשים מקוונים:"}.
{"Online Users","משתמשים מקוונים"}.
{"Online","מקוון"}.
{"Only deliver notifications to available users","מסור התראות אל משתמשים זמינים בלבד"}.
{"Only deliver notifications to available users","מסור התראות למשתמשים זמינים בלבד"}.
{"Only moderators and participants are allowed to change the subject in this room","רק אחראים ומשתתפים רשאים לשנות את הנושא בחדר זה"}.
{"Only moderators are allowed to change the subject in this room","רק אחראים רשאים לשנות את הנושא בחדר זה"}.
{"Options","אפשרויות"}.
@@ -220,34 +234,40 @@
{"Outgoing s2s Connections:","חיבורי s2s יוצאים:"}.
{"Outgoing s2s Connections","חיבורי s2s יוצאים"}.
{"Outgoing s2s Servers:","שרתי s2s יוצאים:"}.
{"Packet","חבילת מידע"}.
{"Password ~b","סיסמה ~b"}.
{"Password Verification:","אימות סיסמה:"}.
{"Password Verification","אימות סיסמה"}.
{"Password:","סיסמה:"}.
{"Password","סיסמה"}.
{"Path to Dir","נתיב אל מדור"}.
{"Path to File","נתיב אל קובץ"}.
{"Path to Dir","נתיב למדור"}.
{"Path to File","נתיב לקובץ"}.
{"Pending","ממתינות"}.
{"Period: ","משך זמן: "}.
{"Permanent rooms","חדרים קבועים"}.
{"Persist items to storage","פריטים קבועים לאחסון"}.
{"Ping","פינג"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","נא לשים לב כי אפשרויות אלו יגבו את מסד הנתונים המובנה Mnesia בלבד. אם הינך עושה שימוש במודול ODBC, עליך גם לגבות את מסד הנתונים SQL אשר מצוי ברשותך בנפרד."}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","אנא שים לב כי אפשרויות אלו יגבו את מסד הנתונים המובנה Mnesia בלבד. אם הינך עושה שימוש במודול ODBC, עליך גם לגבות את מסד הנתונים SQL אשר מצוי ברשותך בנפרד."}.
{"Pong","פונג"}.
{"Port ~b","פורט ~b"}.
{"Port","פורט"}.
{"Present real Jabber IDs to","הצג כתובות JID ממשיות"}.
{"private, ","פרטי, "}.
{"Protocol","פרוטוקול"}.
{"Publish-Subscribe","Publish-Subscribe"}.
{"PubSub subscriber request","בקשת מנוי PubSub"}.
{"Purge all items when the relevant publisher goes offline","טיהור כל הפריטים כאשר המפרסם הרלוונטי "}.
{"RAM and disc copy","העתק RAM וגם תקליטור"}.
{"RAM copy","העתק RAM"}.
{"Really delete message of the day?","באמת למחוק את הודעת היום?"}.
{"Raw","גולמי"}.
{"Really delete message of the day?","באמת למחוק את בשורת היום?"}.
{"Register a Jabber account","רשום חשבון Jabber"}.
{"Registered nicknames","שמות כינוי רשומים"}.
{"Registered Users:","משתמשים רשומים:"}.
{"Registered Users","משתמשים רשומים"}.
{"Register","הרשם"}.
{"Registration in mod_irc for ","רישום בתוך mod_irc עבור "}.
{"Remote copy","עותק מרוחק"}.
{"Remote copy","העתק מרוחק"}.
{"Remove All Offline Messages","הסר את כל ההודעות הלא מקוונות"}.
{"Remove User","הסר משתמש"}.
{"Remove","הסר"}.
@@ -269,69 +289,81 @@
{"Roster size","גודל רשימה"}.
{"Roster","רשימה"}.
{"RPC Call Error","שגיאת קריאת RPC"}.
{"Running Nodes","ממסרים שמורצים כעת"}.
{"Running Nodes","צמתים מורצים"}.
{"~s access rule configuration","~s תצורת כללי גישה"}.
{"Saturday","יום שבת"}.
{"Script check","בדיקת תסריט"}.
{"Search Results for ","תוצאות חיפוש עבור "}.
{"Search users in ","חיפוש משתמשים אצל "}.
{"Send announcement to all online users on all hosts","שלח מודעות אל כל המשתמשים המקוונים בכל המארחים"}.
{"Send announcement to all online users","שלח מודעות אל כל המשתמשים המקוונים"}.
{"Send announcement to all users on all hosts","שלח מודעות אל כל המשתמשים בכל המארחים"}.
{"Send announcement to all users","שלח מודעות אל כל המשתמשים"}.
{"Send announcement to all online users on all hosts","שלח בשורה לכל המשתמשים המקוונים בכל המארחים"}.
{"Send announcement to all online users","שלח בשורה לכל המשתמשים המקוונים"}.
{"Send announcement to all users on all hosts","שלח בשורה לכל המשתמשים בכל המארחים"}.
{"Send announcement to all users","שלח בשורה לכל המשתמשים"}.
{"September","ספטמבר"}.
{"Server ~b","שרת ~b"}.
{"Server:","שרת:"}.
{"Set message of the day and send to online users","קבע הודעת היום ושלח אל משתמשים מקוונים"}.
{"Set message of the day on all hosts and send to online users","קבע הודעת היום בכל המארחים ושלח אל משתמשים מקוונים"}.
{"Set message of the day and send to online users","קבע את בשורת היום ושלח למשתמשים מקוונים"}.
{"Set message of the day on all hosts and send to online users","קבע את בשורת היום בכל המארחים ושלח למשתמשים מקוונים"}.
{"Shared Roster Groups","קבוצות רשימה משותפות"}.
{"Show Integral Table","הצג טבלה אינטגרלית"}.
{"Show Ordinary Table","הצג טבלה רגילה"}.
{"Shut Down Service","כבה שירות"}.
{"~s invites you to the room ~s","~s מזמינך אל החדר ~s"}.
{"~s invites you to the room ~s","~s מזמינך לחדר ~s"}.
{"Some Jabber clients can store your password in your computer. Use that feature only if you trust your computer is safe.","לקוחות Jabber מסוימים יכולים לאחסן את הסיסמה שלך על המחשב שלך. השתמש בתכונה זו רק אם אתה סמוך כי המחשב שלך הינו מוגן."}.
{"Specify the access model","ציין את מודל הגישה"}.
{"Specify the event message type","ציין את טיפוס הודעת האירוע"}.
{"Specify the publisher model","ציין את מודל הפרסום"}.
{"~s's Offline Messages Queue","תור הודעות לא מקוונות של ~s"}.
{"Start Modules at ","התחל מודולים אצל "}.
{"Start Modules","התחל מודולים"}.
{"Start","התחל"}.
{"Statistics of ~p","סטטיסטיקות עבור ~p"}.
{"Statistics of ~p","סטטיסטיקות של ~p"}.
{"Statistics","סטטיסטיקה"}.
{"Stop Modules at ","הפסק מודולים אצל "}.
{"Stop Modules","הפסק מודולים"}.
{"Stopped Nodes","ממסרים שנפסקו"}.
{"Stopped Nodes","צמתים שנפסקו"}.
{"Stop","הפסק"}.
{"Storage Type","טיפוס אחסון"}.
{"Store binary backup:","אחסן גיבוי בינארי:"}.
{"Store plain text backup:","אחסן גיבוי טקסט גלוי (plain text):"}.
{"Subject","נושא"}.
{"Submit","שליחה"}.
{"Submitted","נשלח"}.
{"Submit","שלח"}.
{"Subscriber Address","כתובת מנוי"}.
{"Subscription","מִנּוּי"}.
{"Subscription","הרשמה"}.
{"Sunday","יום ראשון"}.
{"The CAPTCHA is valid.","CAPTCHA הינה בתוקף."}.
{"The collections with which a node is affiliated","האוספים עמם צומת מסונף"}.
{"the password is","הסיסמה היא"}.
{"The password of your Jabber account was successfully changed.","סיסמת חשבון Jabber שונתה בהצלחה."}.
{"There was an error changing the password: ","אירעה שגיאה בשינוי הסיסמה: "}.
{"There was an error creating the account: ","אירעה שגיאה ביצירת החשבון: "}.
{"There was an error deleting the account: ","אירעה שגיאה במחיקת החשבון: "}.
{"This IP address is blacklisted in ~s","כתובת IP זו רשומה ברשימה שחורה בתוך ~s"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","חלק זה אינו ער לרישיות: macbeth הינה זהה כשם MacBeth וגם Macbeth."}.
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","עמוד זה מתיר ליצור חשבון Jabber בשרת Jabber זה. כתובת JID (Jabber IDentifier) תגובש באופן של: username@server. נא לקרוא בזהירות את ההוראות למילוי השדות באופן נכון."}.
{"This page allows to unregister a Jabber account in this Jabber server.","עמוד זה מתיר לך לבטל רישום של חשבון Jabber בשרת Jabber זה."}.
{"This room is not anonymous","חדר זה אינו אנונימי"}.
{"Thursday","יום חמישי"}.
{"Time delay","זמן שיהוי"}.
{"Time","זמן"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","יותר מדי (~p) אימותים כושלים מתוך כתובת IP זו (~s). הכתובת תורשה לקבל גישה בשעה ~s UTC"}.
{"Too many unacked stanzas","יותר מדי סטנזות בלי אישורי קבלה"}.
{"To ~s","אל ~s"}.
{"To","אל"}.
{"Transactions Aborted:","טרנזקציות בוטלו:"}.
{"Transactions Committed:","טרנזקציות בוצעו:"}.
{"Transactions Logged:","טרנזקציות נרשמו:"}.
{"Transactions Restarted:","טרנזקציות הותחלו מחדש:"}.
{"Total rooms","חדרים סה״כ"}.
{"To","לכבוד"}.
{"Transactions Aborted:","טרנזקציות שבוטלו:"}.
{"Transactions Committed:","טרנזקציות שבוצעו:"}.
{"Transactions Logged:","טרנזקציות שנרשמו:"}.
{"Transactions Restarted:","טרנזקציות שהותחלו מחדש:"}.
{"Tuesday","יום שלישי"}.
{"Unauthorized","לא מורשה"}.
{"Unregister a Jabber account","בטל רישום חשבון Jabber"}.
{"Unregister","בטל רישום"}.
{"Update message of the day (don't send)","עדכן הודעת היום (אל תשלח)"}.
{"Update message of the day on all hosts (don't send)","עדכן הודעת היום בכל המארחים (אל תשלח)"}.
{"Update message of the day (don't send)","עדכן את בשורת היום (אל תשלח)"}.
{"Update message of the day on all hosts (don't send)","עדכן את בשורת היום בכל המארחים (אל תשלח)"}.
{"Update plan","תכנית עדכון"}.
{"Update ~p","עדכון ~p"}.
{"Update script","תסריט עדכון"}.
{"Update","עדכן"}.
{"Uptime:","זמן פעילות:"}.
{"Use of STARTTLS required","נדרש שימוש של STARTTLS"}.
@@ -339,6 +371,7 @@
{"User Management","ניהול משתמשים"}.
{"Username:","שם משתמש:"}.
{"Users Last Activity","פעילות משתמשים אחרונה"}.
{"User ~s","משתמש ~s"}.
{"Users","משתמשים"}.
{"User","משתמש"}.
{"Validate","הענק תוקף"}.
@@ -356,4 +389,4 @@
{"You need an x:data capable client to search","עליך להשתמש בלקוח אשר מסוגל להבין x:data בכדי לחפש"}.
{"Your Jabber account was successfully created.","חשבון Jabber נוצר בהצלחה."}.
{"Your Jabber account was successfully deleted.","חשבון Jabber נמחק בהצלחה."}.
{"Your messages to ~s are being blocked. To unblock them, visit ~s","ההודעות שלך לערוץ ~s הינן חסומות. כדי למנוע את חסימתן, בקר בכתובת ~s"}.
{"Your messages to ~s are being blocked. To unblock them, visit ~s","ההודעות שלך לערוץ ~s הינן חסומות. כדי לבטל את חסימתן, בקר בכתובת ~s"}.
+533 -535
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -61,7 +61,6 @@ Deps = [{p1_cache_tab, ".*", {git, "https://github.com/processone/cache_tab"}},
{esip, ".*", {git, "https://github.com/processone/p1_sip"}},
{p1_stun, ".*", {git, "https://github.com/processone/stun"}},
{p1_yaml, ".*", {git, "https://github.com/processone/p1_yaml"}},
{ehyperloglog, ".*", {git, "https://github.com/vaxelfel/eHyperLogLog.git"}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils"}}],
ConfigureCmd = fun(Pkg, Flags) ->
+4
View File
@@ -167,6 +167,10 @@ commands() ->
desc = "Export all tables as SQL queries to a file",
module = ejd2odbc, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_scram, tags = [odbc],
desc = "Convert the passwords in 'users' ODBC table to SCRAM",
module = ejabberd_auth_odbc, function = convert_to_scram,
args = [{host, binary}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
+3 -2
View File
@@ -308,7 +308,7 @@ init([{SockMod, Socket}, Opts]) ->
MaxAckQueue = case proplists:get_value(max_ack_queue, Opts) of
Limit when is_integer(Limit), Limit > 0 -> Limit;
infinity -> infinity;
_ -> 500
_ -> 1000
end,
ResumeTimeout = case proplists:get_value(resume_timeout, Opts) of
Timeout when is_integer(Timeout), Timeout >= 0 -> Timeout;
@@ -2025,6 +2025,7 @@ get_conn_type(StateData) ->
p1_tls -> c2s_compressed_tls
end;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket;
_ -> unknown
end.
@@ -3010,7 +3011,7 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
end.
resume_session({Time, PID}) ->
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 3000).
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 5000).
make_resume_id(StateData) ->
{Time, _} = StateData#state.sid,
+12 -13
View File
@@ -206,7 +206,7 @@ get_plain_terms_file(File1, Opts) ->
BinTerms1 = strings_to_binary(Terms),
ModInc = case proplists:get_bool(include_modules_configs, Opts) of
true ->
filelib:wildcard(ext_mod:modules_dir() ++ "/*/conf/*.yaml");
filelib:wildcard(ext_mod:modules_dir() ++ "/*/conf/*.{yml,yaml}");
_ ->
[]
end,
@@ -385,18 +385,19 @@ include_config_files(Terms) ->
include_config_file(File, Opts)
end, lists:flatten(FileOpts)),
SpecialTerms = dict:from_list([{hosts, none}, {listen, none}, {modules, none}]),
SpecialTerms = dict:from_list([{hosts, []}, {listen, []}, {modules, []}]),
PartDict = dict:store(rest, [], SpecialTerms),
Partition = fun(L) ->
lists:foldr(fun({Name, Val} = Pair, Dict) ->
case dict:find(Name, SpecialTerms) of
{ok, _} ->
dict:store(Name, Val, Dict);
dict:append_list(Name, Val, Dict);
_ ->
dict:update(rest, fun(L1) -> [Pair|L1] end, Dict)
dict:append(rest, Pair, Dict)
end;
(Tuple, Dict2) ->
dict:update(rest, fun(L2) -> [Tuple|L2] end, Dict2)
end, dict:from_list([{rest, []}]), L)
dict:append(rest, Tuple, Dict2)
end, PartDict, L)
end,
Merged = dict:merge(fun(_Name, V1, V2) -> V1 ++ V2 end,
@@ -718,16 +719,13 @@ get_modules_with_options() ->
{ok, Mods} = application:get_key(ejabberd, modules),
lists:foldl(
fun(Mod, D) ->
Attrs = Mod:module_info(attributes),
Behavs = proplists:get_value(behaviour, Attrs, []),
case lists:member(ejabberd_config, Behavs) or (Mod == ?MODULE) of
true ->
Opts = Mod:opt_type(''),
case catch Mod:opt_type('') of
Opts when is_list(Opts) ->
lists:foldl(
fun(Opt, Acc) ->
dict:append(Opt, Mod, Acc)
end, D, Opts);
false ->
{'EXIT', {undef, _}} ->
D
end
end, dict:new(), [?MODULE|Mods]).
@@ -1155,7 +1153,8 @@ opt_type(language) ->
opt_type(_) ->
[hosts, language].
-spec may_hide_data(string()) -> string().
-spec may_hide_data(string()) -> string();
(binary()) -> binary().
may_hide_data(Data) ->
case ejabberd_config:get_option(
+46 -35
View File
@@ -167,15 +167,15 @@ process([<<"doc">>, LocalFile], _Request) ->
?DEBUG("Delivering content.", []),
{200, [{<<"Server">>, <<"ejabberd">>}], FileContents};
{error, Error} ->
?DEBUG("Delivering error: ~p", [Error]),
Help = <<" ", FileName/binary,
" - Try to specify the path to ejabberd "
"documentation with the environment variable "
"EJABBERD_DOC_PATH. Check the ejabberd "
"Guide for more information.">>,
?INFO_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
case Error of
eacces -> {403, [], <<"Forbidden", Help/binary>>};
enoent -> {404, [], <<"Not found", Help/binary>>};
enoent -> {307, [{<<"Location">>, <<"http://docs.ejabberd.im/admin/guide/configuration/">>}], <<"Not found", Help/binary>>};
_Else ->
{404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>}
end
@@ -296,7 +296,7 @@ make_xhtml(Els, Host, Node, Lang, JID) ->
#xmlel{name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
{<<"xml:lang">>, Lang}, {<<"lang">>, Lang}],
{<<"xml:lang">>, Lang}, {<<"lang">>, Lang}]++direction(Lang),
children =
[#xmlel{name = <<"head">>, attrs = [],
children =
@@ -340,8 +340,15 @@ make_xhtml(Els, Host, Node, Lang, JID) ->
[{xmlcdata, <<"">>}])]),
?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
[?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
[?XC(<<"p">>,
<<"ejabberd (c) 2002-2015 ProcessOne">>)])])])]}}.
[?XE(<<"p">>,
[?AC(<<"https://www.ejabberd.im/">>, <<"ejabberd">>),
?C(<<" (c) 2002-2015 ">>),
?AC(<<"https://www.process-one.net/">>, <<"ProcessOne">>)]
)])])])]}}.
direction(ltr) -> [{<<"dir">>, <<"ltr">>}];
direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}];
direction(_) -> [].
get_base_path(global, cluster) -> <<"/admin/">>;
get_base_path(Host, cluster) ->
@@ -510,7 +517,7 @@ css(Host) ->
"0px;\n}\n\nh3 {\n color: #000044;\n "
" font-family: Verdana, Arial, Helvetica, "
"sans-serif; \n font-size: 10pt;\n "
"font-weight: bold;\n text-align: left;\n "
"font-weight: bold;\n "
" padding-top: 20px;\n padding-bottom: "
"2px;\n margin-top: 0px;\n margin-bottom: "
"0px;\n}\n\n#content a:link {\n color: "
@@ -590,7 +597,7 @@ logo_fill() ->
process_admin(global,
#request{path = [], auth = {_, _, AJID},
lang = Lang}) ->
make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>,
make_xhtml((?H1GL((?T(<<"Administration">>)), <<"">>,
<<"Contents">>))
++
[?XE(<<"ul">>,
@@ -659,7 +666,7 @@ process_admin(Host,
[{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
<<"ACLDefinition">>, <<"ACL Definition">>))
<<"acl-definition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -668,7 +675,7 @@ process_admin(Host,
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"acls">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
@@ -695,7 +702,7 @@ process_admin(Host,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
<<"ACLDefinition">>, <<"ACL Definition">>))
<<"acl-definition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -703,9 +710,9 @@ process_admin(Host,
nothing -> []
end
++
[?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
[?XAE(<<"p">>, direction(ltr), [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[acls_to_xhtml(ACLs), ?BR,
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>),
@@ -761,7 +768,7 @@ process_admin(Host,
[{{access, '$1', '$2'}}]}]),
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
<<"AccessRights">>, <<"Access Rights">>))
<<"access-rights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -770,7 +777,7 @@ process_admin(Host,
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"access">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
@@ -794,7 +801,7 @@ process_admin(Host,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
<<"AccessRights">>, <<"Access Rights">>))
<<"access-rights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -802,10 +809,10 @@ process_admin(Host,
nothing -> []
end
++
[?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
[?XAE(<<"p">>, direction(ltr), [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[access_rules_to_xhtml(AccessRules, Lang), ?BR,
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>)])],
@@ -853,7 +860,7 @@ process_admin(global,
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
<<"virtualhost">>, <<"Virtual Hosting">>))
<<"virtual-hosting">>, <<"Virtual Hosting">>))
++ Res,
global, Lang, AJID);
process_admin(Host,
@@ -1550,18 +1557,24 @@ user_info(User, Server, Query, Lang) ->
c2s_compressed_tls ->
<<"tls+zlib">>;
http_bind ->
<<"http-bind">>
<<"http-bind">>;
websocket ->
<<"websocket">>;
_ ->
<<"unknown">>
end,
<<" (", ConnS/binary,
<<ConnS/binary,
"://",
(jlib:ip_to_list(IP))/binary,
":",
(jlib:integer_to_binary(Port))/binary,
"#",
(jlib:atom_to_binary(Node))/binary,
")">>
(jlib:atom_to_binary(Node))/binary>>
end,
?LI([?C((<<R/binary, FIP/binary>>))])
case direction(Lang) of
[{_, <<"rtl">>}] -> ?LI([?C((<<FIP/binary, " - ", R/binary>>))]);
_ -> ?LI([?C((<<R/binary, " - ", FIP/binary>>))])
end
end,
lists:sort(Resources))))]
end,
@@ -1872,9 +1885,7 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
[?XRES(<<(?T(<<"Error">>))/binary, ": ",
(list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
end,
(?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])),
<<"list-eja-commands">>,
<<"List of ejabberd Commands">>))
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))]
++
ResS ++
[?XCT(<<"p">>,
@@ -2036,7 +2047,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
[]])),
H1String = <<(?T(<<"Listened Ports at ">>))/binary,
(iolist_to_binary(atom_to_list(Node)))/binary>>,
(?H1GL(H1String, <<"listened">>, <<"Listening Ports">>))
(?H1GL(H1String, <<"listening-ports">>, <<"Listening Ports">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2064,7 +2075,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
NewModules = lists:sort(rpc:call(Node, gen_mod,
loaded_modules_with_opts, [Host])),
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
(?H1GL(H1String, <<"modoverview">>,
(?H1GL(H1String, <<"modules-overview">>,
<<"Modules Overview">>))
++
case Res of
@@ -2381,18 +2392,18 @@ node_ports_to_xhtml(Ports, Lang) ->
[?INPUTS(<<"text">>,
<<"module", SSPort/binary>>,
SModule, <<"15">>)]),
?XE(<<"td">>,
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SSPort/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
<<"35">>, SOptsClean)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"add", SSPort/binary>>,
<<"Update">>)]),
<<"Restart">>)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"delete", SSPort/binary>>,
<<"Delete">>)])])
<<"Stop">>)])])
end,
Ports)
++
@@ -2407,12 +2418,12 @@ node_ports_to_xhtml(Ports, Lang) ->
?XE(<<"td">>,
[?INPUTS(<<"text">>, <<"modulenew">>, <<"">>,
<<"15">>)]),
?XE(<<"td">>,
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>,
<<"[]">>)]),
?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
[?INPUTT(<<"submit">>, <<"addnew">>,
<<"Add New">>)])])]))]).
<<"Start">>)])])]))]).
make_netprot_html(NetProt) ->
?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}],
@@ -2524,7 +2535,7 @@ node_modules_to_xhtml(Modules, Lang) ->
40),
?XE(<<"tr">>,
[?XC(<<"td">>, SModule),
?XE(<<"td">>,
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SModule/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
<<"40">>, SOpts)]),
@@ -2543,7 +2554,7 @@ node_modules_to_xhtml(Modules, Lang) ->
[?XE(<<"tr">>,
[?XE(<<"td">>,
[?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]),
?XE(<<"td">>,
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>,
<<"[]">>)]),
?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
+77 -4
View File
@@ -164,6 +164,7 @@ install(Package) when is_binary(Package) ->
case compile_and_install(Module, Attrs) of
ok ->
code:add_patha(module_ebin_dir(Module)),
ejabberd_config:reload_file(),
ok;
Error ->
delete_path(module_lib_dir(Module)),
@@ -182,7 +183,8 @@ uninstall(Package) when is_binary(Package) ->
code:purge(Module),
code:delete(Module),
code:del_path(module_ebin_dir(Module)),
delete_path(module_lib_dir(Module));
delete_path(module_lib_dir(Module)),
ejabberd_config:reload_file();
false ->
{error, not_installed}
end.
@@ -445,9 +447,14 @@ compile_and_install(Module, Spec) ->
true ->
{ok, Dir} = file:get_cwd(),
file:set_cwd(SrcDir),
Result = case compile(Module, Spec, LibDir) of
ok -> install(Module, Spec, LibDir);
Error -> Error
Result = case compile_deps(Module, Spec, LibDir) of
ok ->
case compile(Module, Spec, LibDir) of
ok -> install(Module, Spec, LibDir);
Error -> Error
end;
Error ->
Error
end,
file:set_cwd(Dir),
Result;
@@ -459,6 +466,35 @@ compile_and_install(Module, Spec) ->
end
end.
compile_deps(_Module, _Spec, DestDir) ->
case filelib:is_dir("deps") of
true -> ok;
false -> fetch_rebar_deps()
end,
Ebin = filename:join(DestDir, "ebin"),
filelib:ensure_dir(filename:join(Ebin, ".")),
Result = lists:foldl(fun(Dep, Acc) ->
Inc = filename:join(Dep, "include"),
Src = filename:join(Dep, "src"),
Options = [{outdir, Ebin}, {i, Inc}],
[file:copy(App, Ebin) || App <- filelib:wildcard(Src++"/*.app")],
Acc++[case compile:file(File, Options) of
{ok, _} -> ok;
{ok, _, _} -> ok;
{ok, _, _, _} -> ok;
error -> {error, {compilation_failed, File}};
Error -> Error
end
|| File <- filelib:wildcard(Src++"/*.erl")]
end, [], filelib:wildcard("deps/*")),
case lists:dropwhile(
fun(ok) -> true;
(_) -> false
end, Result) of
[] -> ok;
[Error|_] -> Error
end.
compile(_Module, _Spec, DestDir) ->
Ebin = filename:join(DestDir, "ebin"),
filelib:ensure_dir(filename:join(Ebin, ".")),
@@ -470,6 +506,7 @@ compile(_Module, _Spec, DestDir) ->
Options = [{outdir, Ebin}, {i, "include"}, {i, EjabInc},
verbose, report_errors, report_warnings]
++ Logger ++ ExtLib,
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
Result = [case compile:file(File, Options) of
{ok, _} -> ok;
{ok, _, _} -> ok;
@@ -504,6 +541,42 @@ install(Module, Spec, DestDir) ->
Error -> Error
end.
%% -- minimalist rebar spec parser, only support git
fetch_rebar_deps() ->
case rebar_deps("rebar.config")++rebar_deps("rebar.config.script") of
[] ->
ok;
Deps ->
filelib:ensure_dir(filename:join("deps", ".")),
lists:foreach(fun({_App, Cmd}) ->
os:cmd("cd deps; "++Cmd++"; cd ..")
end, Deps)
end.
rebar_deps(Script) ->
case file:script(Script) of
{ok, Config} when is_list(Config) ->
[rebar_dep(Dep) || Dep <- proplists:get_value(deps, Config, [])];
{ok, {deps, Deps}} ->
[rebar_dep(Dep) || Dep <- Deps];
_ ->
[]
end.
rebar_dep({App, _, {git, Url}}) ->
{App, "git clone "++Url++" "++filename:basename(App)};
rebar_dep({App, _, {git, Url, {branch, Ref}}}) ->
{App, "git clone -n "++Url++" "++filename:basename(App)++
"; (cd "++filename:basename(App)++
"; git checkout -q origin/"++Ref++")"};
rebar_dep({App, _, {git, Url, {tag, Ref}}}) ->
{App, "git clone -n "++Url++" "++filename:basename(App)++
"; (cd "++filename:basename(App)++
"; git checkout -q "++Ref++")"};
rebar_dep({App, _, {git, Url, Ref}}) ->
{App, "git clone -n "++Url++" "++filename:basename(App)++
"; (cd "++filename:basename(App)++
"; git checkout -q "++Ref++")"}.
%% -- YAML spec parser
consult(File) ->
+7 -3
View File
@@ -515,12 +515,16 @@ set_password(User, Host, Password) ->
%% Copied some code from ejabberd_commands.erl
check_password_hash(User, Host, PasswordHash, HashMethod) ->
AccountPass = ejabberd_auth:get_password_s(User, Host),
AccountPassHash = case HashMethod of
"md5" -> get_md5(AccountPass);
"sha" -> get_sha(AccountPass);
AccountPassHash = case {AccountPass, HashMethod} of
{A, _} when is_tuple(A) -> scrammed;
{_, "md5"} -> get_md5(AccountPass);
{_, "sha"} -> get_sha(AccountPass);
_ -> undefined
end,
case AccountPassHash of
scrammed ->
?ERROR_MSG("Passwords are scrammed, and check_password_hash can not work.", []),
throw(passwords_scrammed_command_cannot_work);
undefined -> error;
PasswordHash -> ok;
_ -> error
+18 -21
View File
@@ -243,27 +243,24 @@ c2s_presence_in(C2SState,
error -> gb_trees:empty()
end,
Caps = read_caps(Els),
{CapsUpdated, NewRs} = case Caps of
nothing when Insert == true -> {false, Rs};
_ when Insert == true ->
case gb_trees:lookup(LFrom, Rs) of
{value, Caps} -> {false, Rs};
none ->
{true,
gb_trees:insert(LFrom, Caps,
Rs)};
_ ->
{true,
gb_trees:update(LFrom, Caps, Rs)}
end;
_ -> {false, gb_trees:delete_any(LFrom, Rs)}
end,
if CapsUpdated ->
ejabberd_hooks:run(caps_update, To#jid.lserver,
[From, To,
get_features(To#jid.lserver, Caps)]);
true -> ok
end,
NewRs = case Caps of
nothing when Insert == true -> Rs;
_ when Insert == true ->
case gb_trees:lookup(LFrom, Rs) of
{value, Caps} -> Rs;
none ->
ejabberd_hooks:run(caps_add, To#jid.lserver,
[From, To,
get_features(To#jid.lserver, Caps)]),
gb_trees:insert(LFrom, Caps, Rs);
_ ->
ejabberd_hooks:run(caps_update, To#jid.lserver,
[From, To,
get_features(To#jid.lserver, Caps)]),
gb_trees:update(LFrom, Caps, Rs)
end;
_ -> gb_trees:delete_any(LFrom, Rs)
end,
ejabberd_c2s:set_aux_field(caps_resources, NewRs,
C2SState);
true -> C2SState
+10 -3
View File
@@ -167,10 +167,17 @@ initialize(Host, Opts) ->
?DEFAULT_CONTENT_TYPE),
ContentTypes = build_list_content_types(
gen_mod:get_opt(content_types, Opts,
fun(L) when is_list(L) -> L end,
[]),
fun(L) when is_list(L) ->
lists:map(
fun({K, V}) ->
{iolist_to_binary(K),
iolist_to_binary(V)}
end, L)
end, []),
?DEFAULT_CONTENT_TYPES),
?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++
?INFO_MSG("known content types: ~s",
[str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes],
<<", ">>)]),
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
CustomHeaders, DefaultContentType, ContentTypes}.
+186 -131
View File
@@ -34,7 +34,8 @@
-export([start/2, stop/1]).
-export([user_send_packet/4, user_receive_packet/5,
process_iq/3, remove_user/2, mod_opt_type/1]).
process_iq_v0_2/3, process_iq_v0_3/3, remove_user/2,
mod_opt_type/1]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
@@ -64,13 +65,13 @@ start(Host, Opts) ->
init_db(DBType, Host),
init_cache(DBType, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_TMP, ?MODULE, process_iq, IQDisc),
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MAM_TMP, ?MODULE, process_iq, IQDisc),
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_0, ?MODULE, process_iq, IQDisc),
?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MAM_0, ?MODULE, process_iq, IQDisc),
?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
user_receive_packet, 500),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
@@ -197,13 +198,11 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
Pkt
end.
process_iq(#jid{lserver = LServer} = From,
% Query archive v0.2
process_iq_v0_2(#jid{lserver = LServer} = From,
#jid{lserver = LServer} = To,
#iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
Fs = case NS of
?NS_MAM_TMP ->
lists:flatmap(
Fs = lists:flatmap(
fun(#xmlel{name = <<"start">>} = El) ->
[{<<"start">>, [xml:get_tag_cdata(El)]}];
(#xmlel{name = <<"end">>} = El) ->
@@ -218,9 +217,16 @@ process_iq(#jid{lserver = LServer} = From,
[{<<"set">>, SubEl}];
(_) ->
[]
end, SubEl#xmlel.children);
?NS_MAM_0 ->
case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
end, SubEl#xmlel.children),
process_iq(From, To, IQ, SubEl, Fs);
process_iq_v0_2(From, To, IQ) ->
process_iq(From, To, IQ).
% Query archive v0.3
process_iq_v0_3(#jid{lserver = LServer} = From,
#jid{lserver = LServer} = To,
#iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
Fs = case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
{#xmlel{} = XData, false} ->
jlib:parse_xdata_submit(XData);
@@ -230,34 +236,16 @@ process_iq(#jid{lserver = LServer} = From,
[{<<"set">>, SubEl}];
{false, false} ->
[]
end
end,
case catch lists:foldl(
fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
{{_, _, _} = jlib:datetime_string_to_timestamp(Data),
End, With, RSM};
({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
{Start,
{_, _, _} = jlib:datetime_string_to_timestamp(Data),
With, RSM};
({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM};
({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End,
{room, jlib:jid_tolower(jlib:string_to_jid(Data))},
RSM};
({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, {text, Data}, RSM};
({<<"set">>, El}, {Start, End, With, _}) ->
{Start, End, With, jlib:rsm_decode(El)};
(_, Acc) ->
Acc
end, {none, [], none, none}, Fs) of
{'EXIT', _} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
{Start, End, With, RSM} ->
select_and_send(From, To, Start, End, With, RSM, IQ)
end;
process_iq(From, To, IQ, SubEl, Fs);
process_iq_v0_3(From, To, IQ) ->
process_iq(From, To, IQ).
%%%===================================================================
%%% Internal functions
%%%===================================================================
% Preference setting (both v0.2 & v0.3)
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
@@ -289,9 +277,34 @@ process_iq(#jid{luser = LUser, lserver = LServer},
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
process_iq(From, To, IQ, SubEl, Fs) ->
case catch lists:foldl(
fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
{{_, _, _} = jlib:datetime_string_to_timestamp(Data),
End, With, RSM};
({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
{Start,
{_, _, _} = jlib:datetime_string_to_timestamp(Data),
With, RSM};
({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM};
({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End,
{room, jlib:jid_tolower(jlib:string_to_jid(Data))},
RSM};
({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, {text, Data}, RSM};
({<<"set">>, El}, {Start, End, With, _}) ->
{Start, End, With, jlib:rsm_decode(El)};
(_, Acc) ->
Acc
end, {none, [], none, none}, Fs) of
{'EXIT', _} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
{Start, End, With, RSM} ->
select_and_send(From, To, Start, End, With, RSM, IQ)
end.
should_archive(#xmlel{name = <<"message">>} = Pkt) ->
case {xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs),
xml:get_subtag_cdata(Pkt, <<"body">>)} of
@@ -505,10 +518,10 @@ select_and_send(#jid{lserver = LServer} = From,
DBType).
select_and_send(From, To, Start, End, With, RSM, IQ, DBType) ->
{Msgs, Count} = select_and_start(From, To, Start, End, With,
RSM, DBType),
{Msgs, IsComplete, Count} = select_and_start(From, To, Start, End, With,
RSM, DBType),
SortedMsgs = lists:keysort(2, Msgs),
send(From, To, SortedMsgs, RSM, Count, IQ).
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
select_and_start(From, _To, StartUser, End, With, RSM, DB) ->
{JidRequestor, Start, With2} = case With of
@@ -525,21 +538,41 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
Start, End, With, RSM, mnesia) ->
MS = make_matchspec(LUser, LServer, Start, End, With),
Msgs = mnesia:dirty_select(archive_msg, MS),
FilteredMsgs = filter_by_rsm(Msgs, RSM),
{FilteredMsgs, IsComplete} = filter_by_rsm(Msgs, RSM),
Count = length(Msgs),
{lists:map(
fun(Msg) ->
{Msg#archive_msg.id,
jlib:binary_to_integer(Msg#archive_msg.id),
msg_to_el(Msg, JidRequestor)}
end, FilteredMsgs), Count};
end, FilteredMsgs), IsComplete, Count};
select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
Start, End, With, RSM, {odbc, Host}) ->
{Query, CountQuery} = make_sql_query(LUser, LServer,
{Query, CountQuery} = make_sql_query(LUser, LServer,
Start, End, With, RSM),
% XXX TODO from XEP-0313:
% To conserve resources, a server MAY place a reasonable limit on
% how many stanzas may be pushed to a client in one request. If a
% query returns a number of stanzas greater than this limit and
% the client did not specify a limit using RSM then the server
% should return a policy-violation error to the client.
case {ejabberd_odbc:sql_query(Host, Query),
ejabberd_odbc:sql_query(Host, CountQuery)} of
{{selected, _, Res}, {selected, _, [[Count]]}} ->
{Max, Direction} = case RSM of
#rsm_in{max = M, direction = D} -> {M, D};
_ -> {undefined, undefined}
end,
{Res1, IsComplete} =
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before ->
{lists:nthtail(1, Res), false};
true ->
{lists:sublist(Res, Max), false}
end;
true ->
{Res, true}
end,
{lists:map(
fun([TS, XML, PeerBin]) ->
#xmlel{} = El = xml_stream:parse_element(XML),
@@ -550,9 +583,9 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
packet = El,
peer = PeerJid},
JidRequestor)}
end, Res), jlib:binary_to_integer(Count)};
_ ->
{[], 0}
end, Res1), IsComplete, jlib:binary_to_integer(Count)};
_ ->
{[], false, 0}
end.
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer},
@@ -580,14 +613,19 @@ maybe_update_from_to(Pkt, JidRequestor, Peer) ->
_ -> Pkt
end.
send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) ->
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
QID = xml:get_tag_attr_s(<<"queryid">>, SubEl),
NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
QIDAttr = if QID /= <<>> ->
[{<<"queryid">>, QID}];
true ->
[]
end,
[{<<"queryid">>, QID}];
true ->
[]
end,
CompleteAttr = if NS == ?NS_MAM_TMP ->
[];
NS == ?NS_MAM_0 ->
[{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
end,
Els = lists:map(
fun({ID, _IDInt, El}) ->
#xmlel{name = <<"message">>,
@@ -596,7 +634,7 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) ->
{<<"id">>, ID}|QIDAttr],
children = [El]}]}
end, Msgs),
RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr, NS),
RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
case NS of
?NS_MAM_TMP ->
lists:foreach(
@@ -618,55 +656,57 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) ->
end.
make_rsm_out(_Msgs, none, _Count, _QIDAttr, ?NS_MAM_TMP) ->
make_rsm_out(_Msgs, none, _Count, _Attrs, ?NS_MAM_TMP) ->
[];
make_rsm_out(_Msgs, none, _Count, QIDAttr, ?NS_MAM_0) ->
[#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|QIDAttr]}];
make_rsm_out([], #rsm_in{}, Count, QIDAttr, NS) ->
make_rsm_out(_Msgs, none, _Count, Attrs, ?NS_MAM_0) ->
[#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|Attrs]}];
make_rsm_out([], #rsm_in{}, Count, Attrs, NS) ->
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr],
children = jlib:rsm_encode(#rsm_out{count = Count})}];
make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, QIDAttr, NS) ->
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
children = jlib:rsm_encode(#rsm_out{count = Count})}];
make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, Attrs, NS) ->
{LastID, _, _} = lists:last(Msgs),
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr],
children = jlib:rsm_encode(
#rsm_out{first = FirstID, count = Count,
last = LastID})}].
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
children = jlib:rsm_encode(
#rsm_out{first = FirstID, count = Count,
last = LastID})}].
filter_by_rsm(Msgs, none) ->
Msgs;
filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max =< 0 ->
[];
{Msgs, true};
filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
{[], true};
filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
NewMsgs = case Direction of
aft ->
lists:filter(
fun(#archive_msg{id = I}) ->
I > ID
end, Msgs);
before ->
lists:foldl(
fun(#archive_msg{id = I} = Msg, Acc) when I < ID ->
[Msg|Acc];
(_, Acc) ->
Acc
end, [], Msgs);
_ ->
Msgs
end,
aft when ID /= <<"">> ->
lists:filter(
fun(#archive_msg{id = I}) ->
I > ID
end, Msgs);
before when ID /= <<"">> ->
lists:foldl(
fun(#archive_msg{id = I} = Msg, Acc) when I < ID ->
[Msg|Acc];
(_, Acc) ->
Acc
end, [], Msgs);
before when ID == <<"">> ->
lists:reverse(Msgs);
_ ->
Msgs
end,
filter_by_max(NewMsgs, Max).
filter_by_max(Msgs, undefined) ->
Msgs;
{Msgs, true};
filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
lists:sublist(Msgs, Len);
{lists:sublist(Msgs, Len), length(Msgs) =< Len};
filter_by_max(_Msgs, _Junk) ->
[].
{[], true}.
make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
ets:fun2ms(
@@ -705,45 +745,43 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
RSM#rsm_in.direction,
RSM#rsm_in.id};
none ->
{none, none, none}
{none, none, <<>>}
end,
LimitClause = if is_integer(Max), Max >= 0 ->
[<<" limit ">>, jlib:integer_to_binary(Max)];
true ->
[]
end,
[<<" limit ">>, jlib:integer_to_binary(Max+1)];
true ->
[]
end,
WithClause = case With of
{text, <<>>} ->
[];
{text, Txt} ->
[<<" and match (txt) against ('">>,
ejabberd_odbc:escape(Txt), <<"')">>];
{_, _, <<>>} ->
[<<" and bare_peer='">>,
ejabberd_odbc:escape(jlib:jid_to_string(With)),
<<"'">>];
{_, _, _} ->
[<<" and peer='">>,
ejabberd_odbc:escape(jlib:jid_to_string(With)),
<<"'">>];
none ->
[]
end,
DirectionClause = case catch jlib:binary_to_integer(ID) of
I when is_integer(I), I >= 0 ->
case Direction of
before ->
[<<" and timestamp < ">>, ID,
<<" order by timestamp desc">>];
aft ->
[<<" and timestamp > ">>, ID,
<<" order by timestamp asc">>];
_ ->
[]
end;
_ ->
[]
end,
{text, <<>>} ->
[];
{text, Txt} ->
[<<" and match (txt) against ('">>,
ejabberd_odbc:escape(Txt), <<"')">>];
{_, _, <<>>} ->
[<<" and bare_peer='">>,
ejabberd_odbc:escape(jlib:jid_to_string(With)),
<<"'">>];
{_, _, _} ->
[<<" and peer='">>,
ejabberd_odbc:escape(jlib:jid_to_string(With)),
<<"'">>];
none ->
[]
end,
PageClause = case catch jlib:binary_to_integer(ID) of
I when is_integer(I), I >= 0 ->
case Direction of
before ->
[<<" AND timestamp < ">>, ID];
aft ->
[<<" AND timestamp > ">>, ID];
_ ->
[]
end;
_ ->
[]
end,
StartClause = case Start of
{_, _, _} ->
[<<" and timestamp >= ">>,
@@ -759,11 +797,28 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
[]
end,
SUser = ejabberd_odbc:escape(LUser),
{[<<"select timestamp, xml, peer from archive where username='">>,
SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++
DirectionClause ++ LimitClause ++ [<<";">>],
[<<"select count(*) from archive where username='">>,
SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++ [<<";">>]}.
Query = [<<"SELECT timestamp, xml, peer"
" FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, StartClause, EndClause,
PageClause],
QueryPage =
case Direction of
before ->
% ID can be empty because of
% XEP-0059: Result Set Management
% 2.5 Requesting the Last Page in a Result Set
[<<"SELECT timestamp, xml, peer FROM (">>, Query,
<<" ORDER BY timestamp DESC ">>,
LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
_ ->
[Query, <<" ORDER BY timestamp ASC ">>,
LimitClause, <<";">>]
end,
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
+2 -1
View File
@@ -115,7 +115,8 @@ shutdown_rooms(Host) ->
Rooms = mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1',
pid = '$2'},
[{'==', {element, 2, '$1'}, MyHost}],
[{'==', {element, 2, '$1'}, MyHost},
{'==', {node, '$2'}, node()}],
['$2']}]),
[Pid ! shutdown || Pid <- Rooms],
Rooms.
+7 -7
View File
@@ -241,8 +241,8 @@ web_menu_host(Acc, _Host, Lang) ->
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
Res = [?XC(<<"h1">>, <<"Multi-User Chat">>),
?XC(<<"h3">>, <<"Statistics">>),
Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>),
?XCT(<<"h3">>, <<"Statistics">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)),
?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
@@ -301,22 +301,22 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
<<"Persistent">>,
<<"Logging">>,
<<"Just created">>,
<<"Title">>],
<<"Room title">>],
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
NCS = jlib:integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
?ACT(<<"?sort=", NCS/binary>>, <<"<">>),
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
?C(<<" ">>),
?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]),
?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
{TD, Num_column+1}
end,
1,
Titles),
[?XC(<<"h1">>, <<"Multi-User Chat">>),
?XC(<<"h2">>, <<"Rooms">>),
[?XCT(<<"h1">>, <<"Multi-User Chat">>),
?XCT(<<"h2">>, <<"Chatrooms">>),
?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>, Titles_TR)]
+60 -39
View File
@@ -947,20 +947,32 @@ process_groupchat_message(From,
end,
case IsAllowed of
true ->
send_multiple(
jlib:jid_replace_resource(StateData#state.jid, FromNick),
StateData#state.server_host,
StateData#state.users,
Packet),
NewStateData2 = case has_body_or_subject(Packet) of
true ->
add_message_to_history(FromNick, From,
Packet,
NewStateData1);
false ->
NewStateData1
end,
{next_state, normal_state, NewStateData2};
case
ejabberd_hooks:run_fold(muc_filter_packet,
StateData#state.server_host,
Packet,
[StateData,
StateData#state.jid,
From, FromNick])
of
drop ->
{next_state, normal_state, StateData};
NewPacket ->
send_multiple(jlib:jid_replace_resource(StateData#state.jid,
FromNick),
StateData#state.server_host,
StateData#state.users,
Packet),
NewStateData2 = case has_body_or_subject(Packet) of
true ->
add_message_to_history(FromNick, From,
Packet,
NewStateData1);
false ->
NewStateData1
end,
{next_state, normal_state, NewStateData2}
end;
_ ->
Err = case
(StateData#state.config)#config.allow_change_subj
@@ -1853,31 +1865,6 @@ add_new_user(From, Nick,
NewState = add_user_presence(From, Packet,
add_online_user(From, Nick, Role,
StateData)),
if not (NewState#state.config)#config.anonymous ->
WPacket = #xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"groupchat">>}],
children =
[#xmlel{name = <<"body">>,
attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"This room is not anonymous">>)}]},
#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
?NS_MUC_USER}],
children =
[#xmlel{name =
<<"status">>,
attrs =
[{<<"code">>,
<<"100">>}],
children =
[]}]}]},
ejabberd_router:route(StateData#state.jid, From, WPacket);
true -> ok
end,
send_existing_presences(From, NewState),
send_new_presence(From, NewState),
Shift = count_stanza_shift(Nick, Els, NewState),
@@ -3757,6 +3744,7 @@ set_xoption([_ | _Opts], _Config) ->
{error, ?ERR_BAD_REQUEST}.
change_config(Config, StateData) ->
send_config_change_info(Config, StateData),
NSD = StateData#state{config = Config},
case {(StateData#state.config)#config.persistent,
Config#config.persistent}
@@ -3777,6 +3765,39 @@ change_config(Config, StateData) ->
_ -> {result, [], NSD}
end.
send_config_change_info(Config, #state{config = Config}) -> ok;
send_config_change_info(New, #state{config = Old} = StateData) ->
Codes = case {Old#config.logging, New#config.logging} of
{false, true} -> [<<"170">>];
{true, false} -> [<<"171">>];
_ -> []
end
++
case {Old#config.anonymous, New#config.anonymous} of
{true, false} -> [<<"172">>];
{false, true} -> [<<"173">>];
_ -> []
end
++
case Old#config{anonymous = New#config.anonymous,
logging = New#config.logging} of
New -> [];
_ -> [<<"104">>]
end,
StatusEls = [#xmlel{name = <<"status">>,
attrs = [{<<"code">>, Code}],
children = []} || Code <- Codes],
Message = #xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"groupchat">>},
{<<"id">>, randoms:get_string()}],
children = [#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
children = StatusEls}]},
send_multiple(StateData#state.jid,
StateData#state.server_host,
StateData#state.users,
Message).
remove_nonmembers(StateData) ->
lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
Affiliation = get_affiliation(JID, SD),
+20 -13
View File
@@ -62,7 +62,7 @@
-define(PEPNODE, <<"pep">>).
%% exports for hooks
-export([presence_probe/3, caps_update/3,
-export([presence_probe/3, caps_add/3, caps_update/3,
in_subscription/6, out_subscription/4,
on_user_offline/3, remove_user/2,
disco_local_identity/5, disco_local_features/5,
@@ -293,6 +293,8 @@ init([ServerHost, Opts]) ->
?MODULE, remove_user, 50),
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:add(caps_add, ServerHost,
?MODULE, caps_add, 80),
ejabberd_hooks:add(caps_update, ServerHost,
?MODULE, caps_update, 80),
ejabberd_hooks:add(disco_sm_identity, ServerHost,
@@ -312,7 +314,6 @@ init([ServerHost, Opts]) ->
pubsub_migrate:update_node_database(Host, ServerHost),
pubsub_migrate:update_state_database(Host, ServerHost),
pubsub_migrate:update_lastitem_database(Host, ServerHost),
init_nodes(Host, ServerHost, NodeTree, Plugins),
{_, State} = init_send_loop(ServerHost),
{ok, State}.
@@ -386,14 +387,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost),
ok.
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
case lists:member(<<"hometree">>, Plugins) of
true ->
create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>),
create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), <<"hometree">>);
false -> ok
end.
send_loop(State) ->
receive
{presence, JID, Pid} ->
@@ -683,12 +676,24 @@ disco_items(Host, Node, From) ->
%% presence hooks handling functions
%%
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
when Host =/= S ->
%% When a remote contact goes online while the local user is offline, the
%% remote contact won't receive last items from the local user even if
%% ignore_pep_from_offline is set to false. To work around this issue a bit,
%% we'll also send the last items to remote contacts when the local user
%% connects. That's the reason to use the caps_add hook instead of the
%% presence_probe_hook for remote contacts: The latter is only called when a
%% contact becomes available; the former is also executed when the local
%% user goes online (because that triggers the contact to send a presence
%% packet with CAPS).
presence(Host, {presence, U, S, [R], JID});
caps_update(_From, _To, _Feature) ->
caps_add(_From, _To, _Feature) ->
ok.
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
presence(Host, {presence, U, S, [R], JID}).
presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
presence(S, {presence, JID, Pid}),
presence(S, {presence, U, S, [R], JID});
@@ -700,7 +705,7 @@ presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} =
presence(S, {presence, U, S, [R], JID});
presence_probe(_Host, _JID, _Pid) ->
%% ignore presence_probe from remote contacts,
%% those are handled via caps_update
%% those are handled via caps_add
ok.
presence(ServerHost, Presence) ->
@@ -872,6 +877,8 @@ terminate(_Reason,
ejabberd_router:unregister_route(Host),
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:delete(caps_add, ServerHost,
?MODULE, caps_add, 80),
ejabberd_hooks:delete(caps_update, ServerHost,
?MODULE, caps_update, 80),
ejabberd_hooks:delete(disco_sm_identity, ServerHost,
+29 -29
View File
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -86,89 +86,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
node_hometree:create_node(Nidx, Owner).
node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
node_hometree:delete_node(Removed).
node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_hometree:purge_node(Nidx, Owner).
node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
node_hometree:get_entity_affiliations(Host, Owner).
node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
node_hometree:get_node_affiliations(Nidx).
node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_hometree:get_affiliation(Nidx, Owner).
node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree:set_affiliation(Nidx, Owner, Affiliation).
node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
node_hometree:get_node_subscriptions(Nidx).
node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_hometree:get_subscriptions(Nidx, Owner).
node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_hometree:get_states(Nidx).
node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
node_hometree:get_state(Nidx, JID).
node_flat:get_state(Nidx, JID).
set_state(State) ->
node_hometree:set_state(State).
node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
node_hometree:get_items(Nidx, From, RSM).
node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree:get_items(Nidx, JID, AccessModel,
node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
node_hometree:get_item(Nidx, ItemId).
node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree:set_item(Item).
node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
+29 -29
View File
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
node_hometree:create_node(Nidx, Owner).
node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
node_hometree:delete_node(Removed).
node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_hometree:purge_node(Nidx, Owner).
node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
node_hometree:get_entity_affiliations(Host, Owner).
node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
node_hometree:get_node_affiliations(Nidx).
node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_hometree:get_affiliation(Nidx, Owner).
node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree:set_affiliation(Nidx, Owner, Affiliation).
node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
node_hometree:get_node_subscriptions(Nidx).
node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_hometree:get_subscriptions(Nidx, Owner).
node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_hometree:get_states(Nidx).
node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
node_hometree:get_state(Nidx, JID).
node_flat:get_state(Nidx, JID).
set_state(State) ->
node_hometree:set_state(State).
node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
node_hometree:get_items(Nidx, From, RSM).
node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree:get_items(Nidx, JID, AccessModel,
node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
node_hometree:get_item(Nidx, ItemId).
node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree:set_item(Item).
node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
+2 -2
View File
@@ -179,7 +179,7 @@ get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
node_hometree:node_to_path(Node).
path_to_node(Path) ->
node_flat:path_to_node(Path).
node_hometree:path_to_node(Path).
+711 -48
View File
@@ -24,6 +24,12 @@
%%% @end
%%% ====================================================================
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
%%% as a developer basis and reference to build its own custom pubsub node
%%% types.</p>
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-module(node_flat).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -40,9 +46,10 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
set_state/1, get_items/7, get_items/3, get_item/7,
set_state/1, get_items/6, get_items/2,
get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1]).
path_to_node/1, can_fetch_item/2, is_subscribed/1]).
init(_Host, _ServerHost, _Opts) ->
pubsub_subscription:init(),
@@ -60,18 +67,55 @@ init(_Host, _ServerHost, _Opts) ->
end,
ok.
terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
terminate(_Host, _ServerHost) ->
ok.
options() ->
node_hometree:options().
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{purge_offline, false},
{persist_items, true},
{max_items, ?MAXITEMS},
{subscribe, true},
{access_model, open},
{roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
{presence_based_delivery, false}].
features() ->
node_hometree:features().
[<<"create-nodes">>,
<<"auto-create">>,
<<"access-authorize">>,
<<"delete-nodes">>,
<<"delete-items">>,
<<"get-pending">>,
<<"instant-nodes">>,
<<"manage-subscriptions">>,
<<"modify-affiliations">>,
<<"multi-subscribe">>,
<<"outcast-affiliation">>,
<<"persistent-items">>,
<<"publish">>,
<<"publish-only-affiliation">>,
<<"purge-nodes">>,
<<"retract-items">>,
<<"retrieve-affiliations">>,
<<"retrieve-items">>,
<<"retrieve-subscriptions">>,
<<"subscribe">>,
<<"subscription-notifications">>,
<<"subscription-options">>].
%% use same code as node_hometree, but do not limite node to
%% the home/localhost/user/... hierarchy
%% any node is allowed
%% @doc Checks if the current user has the permission to create the requested node
%% <p>In flat node, any unused node name is allowed. The access parameter is also
%% checked. This parameter depends on the value of the
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
Allowed = case LOwner of
@@ -83,88 +127,684 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
node_hometree:create_node(Nidx, Owner).
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
set_state(#pubsub_state{stateid = {OwnerKey, Nidx},
affiliation = owner}),
{result, {default, broadcast}}.
delete_node(Removed) ->
node_hometree:delete_node(Removed).
delete_node(Nodes) ->
Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
lists:map(fun (S) -> {J, S} end, Ss)
end,
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
{result, States} = get_states(Nidx),
lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
del_items(Nidx, Items),
del_state(Nidx, LJID)
end, States),
{PubsubNode, lists:flatmap(Tr, States)}
end, Nodes),
{result, {default, broadcast, Reply}}.
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
%% <p>The mechanism works as follow:
%% <ul>
%% <li>The main PubSub module prepares the subscription and passes the
%% result of the preparation as a record.</li>
%% <li>This function gets the prepared record and several other parameters and
%% can decide to:<ul>
%% <li>reject the subscription;</li>
%% <li>allow it as is, letting the main module perform the database
%% persistance;</li>
%% <li>allow it, modifying the record. The main module will store the
%% modified record;</li>
%% <li>allow it, but perform the needed persistance operations.</li></ul>
%% </li></ul></p>
%% <p>The selected behaviour depends on the return parameter:
%% <ul>
%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
%% subscription will actually be performed.</li>
%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
%% parameter contains an error, no subscription will be performed.</li>
%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
%% passed in parameter <tt>SubscribeResult</tt>.</li>
%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
%% no further persistance operation will be performed. This case is used,
%% when the plugin module is doing the persistance by itself or when it want
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(Nidx, Sender, Subscriber,
AccessModel, SendLast, PresenceSubscription,
RosterGroup, Options).
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
GenState = get_state(Nidx, GenKey),
SubState = case SubKey of
GenKey -> GenState;
_ -> get_state(Nidx, SubKey)
end,
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
PendingSubscription = lists:any(fun
({pending, _}) -> true;
(_) -> false
end,
Subscriptions),
Owner = Affiliation == owner,
if not Authorized ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
(Affiliation == outcast) or (Affiliation == publish_only) ->
{error, ?ERR_FORBIDDEN};
PendingSubscription ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
(AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
(AccessModel == roster) and (not RosterGroup) and (not Owner) ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
(AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
{error,
?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
%%ForbiddenAnonymous ->
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
true ->
SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
NewSub = case AccessModel of
authorize -> pending;
_ -> subscribed
end,
set_state(SubState#pubsub_state{subscriptions =
[{NewSub, SubId} | Subscriptions]}),
case {NewSub, SendLast} of
{subscribed, never} ->
{result, {default, subscribed, SubId}};
{subscribed, _} ->
{result, {default, subscribed, SubId, send_last}};
{_, _} ->
{result, {default, pending, SubId}}
end
end.
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
GenState = get_state(Nidx, GenKey),
SubState = case SubKey of
GenKey -> GenState;
_ -> get_state(Nidx, SubKey)
end,
Subscriptions = lists:filter(fun
({_Sub, _SubId}) -> true;
(_SubId) -> false
end,
SubState#pubsub_state.subscriptions),
SubIdExists = case SubId of
<<>> -> false;
Binary when is_binary(Binary) -> true;
_ -> false
end,
if
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
{error, ?ERR_FORBIDDEN};
%% Entity did not specify SubId
%%SubId == "", ?? ->
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%% Invalid subscription identifier
%%InvalidSubId ->
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% Requesting entity is not a subscriber
Subscriptions == [] ->
{error,
?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
%% Subid supplied, so use that.
SubIdExists ->
Sub = first_in_list(fun
({_, S}) when S == SubId -> true;
(_) -> false
end,
SubState#pubsub_state.subscriptions),
case Sub of
{value, S} ->
delete_subscriptions(SubKey, Nidx, [S], SubState),
{result, default};
false ->
{error,
?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
end;
%% Asking to remove all subscriptions to the given node
SubId == all ->
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
{result, default};
%% No subid supplied, but there's only one matching subscription
length(Subscriptions) == 1 ->
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
{result, default};
%% No subid and more than one possible subscription match.
true ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
pubsub_subscription:delete_subscription(SubKey, Nidx, SubId),
Acc -- [{Subscription, SubId}]
end, SubState#pubsub_state.subscriptions, Subscriptions),
case {SubState#pubsub_state.affiliation, NewSubs} of
{none, []} -> del_state(Nidx, SubKey);
_ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
%% @doc <p>Publishes the item passed as parameter.</p>
%% <p>The mechanism works as follow:
%% <ul>
%% <li>The main PubSub module prepares the item to publish and passes the
%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
%% <li>reject the publication;</li>
%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
%% <li>allow it, but perform the needed persistance operations.</li></ul>
%% </li></ul></p>
%% <p>The selected behaviour depends on the return parameter:
%% <ul>
%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
%% publication is actually performed.</li>
%% <li><tt>true</tt>: Publication operation is allowed, based on the
%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
%% parameter contains an error, no subscription will actually be
%% performed.</li>
%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
%% in parameter <tt>Item</tt>. The persistance will be performed by the main
%% module.</li>
%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
%% no further persistance operation will be performed. This case is used,
%% when the plugin module is doing the persistance by itself or when it want
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
SubState = case SubKey of
GenKey -> GenState;
_ -> get_state(Nidx, SubKey)
end,
Affiliation = GenState#pubsub_state.affiliation,
Subscribed = case PublishModel of
subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
is_subscribed(SubState#pubsub_state.subscriptions);
_ -> undefined
end,
if not ((PublishModel == open) or
(PublishModel == publishers) and
((Affiliation == owner)
or (Affiliation == publisher)
or (Affiliation == publish_only))
or (Subscribed == true)) ->
{error, ?ERR_FORBIDDEN};
true ->
if MaxItems > 0 ->
Now = now(),
PubId = {Now, SubKey},
Item = case get_item(Nidx, ItemId) of
{result, OldItem} ->
OldItem#pubsub_item{modification = PubId,
payload = Payload};
_ ->
#pubsub_item{itemid = {ItemId, Nidx},
creation = {Now, GenKey},
modification = PubId,
payload = Payload}
end,
Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
{result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
set_item(Item),
set_state(GenState#pubsub_state{items = NI}),
{result, {default, broadcast, OI}};
true ->
{result, {default, broadcast, []}}
end
end.
%% @doc <p>This function is used to remove extra items, most notably when the
%% maximum number of items has been reached.</p>
%% <p>This function is used internally by the core PubSub module, as no
%% permission check is performed.</p>
%% <p>In the default plugin module, the oldest items are removed, but other
%% rules can be used.</p>
%% <p>If another PubSub plugin wants to delegate the item removal (and if the
%% plugin is using the default pubsub storage), it can implements this function like this:
%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
remove_extra_items(_Nidx, unlimited, ItemIds) ->
{result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
del_items(Nidx, OldItems),
{result, {NewItems, OldItems}}.
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p>
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
#pubsub_state{affiliation = Affiliation, items = Items} = GenState,
Allowed = Affiliation == publisher orelse
Affiliation == owner orelse
PublishModel == open orelse
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}}} -> true;
_ -> false
end,
if not Allowed ->
{error, ?ERR_FORBIDDEN};
true ->
case lists:member(ItemId, Items) of
true ->
del_item(Nidx, ItemId),
set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
{result, {default, broadcast}};
false ->
case Affiliation of
owner ->
{result, States} = get_states(Nidx),
lists:foldl(fun
(#pubsub_state{items = PI} = S, Res) ->
case lists:member(ItemId, PI) of
true ->
Nitems = lists:delete(ItemId, PI),
del_item(Nidx, ItemId),
set_state(S#pubsub_state{items = Nitems}),
{result, {default, broadcast}};
false ->
Res
end;
(_, Res) ->
Res
end,
{error, ?ERR_ITEM_NOT_FOUND}, States);
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end
end.
purge_node(Nidx, Owner) ->
node_hometree:purge_node(Nidx, Owner).
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
case GenState of
#pubsub_state{affiliation = owner} ->
{result, States} = get_states(Nidx),
lists:foreach(fun
(#pubsub_state{items = []}) ->
ok;
(#pubsub_state{items = Items} = S) ->
del_items(Nidx, Items),
set_state(S#pubsub_state{items = []})
end,
States),
{result, {default, broadcast}};
_ ->
{error, ?ERR_FORBIDDEN}
end.
%% @doc <p>Return the current affiliations for the given user</p>
%% <p>The default module reads affiliations in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
%% table, it should return an empty list, as the affiliation will be read by
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
node_hometree:get_entity_affiliations(Host, Owner).
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = mod_pubsub:tree(Host),
Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
_ -> Acc
end
end,
[], States),
{result, Reply}.
get_node_affiliations(Nidx) ->
node_hometree:get_node_affiliations(Nidx).
{result, States} = get_states(Nidx),
Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
{result, lists:map(Tr, States)}.
get_affiliation(Nidx, Owner) ->
node_hometree:get_affiliation(Nidx, Owner).
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
#pubsub_state{affiliation = Affiliation} = get_state(Nidx, GenKey),
{result, Affiliation}.
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree:set_affiliation(Nidx, Owner, Affiliation).
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
case {Affiliation, GenState#pubsub_state.subscriptions} of
{none, []} -> del_state(Nidx, GenKey);
_ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
end.
%% @doc <p>Return the current subscriptions for the given user</p>
%% <p>The default module reads subscriptions in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
%% table, it should return an empty list, as the affiliation will be read by
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = case SubKey of
GenKey ->
mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
_ ->
mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
++
mnesia:match_object(#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
end,
NodeTree = mod_pubsub:tree(Host),
Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {Host, _}} = Node ->
lists:foldl(fun ({Sub, SubId}, Acc2) ->
[{Node, Sub, SubId, J} | Acc2]
end,
Acc, Ss);
_ ->
Acc
end
end,
[], States),
{result, Reply}.
get_node_subscriptions(Nidx) ->
node_hometree:get_node_subscriptions(Nidx).
{result, States} = get_states(Nidx),
Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
case Subscriptions of
[_ | _] ->
lists:foldl(fun ({S, SubId}, Acc) ->
[{J, S, SubId} | Acc]
end,
[], Subscriptions);
[] ->
[];
_ ->
[{J, none}]
end
end,
{result, lists:flatmap(Tr, States)}.
get_subscriptions(Nidx, Owner) ->
node_hometree:get_subscriptions(Nidx, Owner).
SubKey = jlib:jid_tolower(Owner),
SubState = get_state(Nidx, SubKey),
{result, SubState#pubsub_state.subscriptions}.
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
SubKey = jlib:jid_tolower(Owner),
SubState = get_state(Nidx, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
{_, []} ->
case Subscription of
none ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
_ ->
new_subscription(Nidx, Owner, Subscription, SubState)
end;
{<<>>, [{_, SID}]} ->
case Subscription of
none -> unsub_with_subid(Nidx, SID, SubState);
_ -> replace_subscription({Subscription, SID}, SubState)
end;
{<<>>, [_ | _]} ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
_ ->
case Subscription of
none -> unsub_with_subid(Nidx, SubId, SubState);
_ -> replace_subscription({Subscription, SubId}, SubState)
end
end.
replace_subscription(NewSub, SubState) ->
NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
set_state(SubState#pubsub_state{subscriptions = NewSubs}).
replace_subscription(_, [], Acc) -> Acc;
replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
new_subscription(Nidx, Owner, Sub, SubState) ->
SubId = pubsub_subscription:add_subscription(Owner, Nidx, []),
Subs = SubState#pubsub_state.subscriptions,
set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}),
{Sub, SubId}.
unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, Nidx, SubId),
NewSubs = [{S, Sid}
|| {S, Sid} <- SubState#pubsub_state.subscriptions,
SubId =/= Sid],
case {NewSubs, SubState#pubsub_state.affiliation} of
{[], none} -> del_state(Nidx, Entity);
_ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
%% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
affiliation = owner,
_ = '_'}),
NodeIdxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
NodeTree = mod_pubsub:tree(Host),
Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
case lists:member(Nidx, NodeIdxs) of
true ->
case get_nodes_helper(NodeTree, S) of
{value, Node} -> [Node | Acc];
false -> Acc
end;
false ->
Acc
end
end,
[], pubsub_state),
{result, Reply}.
get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
HasPending = fun
({pending, _}) -> true;
(pending) -> true;
(_) -> false
end,
case lists:any(HasPending, Subs) of
true ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {_, Node}} -> {value, Node};
_ -> false
end;
false ->
false
end.
%% @doc Returns the list of stored states for a given node.
%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
%% <p>We can consider that the pubsub_state table have been created by the main
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the states where they wants (for example in a
%% relational database).</p>
%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
%% they can implement this function like this:
%% ```get_states(Nidx) ->
%% node_default:get_states(Nidx).'''</p>
get_states(Nidx) ->
node_hometree:get_states(Nidx).
States = case catch mnesia:match_object(
#pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of
List when is_list(List) -> List;
_ -> []
end,
{result, States}.
get_state(Nidx, JID) ->
node_hometree:get_state(Nidx, JID).
%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(Nidx, Key) ->
StateId = {Key, Nidx},
case catch mnesia:read({pubsub_state, StateId}) of
[State] when is_record(State, pubsub_state) -> State;
_ -> #pubsub_state{stateid = StateId}
end.
set_state(State) ->
node_hometree:set_state(State).
%% @doc <p>Write a state into database.</p>
set_state(State) when is_record(State, pubsub_state) ->
mnesia:write(State).
%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
get_items(Nidx, From, RSM) ->
node_hometree:get_items(Nidx, From, RSM).
%% @doc <p>Delete a state from database.</p>
del_state(Nidx, Key) ->
mnesia:delete({pubsub_state, {Key, Nidx}}).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
%% @doc Returns the list of stored items for a given node.
%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
%% <p>We can consider that the pubsub_item table have been created by the main
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the items where they wants (for example in a
%% relational database), or they can even decide not to persist any items.</p>
%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
%% they can implement this function like this:
%% ```get_items(Nidx, From) ->
%% node_default:get_items(Nidx, From).'''</p>
get_items(Nidx, From) ->
get_items(Nidx, From, none).
get_items(Nidx, _From, _RSM) ->
Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}),
{result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}.
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, _RSM) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
SubState = get_state(Nidx, SubKey),
Affiliation = GenState#pubsub_state.affiliation,
BareSubscriptions = GenState#pubsub_state.subscriptions,
FullSubscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
can_fetch_item(Affiliation, FullSubscriptions),
if %%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
(Affiliation == outcast) or (Affiliation == publish_only) ->
{error, ?ERR_FORBIDDEN};
(AccessModel == presence) and not PresenceSubscription ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
(AccessModel == roster) and not RosterGroup ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
(AccessModel == whitelist) and not Whitelisted ->
{error,
?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
(AccessModel == authorize) and not Whitelisted ->
{error, ?ERR_FORBIDDEN};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
true ->
get_items(Nidx, JID)
end.
%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(Nidx, ItemId) ->
node_hometree:get_item(Nidx, ItemId).
case mnesia:read({pubsub_item, {ItemId, Nidx}}) of
[Item] when is_record(Item, pubsub_item) -> {result, Item};
_ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = GenState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
if %%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
(Affiliation == outcast) or (Affiliation == publish_only) ->
{error, ?ERR_FORBIDDEN};
(AccessModel == presence) and not PresenceSubscription ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
(AccessModel == roster) and not RosterGroup ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
(AccessModel == whitelist) and not Whitelisted ->
{error,
?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
(AccessModel == authorize) and not Whitelisted ->
{error, ?ERR_FORBIDDEN};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
true ->
get_item(Nidx, ItemId)
end.
set_item(Item) ->
node_hometree:set_item(Item).
%% @doc <p>Write an item into database.</p>
set_item(Item) when is_record(Item, pubsub_item) ->
mnesia:write(Item).
%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
%% @doc <p>Delete an item from database.</p>
del_item(Nidx, ItemId) ->
mnesia:delete({pubsub_item, {ItemId, Nidx}}).
del_items(Nidx, ItemIds) ->
lists:foreach(fun (ItemId) -> del_item(Nidx, ItemId)
end,
ItemIds).
get_item_name(_Host, _Node, Id) ->
Id.
%% @doc <p>Return the path of the node. In flat it's just node id.</p>
node_to_path(Node) ->
[(Node)].
@@ -178,3 +818,26 @@ path_to_node(Path) ->
% default case (used by PEP for example)
_ -> iolist_to_binary(Path)
end.
can_fetch_item(owner, _) -> true;
can_fetch_item(member, _) -> true;
can_fetch_item(publisher, _) -> true;
can_fetch_item(publish_only, _) -> false;
can_fetch_item(outcast, _) -> false;
can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
%can_fetch_item(_Affiliation, _Subscription) -> false.
is_subscribed(Subscriptions) ->
lists:any(fun
({subscribed, _SubId}) -> true;
(_) -> false
end,
Subscriptions).
first_in_list(_Pred, []) ->
false;
first_in_list(Pred, [H | T]) ->
case Pred(H) of
true -> {value, H};
_ -> first_in_list(Pred, T)
end.
+934 -55
View File
File diff suppressed because it is too large Load Diff
+49 -727
View File
@@ -24,20 +24,6 @@
%%% @end
%%% ====================================================================
%%% @todo The item table should be handled by the plugin, but plugin that do
%%% not want to manage it should be able to use the default behaviour.
%%% @todo Plugin modules should be able to register to receive presence update
%%% send to pubsub.
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
%%% as a developer basis and reference to build its own custom pubsub node
%%% types.</p>
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
%%% development is still a work in progress. However, the system is already
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
-module(node_hometree).
-behaviour(gen_pubsub_node).
@@ -55,81 +41,32 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
set_state/1, get_items/6, get_items/2,
get_items/7, get_items/3, get_item/7,
set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1]).
init(Host, ServerHost, _Opts) ->
pubsub_subscription:init(),
mnesia:create_table(pubsub_state,
[{disc_copies, [node()]},
{type, ordered_set},
{attributes, record_info(fields, pubsub_state)}]),
mnesia:create_table(pubsub_item,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_item)}]),
ItemsFields = record_info(fields, pubsub_item),
case mnesia:table_info(pubsub_item, attributes) of
ItemsFields -> ok;
_ -> mnesia:transform_table(pubsub_item, ignore, ItemsFields)
end,
init(Host, ServerHost, Opts) ->
node_flat:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
terminate(_Host, _ServerHost) ->
ok.
terminate(Host, ServerHost) ->
node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{purge_offline, false},
{persist_items, true},
{max_items, ?MAXITEMS},
{subscribe, true},
{access_model, open},
{roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
{presence_based_delivery, false}].
node_flat:options().
features() ->
[<<"create-nodes">>,
<<"auto-create">>,
<<"access-authorize">>,
<<"delete-nodes">>,
<<"delete-items">>,
<<"get-pending">>,
<<"instant-nodes">>,
<<"manage-subscriptions">>,
<<"modify-affiliations">>,
<<"multi-subscribe">>,
<<"outcast-affiliation">>,
<<"persistent-items">>,
<<"publish">>,
<<"publish-only-affiliation">>,
<<"purge-nodes">>,
<<"retract-items">>,
<<"retrieve-affiliations">>,
<<"retrieve-items">>,
<<"retrieve-subscriptions">>,
<<"subscribe">>,
<<"subscription-notifications">>,
<<"subscription-options">>].
node_flat:features().
%% @doc Checks if the current user has the permission to create the requested node
%% <p>In {@link node_default}, the permission is decided by the place in the
%% <p>In hometree node, the permission is decided by the place in the
%% hierarchy where the user is creating the node. The access parameter is also
%% checked in the default module. This parameter depends on the value of the
%% checked. This parameter depends on the value of the
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
%% <p>This function also check that node can be created a a children of its
%% <p>This function also check that node can be created as a children of its
%% parent node</p>
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
@@ -150,707 +87,92 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
set_state(#pubsub_state{stateid = {OwnerKey, Nidx},
affiliation = owner}),
{result, {default, broadcast}}.
node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
lists:map(fun (S) -> {J, S} end, Ss)
end,
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
{result, States} = get_states(Nidx),
lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
del_items(Nidx, Items),
del_state(Nidx, LJID)
end, States),
{PubsubNode, lists:flatmap(Tr, States)}
end, Nodes),
{result, {default, broadcast, Reply}}.
node_flat:delete_node(Nodes).
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
%% <p>The mechanism works as follow:
%% <ul>
%% <li>The main PubSub module prepares the subscription and passes the
%% result of the preparation as a record.</li>
%% <li>This function gets the prepared record and several other parameters and
%% can decide to:<ul>
%% <li>reject the subscription;</li>
%% <li>allow it as is, letting the main module perform the database
%% persistance;</li>
%% <li>allow it, modifying the record. The main module will store the
%% modified record;</li>
%% <li>allow it, but perform the needed persistance operations.</li></ul>
%% </li></ul></p>
%% <p>The selected behaviour depends on the return parameter:
%% <ul>
%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
%% subscription will actually be performed.</li>
%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
%% parameter contains an error, no subscription will be performed.</li>
%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
%% passed in parameter <tt>SubscribeResult</tt>.</li>
%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
%% no further persistance operation will be performed. This case is used,
%% when the plugin module is doing the persistance by itself or when it want
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
GenState = get_state(Nidx, GenKey),
SubState = case SubKey of
GenKey -> GenState;
_ -> get_state(Nidx, SubKey)
end,
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
PendingSubscription = lists:any(fun
({pending, _}) -> true;
(_) -> false
end,
Subscriptions),
Owner = Affiliation == owner,
if not Authorized ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
(Affiliation == outcast) or (Affiliation == publish_only) ->
{error, ?ERR_FORBIDDEN};
PendingSubscription ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
(AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
(AccessModel == roster) and (not RosterGroup) and (not Owner) ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
(AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
{error,
?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
%%ForbiddenAnonymous ->
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
true ->
SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
NewSub = case AccessModel of
authorize -> pending;
_ -> subscribed
end,
set_state(SubState#pubsub_state{subscriptions =
[{NewSub, SubId} | Subscriptions]}),
case {NewSub, SendLast} of
{subscribed, never} ->
{result, {default, subscribed, SubId}};
{subscribed, _} ->
{result, {default, subscribed, SubId, send_last}};
{_, _} ->
{result, {default, pending, SubId}}
end
end.
node_flat:subscribe_node(Nidx, Sender, Subscriber,
AccessModel, SendLast, PresenceSubscription,
RosterGroup, Options).
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
GenState = get_state(Nidx, GenKey),
SubState = case SubKey of
GenKey -> GenState;
_ -> get_state(Nidx, SubKey)
end,
Subscriptions = lists:filter(fun
({_Sub, _SubId}) -> true;
(_SubId) -> false
end,
SubState#pubsub_state.subscriptions),
SubIdExists = case SubId of
<<>> -> false;
Binary when is_binary(Binary) -> true;
_ -> false
end,
if
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
{error, ?ERR_FORBIDDEN};
%% Entity did not specify SubId
%%SubId == "", ?? ->
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%% Invalid subscription identifier
%%InvalidSubId ->
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% Requesting entity is not a subscriber
Subscriptions == [] ->
{error,
?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
%% Subid supplied, so use that.
SubIdExists ->
Sub = first_in_list(fun
({_, S}) when S == SubId -> true;
(_) -> false
end,
SubState#pubsub_state.subscriptions),
case Sub of
{value, S} ->
delete_subscriptions(SubKey, Nidx, [S], SubState),
{result, default};
false ->
{error,
?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
end;
%% Asking to remove all subscriptions to the given node
SubId == all ->
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
{result, default};
%% No subid supplied, but there's only one matching subscription
length(Subscriptions) == 1 ->
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
{result, default};
%% No subid and more than one possible subscription match.
true ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
end.
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
pubsub_subscription:delete_subscription(SubKey, Nidx, SubId),
Acc -- [{Subscription, SubId}]
end, SubState#pubsub_state.subscriptions, Subscriptions),
case {SubState#pubsub_state.affiliation, NewSubs} of
{none, []} -> del_state(Nidx, SubKey);
_ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
%% @doc <p>Publishes the item passed as parameter.</p>
%% <p>The mechanism works as follow:
%% <ul>
%% <li>The main PubSub module prepares the item to publish and passes the
%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
%% <li>reject the publication;</li>
%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
%% <li>allow it, but perform the needed persistance operations.</li></ul>
%% </li></ul></p>
%% <p>The selected behaviour depends on the return parameter:
%% <ul>
%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
%% publication is actually performed.</li>
%% <li><tt>true</tt>: Publication operation is allowed, based on the
%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
%% parameter contains an error, no subscription will actually be
%% performed.</li>
%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
%% in parameter <tt>Item</tt>. The persistance will be performed by the main
%% module.</li>
%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
%% no further persistance operation will be performed. This case is used,
%% when the plugin module is doing the persistance by itself or when it want
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
SubState = case SubKey of
GenKey -> GenState;
_ -> get_state(Nidx, SubKey)
end,
Affiliation = GenState#pubsub_state.affiliation,
Subscribed = case PublishModel of
subscribers -> is_subscribed(SubState#pubsub_state.subscriptions);
_ -> undefined
end,
if not ((PublishModel == open) or
(PublishModel == publishers) and
((Affiliation == owner)
or (Affiliation == publisher)
or (Affiliation == publish_only))
or (Subscribed == true)) ->
{error, ?ERR_FORBIDDEN};
true ->
if MaxItems > 0 ->
Now = now(),
PubId = {Now, SubKey},
Item = case get_item(Nidx, ItemId) of
{result, OldItem} ->
OldItem#pubsub_item{modification = PubId,
payload = Payload};
_ ->
#pubsub_item{itemid = {ItemId, Nidx},
creation = {Now, GenKey},
modification = PubId,
payload = Payload}
end,
Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
{result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
set_item(Item),
set_state(GenState#pubsub_state{items = NI}),
{result, {default, broadcast, OI}};
true ->
{result, {default, broadcast, []}}
end
end.
%% @doc <p>This function is used to remove extra items, most notably when the
%% maximum number of items has been reached.</p>
%% <p>This function is used internally by the core PubSub module, as no
%% permission check is performed.</p>
%% <p>In the default plugin module, the oldest items are removed, but other
%% rules can be used.</p>
%% <p>If another PubSub plugin wants to delegate the item removal (and if the
%% plugin is using the default pubsub storage), it can implements this function like this:
%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
remove_extra_items(_Nidx, unlimited, ItemIds) ->
{result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
del_items(Nidx, OldItems),
{result, {NewItems, OldItems}}.
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p>
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
#pubsub_state{affiliation = Affiliation, items = Items} = GenState,
Allowed = Affiliation == publisher orelse
Affiliation == owner orelse
PublishModel == open orelse
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}}} -> true;
_ -> false
end,
if not Allowed ->
{error, ?ERR_FORBIDDEN};
true ->
case lists:member(ItemId, Items) of
true ->
del_item(Nidx, ItemId),
set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
{result, {default, broadcast}};
false ->
case Affiliation of
owner ->
{result, States} = get_states(Nidx),
lists:foldl(fun
(#pubsub_state{items = PI} = S, Res) ->
case lists:member(ItemId, PI) of
true ->
Nitems = lists:delete(ItemId, PI),
del_item(Nidx, ItemId),
set_state(S#pubsub_state{items = Nitems}),
{result, {default, broadcast}};
false ->
Res
end;
(_, Res) ->
Res
end,
{error, ?ERR_ITEM_NOT_FOUND}, States);
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end
end.
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
case GenState of
#pubsub_state{affiliation = owner} ->
{result, States} = get_states(Nidx),
lists:foreach(fun
(#pubsub_state{items = []}) ->
ok;
(#pubsub_state{items = Items} = S) ->
del_items(Nidx, Items),
set_state(S#pubsub_state{items = []})
end,
States),
{result, {default, broadcast}};
_ ->
{error, ?ERR_FORBIDDEN}
end.
node_flat:purge_node(Nidx, Owner).
%% @doc <p>Return the current affiliations for the given user</p>
%% <p>The default module reads affiliations in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
%% table, it should return an empty list, as the affiliation will be read by
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = mod_pubsub:tree(Host),
Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
_ -> Acc
end
end,
[], States),
{result, Reply}.
node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
{result, States} = get_states(Nidx),
Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
{result, lists:map(Tr, States)}.
node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
#pubsub_state{affiliation = Affiliation} = get_state(Nidx, GenKey),
{result, Affiliation}.
node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
case {Affiliation, GenState#pubsub_state.subscriptions} of
{none, []} -> del_state(Nidx, GenKey);
_ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
end.
node_flat:set_affiliation(Nidx, Owner, Affiliation).
%% @doc <p>Return the current subscriptions for the given user</p>
%% <p>The default module reads subscriptions in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
%% table, it should return an empty list, as the affiliation will be read by
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = case SubKey of
GenKey ->
mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
_ ->
mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
++
mnesia:match_object(#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
end,
NodeTree = mod_pubsub:tree(Host),
Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {Host, _}} = Node ->
lists:foldl(fun ({Sub, SubId}, Acc2) ->
[{Node, Sub, SubId, J} | Acc2]
end,
Acc, Ss);
_ ->
Acc
end
end,
[], States),
{result, Reply}.
node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
{result, States} = get_states(Nidx),
Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
case Subscriptions of
[_ | _] ->
lists:foldl(fun ({S, SubId}, Acc) ->
[{J, S, SubId} | Acc]
end,
[], Subscriptions);
[] ->
[];
_ ->
[{J, none}]
end
end,
{result, lists:flatmap(Tr, States)}.
node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
SubState = get_state(Nidx, SubKey),
{result, SubState#pubsub_state.subscriptions}.
node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
SubKey = jlib:jid_tolower(Owner),
SubState = get_state(Nidx, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
{_, []} ->
case Subscription of
none ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
_ ->
new_subscription(Nidx, Owner, Subscription, SubState)
end;
{<<>>, [{_, SID}]} ->
case Subscription of
none -> unsub_with_subid(Nidx, SID, SubState);
_ -> replace_subscription({Subscription, SID}, SubState)
end;
{<<>>, [_ | _]} ->
{error,
?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
_ ->
case Subscription of
none -> unsub_with_subid(Nidx, SubId, SubState);
_ -> replace_subscription({Subscription, SubId}, SubState)
end
end.
node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
replace_subscription(NewSub, SubState) ->
NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
set_state(SubState#pubsub_state{subscriptions = NewSubs}).
replace_subscription(_, [], Acc) -> Acc;
replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
new_subscription(Nidx, Owner, Sub, SubState) ->
SubId = pubsub_subscription:add_subscription(Owner, Nidx, []),
Subs = SubState#pubsub_state.subscriptions,
set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}),
{Sub, SubId}.
unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, Nidx, SubId),
NewSubs = [{S, Sid}
|| {S, Sid} <- SubState#pubsub_state.subscriptions,
SubId =/= Sid],
case {NewSubs, SubState#pubsub_state.affiliation} of
{[], none} -> del_state(Nidx, Entity);
_ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
%% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
affiliation = owner,
_ = '_'}),
NodeIdxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
NodeTree = mod_pubsub:tree(Host),
Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
case lists:member(Nidx, NodeIdxs) of
true ->
case get_nodes_helper(NodeTree, S) of
{value, Node} -> [Node | Acc];
false -> Acc
end;
false ->
Acc
end
end,
[], pubsub_state),
{result, Reply}.
node_flat:get_pending_nodes(Host, Owner).
get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
HasPending = fun
({pending, _}) -> true;
(pending) -> true;
(_) -> false
end,
case lists:any(HasPending, Subs) of
true ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {_, Node}} -> {value, Node};
_ -> false
end;
false ->
false
end.
%% @doc Returns the list of stored states for a given node.
%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
%% <p>We can consider that the pubsub_state table have been created by the main
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the states where they wants (for example in a
%% relational database).</p>
%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
%% they can implement this function like this:
%% ```get_states(Nidx) ->
%% node_default:get_states(Nidx).'''</p>
get_states(Nidx) ->
States = case catch mnesia:match_object(
#pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of
List when is_list(List) -> List;
_ -> []
end,
{result, States}.
node_flat:get_states(Nidx).
%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(Nidx, Key) ->
StateId = {Key, Nidx},
case catch mnesia:read({pubsub_state, StateId}) of
[State] when is_record(State, pubsub_state) -> State;
_ -> #pubsub_state{stateid = StateId}
end.
get_state(Nidx, JID) ->
node_flat:get_state(Nidx, JID).
%% @doc <p>Write a state into database.</p>
set_state(State) when is_record(State, pubsub_state) ->
mnesia:write(State).
%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
set_state(State) ->
node_flat:set_state(State).
%% @doc <p>Delete a state from database.</p>
del_state(Nidx, Key) ->
mnesia:delete({pubsub_state, {Key, Nidx}}).
get_items(Nidx, From, RSM) ->
node_flat:get_items(Nidx, From, RSM).
%% @doc Returns the list of stored items for a given node.
%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
%% <p>We can consider that the pubsub_item table have been created by the main
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the items where they wants (for example in a
%% relational database), or they can even decide not to persist any items.</p>
%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
%% they can implement this function like this:
%% ```get_items(Nidx, From) ->
%% node_default:get_items(Nidx, From).'''</p>
get_items(Nidx, From) ->
get_items(Nidx, From, none).
get_items(Nidx, _From, _RSM) ->
Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}),
{result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}.
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, _RSM) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
SubState = get_state(Nidx, SubKey),
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
if %%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
(Affiliation == outcast) or (Affiliation == publish_only) ->
{error, ?ERR_FORBIDDEN};
(AccessModel == presence) and not PresenceSubscription ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
(AccessModel == roster) and not RosterGroup ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
(AccessModel == whitelist) and not Whitelisted ->
{error,
?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
(AccessModel == authorize) and not Whitelisted ->
{error, ?ERR_FORBIDDEN};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
true ->
get_items(Nidx, JID)
end.
%% @doc <p>Returns an item (one item list), given its reference.</p>
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
case mnesia:read({pubsub_item, {ItemId, Nidx}}) of
[Item] when is_record(Item, pubsub_item) -> {result, Item};
_ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = GenState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
if %%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
(Affiliation == outcast) or (Affiliation == publish_only) ->
{error, ?ERR_FORBIDDEN};
(AccessModel == presence) and not PresenceSubscription ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
(AccessModel == roster) and not RosterGroup ->
{error,
?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
(AccessModel == whitelist) and not Whitelisted ->
{error,
?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
(AccessModel == authorize) and not Whitelisted ->
{error, ?ERR_FORBIDDEN};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
true ->
get_item(Nidx, ItemId)
end.
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
%% @doc <p>Write an item into database.</p>
set_item(Item) when is_record(Item, pubsub_item) ->
mnesia:write(Item).
%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
set_item(Item) ->
node_flat:set_item(Item).
%% @doc <p>Delete an item from database.</p>
del_item(Nidx, ItemId) ->
mnesia:delete({pubsub_item, {ItemId, Nidx}}).
get_item_name(Host, Node, Id) ->
node_flat:get_item_name(Host, Node, Id).
del_items(Nidx, ItemIds) ->
lists:foreach(fun (ItemId) -> del_item(Nidx, ItemId)
end,
ItemIds).
get_item_name(_Host, _Node, Id) ->
Id.
%% @doc <p>Return the name of the node if known: Default is to return
%% node id.</p>
%% @doc <p>Return the path of the node.</p>
node_to_path(Node) ->
str:tokens(Node, <<"/">>).
path_to_node([]) -> <<>>;
path_to_node(Path) -> iolist_to_binary(str:join([<<"">> | Path], <<"/">>)).
can_fetch_item(owner, _) -> true;
can_fetch_item(member, _) -> true;
can_fetch_item(publisher, _) -> true;
can_fetch_item(publish_only, _) -> false;
can_fetch_item(outcast, _) -> false;
can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
%can_fetch_item(_Affiliation, _Subscription) -> false.
is_subscribed(Subscriptions) ->
lists:any(fun
({subscribed, _SubId}) -> true;
(_) -> false
end,
Subscriptions).
first_in_list(_Pred, []) ->
false;
first_in_list(Pred, [H | T]) ->
case Pred(H) of
true -> {value, H};
_ -> first_in_list(Pred, T)
end.
+42 -1080
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -37,7 +37,6 @@
%%% ignore_pep_from_offline: false
%%% plugins:
%%% - "flat"
%%% - "hometree"
%%% - "pep" # Requires mod_caps.
%%% pep_mapping:
%%% "urn:xmpp:microblog:0": "mb"
+26 -26
View File
@@ -49,12 +49,12 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts),
node_flat:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost), ok.
node_flat:terminate(Host, ServerHost), ok.
options() ->
[{deliver_payloads, true},
@@ -112,34 +112,34 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
node_hometree:create_node(Nidx, Owner).
node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
{result, {_, _, Result}} = node_hometree:delete_node(Nodes),
{result, {_, _, Result}} = node_flat:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
case node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
case node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_hometree:purge_node(Nidx, Owner).
node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
{_, D, _} = SubKey = jlib:jid_tolower(Owner),
@@ -158,13 +158,13 @@ get_entity_affiliations(Host, Owner) ->
get_node_affiliations(Nidx) ->
node_hometree:get_node_affiliations(Nidx).
node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_hometree:get_affiliation(Nidx, Owner).
node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree:set_affiliation(Nidx, Owner, Affiliation).
node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
@@ -198,45 +198,45 @@ get_entity_subscriptions(Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
node_hometree:get_node_subscriptions(Nidx).
node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_hometree:get_subscriptions(Nidx, Owner).
node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_hometree:get_states(Nidx).
node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
node_hometree:get_state(Nidx, JID).
node_flat:get_state(Nidx, JID).
set_state(State) ->
node_hometree:set_state(State).
node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
node_hometree:get_items(Nidx, From, RSM).
node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree:get_items(Nidx, JID, AccessModel,
node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
node_hometree:get_item(Nidx, ItemId).
node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree:set_item(Item).
node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
+40 -40
View File
@@ -50,12 +50,12 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
node_hometree_odbc:init(Host, ServerHost, Opts),
node_flat_odbc:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
node_hometree_odbc:terminate(Host, ServerHost), ok.
node_flat_odbc:terminate(Host, ServerHost), ok.
options() ->
[{odbc, true}, {rsm, true} | node_pep:options()].
@@ -67,56 +67,56 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
node_hometree_odbc:create_node(Nidx, Owner),
node_flat_odbc:create_node(Nidx, Owner),
{result, {default, broadcast}}.
delete_node(Nodes) ->
{result, {_, _, Result}} = node_hometree_odbc:delete_node(Nodes),
{result, {_, _, Result}} = node_flat_odbc:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
case node_hometree_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
case node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_hometree_odbc:purge_node(Nidx, Owner).
node_flat_odbc:purge_node(Nidx, Owner).
get_entity_affiliations(_Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner).
node_flat_odbc:get_entity_affiliations(OwnerKey, Owner).
get_node_affiliations(Nidx) ->
node_hometree_odbc:get_node_affiliations(Nidx).
node_flat_odbc:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_hometree_odbc:get_affiliation(Nidx, Owner).
node_flat_odbc:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree_odbc:set_affiliation(Nidx, Owner, Affiliation).
node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(_Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
Host = node_hometree_odbc:encode_host(element(2, SubKey)),
SJ = node_hometree_odbc:encode_jid(SubKey),
GJ = node_hometree_odbc:encode_jid(GenKey),
Host = node_flat_odbc:encode_host(element(2, SubKey)),
SJ = node_flat_odbc:encode_jid(SubKey),
GJ = node_flat_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -134,11 +134,11 @@ get_entity_subscriptions(_Host, Owner) ->
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
O = node_hometree_odbc:decode_jid(H),
O = node_flat_odbc:decode_jid(H),
Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
node_hometree_odbc:decode_subscriptions(S),
node_hometree_odbc:decode_jid(J)}
node_flat_odbc:decode_subscriptions(S),
node_flat_odbc:decode_jid(J)}
end,
RItems);
_ ->
@@ -149,9 +149,9 @@ get_entity_subscriptions(_Host, Owner) ->
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
Host = node_hometree_odbc:encode_host(element(2, SubKey)),
SJ = node_hometree_odbc:encode_jid(SubKey),
GJ = node_hometree_odbc:encode_jid(GenKey),
Host = node_flat_odbc:encode_host(element(2, SubKey)),
SJ = node_flat_odbc:encode_jid(SubKey),
GJ = node_flat_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -173,11 +173,11 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
O = node_hometree_odbc:decode_jid(H),
O = node_flat_odbc:decode_jid(H),
Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
node_hometree_odbc:decode_subscriptions(S),
node_hometree_odbc:decode_jid(J)}
node_flat_odbc:decode_subscriptions(S),
node_flat_odbc:decode_jid(J)}
end,
RItems);
_ ->
@@ -186,48 +186,48 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
node_hometree_odbc:get_node_subscriptions(Nidx).
node_flat_odbc:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_hometree_odbc:get_subscriptions(Nidx, Owner).
node_flat_odbc:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree_odbc:get_pending_nodes(Host, Owner).
node_flat_odbc:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_hometree_odbc:get_states(Nidx).
node_flat_odbc:get_states(Nidx).
get_state(Nidx, JID) ->
node_hometree_odbc:get_state(Nidx, JID).
node_flat_odbc:get_state(Nidx, JID).
set_state(State) ->
node_hometree_odbc:set_state(State).
node_flat_odbc:set_state(State).
get_items(Nidx, From, RSM) ->
node_hometree_odbc:get_items(Nidx, From, RSM).
node_flat_odbc:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree_odbc:get_items(Nidx, JID, AccessModel,
node_flat_odbc:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(Nidx, JID, Count) ->
node_hometree_odbc:get_last_items(Nidx, JID, Count).
node_flat_odbc:get_last_items(Nidx, JID, Count).
get_item(Nidx, ItemId) ->
node_hometree_odbc:get_item(Nidx, ItemId).
node_flat_odbc:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree_odbc:get_item(Nidx, ItemId, JID, AccessModel,
node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree_odbc:set_item(Item).
node_flat_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree_odbc:get_item_name(Host, Node, Id).
node_flat_odbc:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat_odbc:node_to_path(Node).
+29 -29
View File
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
node_hometree:create_node(Nidx, Owner).
node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
node_hometree:delete_node(Removed).
node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_hometree:purge_node(Nidx, Owner).
node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
node_hometree:get_entity_affiliations(Host, Owner).
node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
node_hometree:get_node_affiliations(Nidx).
node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_hometree:get_affiliation(Nidx, Owner).
node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree:set_affiliation(Nidx, Owner, Affiliation).
node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
node_hometree:get_node_subscriptions(Nidx).
node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_hometree:get_subscriptions(Nidx, Owner).
node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_hometree:get_states(Nidx).
node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
node_hometree:get_state(Nidx, JID).
node_flat:get_state(Nidx, JID).
set_state(State) ->
node_hometree:set_state(State).
node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
node_hometree:get_items(Nidx, From, RSM).
node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree:get_items(Nidx, JID, AccessModel,
node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
node_hometree:get_item(Nidx, ItemId).
node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree:set_item(Item).
node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
+29 -29
View File
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
node_hometree:create_node(Nidx, Owner).
node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
node_hometree:delete_node(Removed).
node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_hometree:purge_node(Nidx, Owner).
node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
node_hometree:get_entity_affiliations(Host, Owner).
node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
node_hometree:get_node_affiliations(Nidx).
node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_hometree:get_affiliation(Nidx, Owner).
node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_hometree:set_affiliation(Nidx, Owner, Affiliation).
node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
node_hometree:get_node_subscriptions(Nidx).
node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_hometree:get_subscriptions(Nidx, Owner).
node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_hometree:get_states(Nidx).
node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
node_hometree:get_state(Nidx, JID).
node_flat:get_state(Nidx, JID).
set_state(State) ->
node_hometree:set_state(State).
node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
node_hometree:get_items(Nidx, From, RSM).
node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree:get_items(Nidx, JID, AccessModel,
node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
node_hometree:get_item(Nidx, ItemId).
node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree:set_item(Item).
node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
+8 -8
View File
@@ -66,7 +66,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
[First | _] -> First
end,
Type = Record#pubsub_node.type,
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
P = ejabberd_odbc:escape(Parent),
Nidx = case nodeidx(Host, Node) of
@@ -116,7 +116,7 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -151,7 +151,7 @@ get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>, H, <<"';">>])
@@ -178,7 +178,7 @@ get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -196,7 +196,7 @@ get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -256,7 +256,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
end.
delete_node(Host, Node) ->
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
Removed = get_subnodes_tree(Host, Node),
catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>,
@@ -295,7 +295,7 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
id = Nidx, type = Type, options = Options}.
nodeidx(Host, Node) ->
H = node_hometree_odbc:encode_host(Host),
H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where "
@@ -311,5 +311,5 @@ nodeidx(Host, Node) ->
end.
nodeowners(Nidx) ->
{result, Res} = node_hometree_odbc:get_node_affiliations(Nidx),
{result, Res} = node_flat_odbc:get_node_affiliations(Nidx),
[LJID || {LJID, Aff} <- Res, Aff =:= owner].
+23 -1
View File
@@ -29,7 +29,8 @@
-include("pubsub.hrl").
-include("logger.hrl").
-export([update_node_database/2, update_state_database/2, update_lastitem_database/2]).
-export([update_node_database/2, update_state_database/2]).
-export([update_item_database/2, update_lastitem_database/2]).
update_item_database_binary() ->
F = fun () ->
@@ -57,6 +58,27 @@ update_item_database_binary() ->
?INFO_MSG("Pubsub items table has been binarized: ~p", [Result])
end.
update_item_database(_Host, _ServerHost) ->
F = fun() ->
?INFO_MSG("Migration of old pubsub items...", []),
lists:foreach(fun (Key) ->
[Item] = mnesia:read({pubsub_item, Key}),
Payload = [xmlelement_to_xmlel(El) || El <- Item#pubsub_item.payload],
mnesia:write(Item#pubsub_item{payload=Payload})
end,
mnesia:all_keys(pubsub_item))
end,
case mnesia:transaction(F) of
{aborted, Reason} ->
?ERROR_MSG("Failed to migrate old pubsub items to xmlel: ~p", [Reason]);
{atomic, Result} ->
?INFO_MSG("Pubsub items has been migrated: ~p", [Result])
end.
xmlelement_to_xmlel({xmlelement, A, B, C}) when is_list(C) ->
{xmlel, A, B, [xmlelement_to_xmlel(El) || El <- C]};
xmlelement_to_xmlel(El) ->
El.
update_node_database_binary() ->
F = fun () ->
+63 -20
View File
@@ -1122,10 +1122,11 @@ muc_master(Config) ->
end
end, RoomCfg#xdata.fields),
NewRoomCfg = #xdata{type = submit, fields = NewFields},
%% BUG: We should not receive any sub_els!
#iq{type = result, sub_els = [_|_]} =
send_recv(Config, #iq{type = set, to = Room,
sub_els = [#muc_owner{config = NewRoomCfg}]}),
ID = send(Config, #iq{type = set, to = Room,
sub_els = [#muc_owner{config = NewRoomCfg}]}),
?recv2(#iq{type = result, id = ID},
#message{from = Room, type = groupchat,
sub_els = [#muc_user{status_codes = [104]}]}),
%% Set subject
send(Config, #message{to = Room, type = groupchat,
body = [#text{data = Subject}]}),
@@ -1662,7 +1663,11 @@ mam_query_all(Config, NS) ->
QID = randoms:get_string(),
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
I = send(Config, #iq{type = get, sub_els = [#mam_query{xmlns = NS, id = QID}]}),
Type = case NS of
?NS_MAM_TMP -> get;
_ -> set
end,
I = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, id = QID}]}),
maybe_recv_iq_result(NS, I),
Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
true -> lists:seq(1, 5) ++ lists:seq(1, 5)
@@ -1685,21 +1690,21 @@ mam_query_all(Config, NS) ->
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I, sub_els = []});
true ->
?recv1(#message{sub_els = [#mam_fin{id = QID}]})
?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]})
end.
mam_query_with(Config, JID, NS) ->
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
Query = if NS == ?NS_MAM_TMP ->
#mam_query{xmlns = NS, with = JID};
{Query, Type} = if NS == ?NS_MAM_TMP ->
{#mam_query{xmlns = NS, with = JID}, get};
true ->
Fs = [#xdata_field{var = <<"jid">>,
values = [jlib:jid_to_string(JID)]}],
#mam_query{xmlns = NS,
xdata = #xdata{type = submit, fields = Fs}}
{#mam_query{xmlns = NS,
xdata = #xdata{type = submit, fields = Fs}}, set}
end,
I = send(Config, #iq{type = get, sub_els = [Query]}),
I = send(Config, #iq{type = Type, sub_els = [Query]}),
Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
true -> lists:seq(1, 5) ++ lists:seq(1, 5)
end,
@@ -1721,7 +1726,7 @@ mam_query_with(Config, JID, NS) ->
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I, sub_els = []});
true ->
?recv1(#message{sub_els = [#mam_fin{}]})
?recv1(#message{sub_els = [#mam_fin{complete = true}]})
end.
maybe_recv_iq_result(?NS_MAM_0, I1) ->
@@ -1732,9 +1737,13 @@ maybe_recv_iq_result(_, _) ->
mam_query_rsm(Config, NS) ->
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
Type = case NS of
?NS_MAM_TMP -> get;
_ -> set
end,
%% Get the first 3 items out of 5
I1 = send(Config,
#iq{type = get,
#iq{type = Type,
sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}),
maybe_recv_iq_result(NS, I1),
lists:foreach(
@@ -1758,12 +1767,13 @@ mam_query_rsm(Config, NS) ->
rsm = #rsm_set{last = Last, count = 5}}]});
true ->
?recv1(#message{sub_els = [#mam_fin{
complete = false,
rsm = #rsm_set{last = Last, count = 10}}]})
end,
%% Get the next items starting from the `Last`.
%% Limit the response to 2 items.
I2 = send(Config,
#iq{type = get,
#iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
rsm = #rsm_set{max = 2,
'after' = Last}}]}),
@@ -1793,15 +1803,16 @@ mam_query_rsm(Config, NS) ->
true ->
?recv1(#message{
sub_els = [#mam_fin{
complete = false,
rsm = #rsm_set{
count = 10,
first = #rsm_first{data = First}}}]})
end,
%% Paging back. Should receive 2 elements: 2, 3.
%% Paging back. Should receive 3 elements: 1, 2, 3.
I3 = send(Config,
#iq{type = get,
#iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
rsm = #rsm_set{max = 2,
rsm = #rsm_set{max = 3,
before = First}}]}),
maybe_recv_iq_result(NS, I3),
lists:foreach(
@@ -1818,17 +1829,18 @@ mam_query_rsm(Config, NS) ->
[#message{
from = MyJID, to = Peer,
body = [Text]}]}]}]})
end, lists:seq(2, 3)),
end, lists:seq(1, 3)),
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I3,
sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
true ->
?recv1(#message{
sub_els = [#mam_fin{rsm = #rsm_set{count = 10}}]})
sub_els = [#mam_fin{complete = true,
rsm = #rsm_set{count = 10}}]})
end,
%% Getting the item count. Should be 5 (or 10).
I4 = send(Config,
#iq{type = get,
#iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
rsm = #rsm_set{max = 0}}]}),
maybe_recv_iq_result(NS, I4),
@@ -1842,9 +1854,40 @@ mam_query_rsm(Config, NS) ->
true ->
?recv1(#message{
sub_els = [#mam_fin{
complete = false,
rsm = #rsm_set{count = 10,
first = undefined,
last = undefined}}]})
end,
%% Should receive 2 last messages
I5 = send(Config,
#iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
rsm = #rsm_set{max = 2,
before = none}}]}),
maybe_recv_iq_result(NS, I5),
lists:foreach(
fun(N) ->
Text = #text{data = jlib:integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
xmlns = NS,
sub_els =
[#forwarded{
delay = #delay{},
sub_els =
[#message{
from = MyJID, to = Peer,
body = [Text]}]}]}]})
end, lists:seq(4, 5)),
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I5,
sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
true ->
?recv1(#message{
sub_els = [#mam_fin{complete = false,
rsm = #rsm_set{count = 10}}]})
end.
client_state_master(Config) ->
+5 -10
View File
@@ -12,8 +12,7 @@ host_config:
mod_announce:
db_type: odbc
access: local
mod_blocking:
db_type: odbc
mod_blocking: []
mod_caps:
db_type: odbc
mod_last:
@@ -65,8 +64,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: odbc
access: local
mod_blocking:
db_type: odbc
mod_blocking: []
mod_caps:
db_type: odbc
mod_last:
@@ -124,8 +122,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: odbc
access: local
mod_blocking:
db_type: odbc
mod_blocking: []
mod_caps:
db_type: odbc
mod_last:
@@ -176,8 +173,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: internal
access: local
mod_blocking:
db_type: internal
mod_blocking: []
mod_caps:
db_type: internal
mod_last:
@@ -232,8 +228,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: riak
access: local
mod_blocking:
db_type: riak
mod_blocking: []
mod_caps:
db_type: riak
mod_last:
+67 -16
View File
@@ -2082,7 +2082,7 @@ encode({mam_result, _, _, _, _} = Result) ->
encode_mam_result(Result, []);
encode({mam_prefs, _, _, _, _} = Prefs) ->
encode_mam_prefs(Prefs, []);
encode({mam_fin, _, _} = Fin) ->
encode({mam_fin, _, _, _, _} = Fin) ->
encode_mam_fin(Fin,
[{<<"xmlns">>, <<"urn:xmpp:mam:0">>}]);
encode({forwarded, _, _} = Forwarded) ->
@@ -2308,7 +2308,7 @@ get_ns({rsm_first, _, _}) ->
get_ns({rsm_set, _, _, _, _, _, _, _}) ->
<<"http://jabber.org/protocol/rsm">>;
get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>;
get_ns({mam_fin, _, _}) -> <<"urn:xmpp:mam:0">>;
get_ns({mam_fin, _, _, _, _}) -> <<"urn:xmpp:mam:0">>;
get_ns({forwarded, _, _}) -> <<"urn:xmpp:forward:0">>;
get_ns({carbons_disable}) -> <<"urn:xmpp:carbons:2">>;
get_ns({carbons_enable}) -> <<"urn:xmpp:carbons:2">>;
@@ -2505,7 +2505,7 @@ pp(mam_query, 7) ->
pp(mam_archived, 2) -> [by, id];
pp(mam_result, 4) -> [xmlns, queryid, id, sub_els];
pp(mam_prefs, 4) -> [xmlns, default, always, never];
pp(mam_fin, 2) -> [id, rsm];
pp(mam_fin, 4) -> [id, rsm, stable, complete];
pp(forwarded, 2) -> [delay, sub_els];
pp(carbons_disable, 0) -> [];
pp(carbons_enable, 0) -> [];
@@ -3709,9 +3709,10 @@ decode_mam_fin(__TopXMLNS, __IgnoreEls,
{xmlel, <<"fin">>, _attrs, _els}) ->
Rsm = decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els,
undefined),
Id = decode_mam_fin_attrs(__TopXMLNS, _attrs,
undefined),
{mam_fin, Id, Rsm}.
{Id, Stable, Complete} =
decode_mam_fin_attrs(__TopXMLNS, _attrs, undefined,
undefined, undefined),
{mam_fin, Id, Rsm, Stable, Complete}.
decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [], Rsm) ->
Rsm;
@@ -3729,16 +3730,37 @@ decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [_ | _els],
decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm).
decode_mam_fin_attrs(__TopXMLNS,
[{<<"queryid">>, _val} | _attrs], _Id) ->
decode_mam_fin_attrs(__TopXMLNS, _attrs, _val);
decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id) ->
decode_mam_fin_attrs(__TopXMLNS, _attrs, Id);
decode_mam_fin_attrs(__TopXMLNS, [], Id) ->
decode_mam_fin_attr_queryid(__TopXMLNS, Id).
[{<<"queryid">>, _val} | _attrs], _Id, Stable,
Complete) ->
decode_mam_fin_attrs(__TopXMLNS, _attrs, _val, Stable,
Complete);
decode_mam_fin_attrs(__TopXMLNS,
[{<<"stable">>, _val} | _attrs], Id, _Stable,
Complete) ->
decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, _val,
Complete);
decode_mam_fin_attrs(__TopXMLNS,
[{<<"complete">>, _val} | _attrs], Id, Stable,
_Complete) ->
decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable,
_val);
decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id,
Stable, Complete) ->
decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable,
Complete);
decode_mam_fin_attrs(__TopXMLNS, [], Id, Stable,
Complete) ->
{decode_mam_fin_attr_queryid(__TopXMLNS, Id),
decode_mam_fin_attr_stable(__TopXMLNS, Stable),
decode_mam_fin_attr_complete(__TopXMLNS, Complete)}.
encode_mam_fin({mam_fin, Id, Rsm}, _xmlns_attrs) ->
encode_mam_fin({mam_fin, Id, Rsm, Stable, Complete},
_xmlns_attrs) ->
_els = lists:reverse('encode_mam_fin_$rsm'(Rsm, [])),
_attrs = encode_mam_fin_attr_queryid(Id, _xmlns_attrs),
_attrs = encode_mam_fin_attr_complete(Complete,
encode_mam_fin_attr_stable(Stable,
encode_mam_fin_attr_queryid(Id,
_xmlns_attrs))),
{xmlel, <<"fin">>, _attrs, _els}.
'encode_mam_fin_$rsm'(undefined, _acc) -> _acc;
@@ -3755,6 +3777,35 @@ encode_mam_fin_attr_queryid(undefined, _acc) -> _acc;
encode_mam_fin_attr_queryid(_val, _acc) ->
[{<<"queryid">>, _val} | _acc].
decode_mam_fin_attr_stable(__TopXMLNS, undefined) ->
undefined;
decode_mam_fin_attr_stable(__TopXMLNS, _val) ->
case catch dec_bool(_val) of
{'EXIT', _} ->
erlang:error({xmpp_codec,
{bad_attr_value, <<"stable">>, <<"fin">>, __TopXMLNS}});
_res -> _res
end.
encode_mam_fin_attr_stable(undefined, _acc) -> _acc;
encode_mam_fin_attr_stable(_val, _acc) ->
[{<<"stable">>, enc_bool(_val)} | _acc].
decode_mam_fin_attr_complete(__TopXMLNS, undefined) ->
undefined;
decode_mam_fin_attr_complete(__TopXMLNS, _val) ->
case catch dec_bool(_val) of
{'EXIT', _} ->
erlang:error({xmpp_codec,
{bad_attr_value, <<"complete">>, <<"fin">>,
__TopXMLNS}});
_res -> _res
end.
encode_mam_fin_attr_complete(undefined, _acc) -> _acc;
encode_mam_fin_attr_complete(_val, _acc) ->
[{<<"complete">>, enc_bool(_val)} | _acc].
decode_mam_prefs(__TopXMLNS, __IgnoreEls,
{xmlel, <<"prefs">>, _attrs, _els}) ->
{Never, Always} = decode_mam_prefs_els(__TopXMLNS,
@@ -4746,10 +4797,10 @@ encode_rsm_before(Cdata, _xmlns_attrs) ->
_attrs = _xmlns_attrs,
{xmlel, <<"before">>, _attrs, _els}.
decode_rsm_before_cdata(__TopXMLNS, <<>>) -> undefined;
decode_rsm_before_cdata(__TopXMLNS, <<>>) -> none;
decode_rsm_before_cdata(__TopXMLNS, _val) -> _val.
encode_rsm_before_cdata(undefined, _acc) -> _acc;
encode_rsm_before_cdata(none, _acc) -> _acc;
encode_rsm_before_cdata(_val, _acc) ->
[{xmlcdata, _val} | _acc].
+4 -2
View File
@@ -280,7 +280,7 @@
units = [] :: [binary()]}).
-record(rsm_set, {'after' :: binary(),
before :: binary(),
before :: 'none' | binary(),
count :: non_neg_integer(),
first :: #rsm_first{},
index :: non_neg_integer(),
@@ -288,7 +288,9 @@
max :: non_neg_integer()}).
-record(mam_fin, {id :: binary(),
rsm :: #rsm_set{}}).
rsm :: #rsm_set{},
stable :: any(),
complete :: any()}).
-record(vcard_tel, {home = false :: boolean(),
work = false :: boolean(),
+9 -2
View File
@@ -2071,6 +2071,7 @@
-xml(rsm_before,
#elem{name = <<"before">>,
xmlns = <<"http://jabber.org/protocol/rsm">>,
cdata = #cdata{default = none},
result = '$cdata'}).
-xml(rsm_last,
@@ -2209,8 +2210,14 @@
-xml(mam_fin,
#elem{name = <<"fin">>,
xmlns = <<"urn:xmpp:mam:0">>,
result = {mam_fin, '$id', '$rsm'},
attrs = [#attr{name = <<"queryid">>, label = '$id'}],
result = {mam_fin, '$id', '$rsm', '$stable', '$complete'},
attrs = [#attr{name = <<"queryid">>, label = '$id'},
#attr{name = <<"stable">>, label = '$stable',
dec = {dec_bool, []},
enc = {enc_bool, []}},
#attr{name = <<"complete">>, label = '$complete',
dec = {dec_bool, []},
enc = {enc_bool, []}}],
refs = [#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}]}).
-xml(forwarded,