Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dbedb69a6 | |||
| 5788609e5c | |||
| 5305b4a82c | |||
| d03d08539b | |||
| 76104cd117 | |||
| e211bf7131 | |||
| 32fc586c08 | |||
| 8fedc945bf | |||
| d481017746 | |||
| 19aad464da | |||
| 8df134e025 | |||
| 9091fcb1a1 | |||
| 9aa2d92d90 | |||
| 311fedaa12 | |||
| 9c36a9df78 | |||
| b47a27f3ac | |||
| 531638e946 | |||
| a724893887 | |||
| 7f4c74dec9 | |||
| 44cc99d616 | |||
| 7395cc910e | |||
| c53d764119 | |||
| 3eb0b161b2 | |||
| 65551afcba | |||
| f4376671ac | |||
| c012d7555b | |||
| 9ede414f01 | |||
| 062d2d696f | |||
| a53191fed5 | |||
| 8e27decdfd | |||
| 0dfc8ade68 | |||
| 1db65e3614 | |||
| a168340838 | |||
| 6604b9efbb | |||
| 2598375051 | |||
| 0f96414279 | |||
| edb18deb8f | |||
| 0770252e9b | |||
| b0453ea2ce | |||
| b7d7dc5201 | |||
| 5856f6d06a | |||
| a25051fead | |||
| 95138864f4 | |||
| 4045c848c4 | |||
| 3267d4c923 | |||
| 0e7d2b73be | |||
| 0474804d4b | |||
| 56034e6ed5 | |||
| df57a07dd5 | |||
| 3c12d1a960 | |||
| 87d6ad395f | |||
| 7c7e51b6e8 | |||
| 4ae1b4db03 | |||
| 04038c01f7 |
@@ -41,4 +41,3 @@ XmppAddr.hrl
|
||||
/test/*.beam
|
||||
/logs/
|
||||
/priv/sql
|
||||
mix.exs
|
||||
|
||||
+11
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+101
-68
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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) ->
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+49
-727
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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].
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user