25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-26 16:26:24 +01:00

Merge branch 'master' of git+ssh://git@gitorious.process-one.net/ejabberd/mainline

This commit is contained in:
Pablo Polvorin 2010-10-12 10:57:07 -03:00
commit ce9ce8293b
86 changed files with 4449 additions and 14105 deletions

View File

@ -35,6 +35,7 @@ clean:
rm -f *.out
rm -f *.pdf
rm -f *.toc
rm -f version.tex
[ ! -f contributed_modules.tex ] || rm contributed_modules.tex
distclean: clean

View File

@ -1,412 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 3.0.0-prealpha Developers Guide
</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<META name="GENERATOR" content="hevea 1.10">
<STYLE type="text/css">
.li-itemize{margin:1ex 0ex;}
.li-enumerate{margin:1ex 0ex;}
.dd-description{margin:0ex 0ex 1ex 4ex;}
.dt-description{margin:0ex;}
.toc{list-style:none;}
.thefootnotes{text-align:left;margin:0ex;}
.dt-thefootnotes{margin:0em;}
.dd-thefootnotes{margin:0em 0em 0em 2em;}
.footnoterule{margin:1em auto 1em 0px;width:50%;}
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
.title{margin:2ex auto;text-align:center}
.center{text-align:center;margin-left:auto;margin-right:auto;}
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
DIV TABLE{margin-left:inherit;margin-right:inherit;}
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
TD P{margin:0px;}
.boxed{border:1px solid black}
.textboxed{border:1px solid black}
.vbar{border:none;width:2px;background-color:black;}
.hbar{border:none;height:2px;width:100%;background-color:black;}
.hfill{border:none;height:1px;width:200%;background-color:black;}
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
.dcenter{margin:0ex auto;}
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
.marginpar{border:solid thin black; width:20%; text-align:left;}
.marginparleft{float:left; margin-left:0ex; margin-right:1ex;}
.marginparright{float:right; margin-left:1ex; margin-right:0ex;}
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
.part{margin:2ex auto;text-align:center}
</STYLE>
</HEAD>
<BODY >
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 3.0.0-prealpha Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
</TABLE><DIV CLASS="center">
<IMG SRC="logo.png" ALT="logo.png">
</DIV><BLOCKQUOTE CLASS="quotation"><I>I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC section Contents-->
<H2 CLASS="section"><!--SEC ANCHOR -->Contents</H2><!--SEC END --><UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc1">1&#XA0;&#XA0;Key Features</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc2">2&#XA0;&#XA0;Additional Features</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc3">3&#XA0;&#XA0;How it Works</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc4">3.1&#XA0;&#XA0;Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc5">3.2&#XA0;&#XA0;Local Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc6">3.3&#XA0;&#XA0;Session Manager</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc7">3.4&#XA0;&#XA0;S2S Manager</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc8">4&#XA0;&#XA0;Authentication</A>
<UL CLASS="toc">
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc9">4.0.1&#XA0;&#XA0;External</A>
</LI></UL>
</UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc10">5&#XA0;&#XA0;XML Representation</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc11">6&#XA0;&#XA0;Module <TT>xml</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc12">7&#XA0;&#XA0;Module <TT>xml_stream</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc13">8&#XA0;&#XA0;Modules</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc14">8.1&#XA0;&#XA0;Module gen_iq_handler</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2&#XA0;&#XA0;Services</A>
</LI></UL>
</LI></UL><P>Introduction
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1">1</A>&#XA0;&#XA0;Key Features</H2><!--SEC END --><P>
<A NAME="keyfeatures"></A>
</P><P><TT>ejabberd</TT> is:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced &#X2018;on the fly&#X2019;.</LI><LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Comprehensive documentation.
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
</LI><LI CLASS="li-itemize">Shared Roster Groups.
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
</LI><LI CLASS="li-itemize">XML-based protocol.
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
</LI></UL></LI></UL><!--TOC section Additional Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2">2</A>&#XA0;&#XA0;Additional Features</H2><!--SEC END --><P>
<A NAME="addfeatures"></A>
</P><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
Modular
<UL CLASS="itemize"><LI CLASS="li-itemize">
Load only the modules you want.
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
</LI></UL>
</LI><LI CLASS="li-itemize">Security
<UL CLASS="itemize"><LI CLASS="li-itemize">
SASL and STARTTLS for c2s and s2s connections.
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
</LI></UL>
</LI><LI CLASS="li-itemize">Databases
<UL CLASS="itemize"><LI CLASS="li-itemize">
Internal database for fast deployment (Mnesia).
</LI><LI CLASS="li-itemize">Native MySQL support.
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
</LI><LI CLASS="li-itemize">ODBC data storage support.
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
</LI><LI CLASS="li-itemize">Authentication
<UL CLASS="itemize"><LI CLASS="li-itemize">
Internal Authentication.
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
</LI></UL>
</LI><LI CLASS="li-itemize">Others
<UL CLASS="itemize"><LI CLASS="li-itemize">
Support for virtual hosting.
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--TOC section How it Works-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc3">3</A>&#XA0;&#XA0;How it Works</H2><!--SEC END --><P>
<A NAME="howitworks"></A></P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
be run on different machines that are connected via a network. They all must
have the ability to connect to port 4369 of all another nodes, and must have
the same magic cookie (see Erlang/OTP documentation, in other words the file
<TT>~ejabberd/.erlang.cookie</TT> must be the same on all nodes). This is
needed because all nodes exchange information about connected users, S2S
connections, registered services, etc&#X2026;</P><P>Each <TT>ejabberd</TT> node have following modules:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
router;
</LI><LI CLASS="li-itemize">local router.
</LI><LI CLASS="li-itemize">session manager;
</LI><LI CLASS="li-itemize">S2S manager;
</LI></UL><!--TOC subsection Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>&#XA0;&#XA0;Router</H3><!--SEC END --><P>This module is the main router of XMPP packets on each node. It routes
them based on their destinations domains. It has two tables: local and global
routes. First, domain of packet destination searched in local table, and if it
found, then the packet is routed to appropriate process. If no, then it
searches in global table, and is routed to the appropriate <TT>ejabberd</TT> node or
process. If it does not exists in either tables, then it sent to the S2S
manager.</P><!--TOC subsection Local Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc5">3.2</A>&#XA0;&#XA0;Local Router</H3><!--SEC END --><P>This module routes packets which have a destination domain equal to this server
name. If destination JID has a non-empty user part, then it routed to the
session manager, else it is processed depending on it&#X2019;s content.</P><!--TOC subsection Session Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc6">3.3</A>&#XA0;&#XA0;Session Manager</H3><!--SEC END --><P>This module routes packets to local users. It searches for what user resource
packet must be sended via presence table. If this resource is connected to
this node, it is routed to C2S process, if it connected via another node, then
the packet is sent to session manager on that node.</P><!--TOC subsection S2S Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>&#XA0;&#XA0;S2S Manager</H3><!--SEC END --><P>This module routes packets to other XMPP servers. First, it checks if an
open S2S connection from the domain of the packet source to the domain of
packet destination already exists. If it is open on another node, then it
routes the packet to S2S manager on that node, if it is open on this node, then
it is routed to the process that serves this connection, and if a connection
does not exist, then it is opened and registered.</P><!--TOC section Authentication-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc8">4</A>&#XA0;&#XA0;Authentication</H2><!--SEC END --><!--TOC subsubsection External-->
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A NAME="htoc9">4.0.1</A>&#XA0;&#XA0;External</H4><!--SEC END --><P>
<A NAME="externalauth"></A>
</P><P>The external authentication script follows
<A HREF="http://www.erlang.org/doc/tutorial/c_portdriver.html">the erlang port driver API</A>.</P><P>That script is supposed to do theses actions, in an infinite loop:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
read from stdin: AABBBBBBBBB.....
<UL CLASS="itemize"><LI CLASS="li-itemize">
A: 2 bytes of length data (a short in network byte order)
</LI><LI CLASS="li-itemize">B: a string of length found in A that contains operation in plain text
operation are as follows:
<UL CLASS="itemize"><LI CLASS="li-itemize">
auth:User:Server:Password (check if a username/password pair is correct)
</LI><LI CLASS="li-itemize">isuser:User:Server (check if it&#X2019;s a valid user)
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user&#X2019;s password)
</LI><LI CLASS="li-itemize">tryregister:User:Server:Password (try to register an account)
</LI><LI CLASS="li-itemize">removeuser:User:Server (remove this account)
</LI><LI CLASS="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
</LI></UL>
</LI></UL>
</LI><LI CLASS="li-itemize">write to stdout: AABB
<UL CLASS="itemize"><LI CLASS="li-itemize">
A: the number 2 (coded as a short, which is bytes length of following result)
</LI><LI CLASS="li-itemize">B: the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
</LI></UL>
</LI></UL><P>Example python script
</P><PRE CLASS="verbatim">#!/usr/bin/python
import sys
from struct import *
def from_ejabberd():
input_length = sys.stdin.read(2)
(size,) = unpack('&gt;h', input_length)
return sys.stdin.read(size).split(':')
def to_ejabberd(bool):
answer = 0
if bool:
answer = 1
token = pack('&gt;hh', 2, answer)
sys.stdout.write(token)
sys.stdout.flush()
def auth(username, server, password):
return True
def isuser(username, server):
return True
def setpass(username, server, password):
return True
while True:
data = from_ejabberd()
success = False
if data[0] == "auth":
success = auth(data[1], data[2], data[3])
elif data[0] == "isuser":
success = isuser(data[1], data[2])
elif data[0] == "setpass":
success = setpass(data[1], data[2], data[3])
to_ejabberd(success)
</PRE><!--TOC section XML Representation-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc10">5</A>&#XA0;&#XA0;XML Representation</H2><!--SEC END --><P>
<A NAME="xmlrepr"></A></P><P>Each XML stanza is represented as the following tuple:
</P><PRE CLASS="verbatim">XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
Name = string()
Attrs = [Attr]
Attr = {Key, Val}
Key = string()
Val = string()
ElementOrCDATA = XMLElement | CDATA
CDATA = {xmlcdata, string()}
</PRE><P>E.&#XA0;g. this stanza:
</P><PRE CLASS="verbatim">&lt;message to='test@conference.example.org' type='groupchat'&gt;
&lt;body&gt;test&lt;/body&gt;
&lt;/message&gt;
</PRE><P>is represented as the following structure:
</P><PRE CLASS="verbatim">{xmlelement, "message",
[{"to", "test@conference.example.org"},
{"type", "groupchat"}],
[{xmlelement, "body",
[],
[{xmlcdata, "test"}]}]}}
</PRE><!--TOC section Module <TT>xml</TT>-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc11">6</A>&#XA0;&#XA0;Module <TT>xml</TT></H2><!--SEC END --><P>
<A NAME="xmlmod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>element_to_string(El) -&gt; string()</CODE>
<PRE CLASS="verbatim">El = XMLElement
</PRE>Returns string representation of XML stanza <TT>El</TT>.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>crypt(S) -&gt; string()</CODE>
<PRE CLASS="verbatim">S = string()
</PRE>Returns string which correspond to <TT>S</TT> with encoded XML special
characters.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -&gt; EList</CODE>
<PRE CLASS="verbatim">ECList = [ElementOrCDATA]
EList = [XMLElement]
</PRE><TT>EList</TT> is a list of all non-CDATA elements of ECList.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -&gt; Res</CODE>
<PRE CLASS="verbatim">El = XMLElement
Path = [PathItem]
PathItem = PathElem | PathAttr | PathCDATA
PathElem = {elem, Name}
PathAttr = {attr, Name}
PathCDATA = cdata
Name = string()
Res = string() | XMLElement
</PRE>If <TT>Path</TT> is empty, then returns <TT>El</TT>. Else sequentially
consider elements of <TT>Path</TT>. Each element is one of:
<DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>{elem, Name}</CODE> <TT>Name</TT> is name of subelement of
<TT>El</TT>, if such element exists, then this element considered in
following steps, else returns empty string.
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>{attr, Name}</CODE> If <TT>El</TT> have attribute <TT>Name</TT>, then
returns value of this attribute, else returns empty string.
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>cdata</CODE> Returns CDATA of <TT>El</TT>.
</DD></DL></DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description">TODO:
<PRE CLASS="verbatim"> get_cdata/1, get_tag_cdata/1
get_attr/2, get_attr_s/2
get_tag_attr/2, get_tag_attr_s/2
get_subtag/2
</PRE></DD></DL><!--TOC section Module <TT>xml_stream</TT>-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc12">7</A>&#XA0;&#XA0;Module <TT>xml_stream</TT></H2><!--SEC END --><P>
<A NAME="xmlstreammod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>parse_element(Str) -&gt; XMLElement | {error, Err}</CODE>
<PRE CLASS="verbatim">Str = string()
Err = term()
</PRE>Parses <TT>Str</TT> using XML parser, returns either parsed element or error
tuple.
</DD></DL><!--TOC section Modules-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc13">8</A>&#XA0;&#XA0;Modules</H2><!--SEC END --><P>
<A NAME="emods"></A></P><!--TOC subsection Module gen_iq_handler-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc14">8.1</A>&#XA0;&#XA0;Module gen_iq_handler</H3><!--SEC END --><P>
<A NAME="geniqhandl"></A></P><P>The module <CODE>gen_iq_handler</CODE> allows to easily write handlers for IQ packets
of particular XML namespaces that addressed to server or to users bare JIDs.</P><P>In this module the following functions are defined:
</P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>add_iq_handler(Component, Host, NS, Module, Function, Type)</CODE>
<PRE CLASS="verbatim">Component = Module = Function = atom()
Host = NS = string()
Type = no_queue | one_queue | parallel
</PRE>Registers function <CODE>Module:Function</CODE> as handler for IQ packets on
virtual host <CODE>Host</CODE> that contain child of namespace <CODE>NS</CODE> in
<CODE>Component</CODE>. Queueing discipline is <CODE>Type</CODE>. There are at least
two components defined:
<DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>ejabberd_local</CODE> Handles packets that addressed to server JID;
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>ejabberd_sm</CODE> Handles packets that addressed to users bare JIDs.
</DD></DL>
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_iq_handler(Component, Host, NS)</CODE>
<PRE CLASS="verbatim">Component = atom()
Host = NS = string()
</PRE>Removes IQ handler on virtual host <CODE>Host</CODE> for namespace <CODE>NS</CODE> from
<CODE>Component</CODE>.
</DD></DL><P>Handler function must have the following type:
</P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>Module:Function(From, To, IQ)</CODE>
<PRE CLASS="verbatim">From = To = jid()
</PRE></DD></DL><PRE CLASS="verbatim">-module(mod_cputime).
-behaviour(gen_mod).
-export([start/2,
stop/1,
process_local_iq/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(NS_CPUTIME, "ejabberd:cputime").
start(Host, Opts) -&gt;
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_CPUTIME,
?MODULE, process_local_iq, IQDisc).
stop(Host) -&gt;
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_CPUTIME).
process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) -&gt;
case Type of
set -&gt;
{iq, ID, error, XMLNS,
[SubEl, ?ERR_NOT_ALLOWED]};
get -&gt;
CPUTime = element(1, erlang:statistics(runtime))/1000,
SCPUTime = lists:flatten(io_lib:format("~.3f", CPUTime)),
{iq, ID, result, XMLNS,
[{xmlelement, "query",
[{"xmlns", ?NS_CPUTIME}],
[{xmlelement, "cputime", [], [{xmlcdata, SCPUTime}]}]}]}
end.
</PRE><!--TOC subsection Services-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc15">8.2</A>&#XA0;&#XA0;Services</H3><!--SEC END --><P>
<A NAME="services"></A></P><PRE CLASS="verbatim">-module(mod_echo).
-behaviour(gen_mod).
-export([start/2, init/1, stop/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
start(Host, Opts) -&gt;
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost])).
init(Host) -&gt;
ejabberd_router:register_local_route(Host),
loop(Host).
loop(Host) -&gt;
receive
{route, From, To, Packet} -&gt;
ejabberd_router:route(To, From, Packet),
loop(Host);
stop -&gt;
ejabberd_router:unregister_route(Host),
ok;
_ -&gt;
loop(Host)
end.
stop(Host) -&gt;
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
</PRE><!--CUT END -->
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
</HTML>

View File

@ -1,131 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 3.0.0-prealpha Feature Sheet
</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<META name="GENERATOR" content="hevea 1.10">
<STYLE type="text/css">
.li-itemize{margin:1ex 0ex;}
.li-enumerate{margin:1ex 0ex;}
.dd-description{margin:0ex 0ex 1ex 4ex;}
.dt-description{margin:0ex;}
.toc{list-style:none;}
.thefootnotes{text-align:left;margin:0ex;}
.dt-thefootnotes{margin:0em;}
.dd-thefootnotes{margin:0em 0em 0em 2em;}
.footnoterule{margin:1em auto 1em 0px;width:50%;}
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
.title{margin:2ex auto;text-align:center}
.center{text-align:center;margin-left:auto;margin-right:auto;}
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
DIV TABLE{margin-left:inherit;margin-right:inherit;}
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
TD P{margin:0px;}
.boxed{border:1px solid black}
.textboxed{border:1px solid black}
.vbar{border:none;width:2px;background-color:black;}
.hbar{border:none;height:2px;width:100%;background-color:black;}
.hfill{border:none;height:1px;width:200%;background-color:black;}
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
.dcenter{margin:0ex auto;}
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
.marginpar{border:solid thin black; width:20%; text-align:left;}
.marginparleft{float:left; margin-left:0ex; margin-right:1ex;}
.marginparright{float:right; margin-left:1ex; margin-right:0ex;}
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
.part{margin:2ex auto;text-align:center}
SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
</STYLE>
</HEAD>
<BODY >
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 3.0.0-prealpha Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD></TR>
</TABLE><DIV CLASS="center">
<IMG SRC="logo.png" ALT="logo.png">
</DIV><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE><P>Introduction
<A NAME="intro"></A></P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. &#X2014;
Joeri</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1"></A>Key Features</H2><!--SEC END --><P>
<A NAME="keyfeatures"></A>
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. &#X2014;
Peter Saint-Andr&#XE9;, Executive Director of the Jabber Software Foundation</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced &#X2018;on the fly&#X2019;.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Comprehensive documentation.
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
</LI><LI CLASS="li-itemize">Shared Roster Groups.
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
</LI><LI CLASS="li-itemize">XML-based protocol.
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
</LI></UL></LI></UL><!--TOC section Additional Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2"></A>Additional Features</H2><!--SEC END --><P>
<A NAME="addfeatures"></A>
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>ejabberd is making inroads to solving the "buggy incomplete server" problem &#X2014;
Justin Karneges, Founder of the Psi and the Delta projects</I></FONT></BLOCKQUOTE><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
Modular
<UL CLASS="itemize"><LI CLASS="li-itemize">
Load only the modules you want.
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
</LI></UL>
</LI><LI CLASS="li-itemize">Security
<UL CLASS="itemize"><LI CLASS="li-itemize">
SASL and STARTTLS for c2s and s2s connections.
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
</LI></UL>
</LI><LI CLASS="li-itemize">Databases
<UL CLASS="itemize"><LI CLASS="li-itemize">
Internal database for fast deployment (Mnesia).
</LI><LI CLASS="li-itemize">Native MySQL support.
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
</LI><LI CLASS="li-itemize">ODBC data storage support.
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
</LI><LI CLASS="li-itemize">Authentication
<UL CLASS="itemize"><LI CLASS="li-itemize">
Internal Authentication.
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
</LI></UL>
</LI><LI CLASS="li-itemize">Others
<UL CLASS="itemize"><LI CLASS="li-itemize">
Support for virtual hosting.
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--CUT END -->
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
</HTML>

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
% ejabberd version (automatically generated).
\newcommand{\version}{3.0.0-alpha-x}

View File

@ -1,75 +0,0 @@
#!/bin/sh
#
# PROVIDE: ejabberd
# REQUIRE: DAEMON
# KEYWORD: shutdown
#
HOME=/usr/pkg/jabber D=/usr/pkg/jabber/ejabberd export HOME
name="ejabberd"
rcvar=$name
if [ -r /etc/rc.conf ]
then
. /etc/rc.conf
else
eval ${rcvar}=YES
fi
# $flags from environment overrides ${rcvar}_flags
if [ -n "${flags}" ]
then
eval ${rcvar}_flags="${flags}"
fi
checkyesno()
{
eval _value=\$${1}
case $_value in
[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;;
[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;;
*)
echo "\$${1} is not set properly."
return 1
;;
esac
}
cmd=${1:-start}
case ${cmd} in
force*)
cmd=${cmd#force}
eval ${rcvar}=YES
;;
esac
if checkyesno ${rcvar}
then
else
exit 0
fi
case ${cmd} in
start)
if [ -x $D/src ]; then
echo "Starting ${name}."
cd $D/src
ERL_MAX_PORTS=32000 export ERL_MAX_PORTS
ulimit -n $ERL_MAX_PORTS
su jabber -c "/usr/pkg/bin/erl -sname ejabberd -s ejabberd -heart -detached -sasl sasl_error_logger '{file, \"ejabberd-sasl.log\"}' &" \
1>/dev/null 2>&1
fi
;;
stop)
echo "rpc:call('ejabberd@`hostname -s`', init, stop, [])." | \
su jabber -c "/usr/pkg/bin/erl -sname ejabberdstop"
;;
restart)
echo "rpc:call('ejabberd@`hostname -s`', init, restart, [])." | \
su jabber -c "/usr/pkg/bin/erl -sname ejabberdrestart"
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac

View File

@ -1,81 +0,0 @@
#!/bin/sh
echo '1. fetch, compile, and install erlang'
if [ ! pkg_info erlang 1>/dev/null 2>&1 ]; then
cd /usr/pkgsrc/lang/erlang
make fetch-list|sh
make
make install
fi
if pkg_info erlang | grep -q erlang-9.1nb1; then
else
echo "erlang-9.1nb1 not installed" 1>&2
exit 1
fi
echo '2. install crypt_drv.so'
if [ ! -d /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib ] ; then
mkdir -p /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
fi
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/crypto_drv.so ]; then
cp work/otp*/lib/crypto/priv/*/*/crypto_drv.so \
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
fi
echo '3. compile and install elibcrypto.so'
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/elibcrypto.so ]; then
cd /usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/crypto/c_src
ld -r -u CRYPTO_set_mem_functions -u MD5 -u MD5_Init -u MD5_Update \
-u MD5_Final -u SHA1 -u SHA1_Init -u SHA1_Update -u SHA1_Final \
-u des_set_key -u des_ncbc_encrypt -u des_ede3_cbc_encrypt \
-L/usr/lib -lcrypto -o ../priv/obj/i386--netbsdelf/elibcrypto.o
cc -shared \
-L/usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/erl_interface/obj/i386--netbsdelf \
-o ../priv/obj/i386--netbsdelf/elibcrypto.so \
../priv/obj/i386--netbsdelf/elibcrypto.o -L/usr/lib -lcrypto
cp ../priv/obj/i386--netbsdelf/elibcrypto.so \
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
fi
echo '4. compile and install ssl_esock'
if [ ! -f /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/bin/ssl_esock ]; then
cd /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/obj/
make
fi
echo '5. initial ejabberd configuration'
cd /usr/pkg/jabber/ejabberd/src
./configure
echo '6. edit ejabberd Makefiles'
for M in Makefile mod_*/Makefile; do
if [ ! -f $M.orig ]; then
mv $M $M.orig
sed -e s%/usr/local%/usr/pkg%g < $M.orig > $M
fi
done
echo '7. compile ejabberd'
gmake
for A in mod_muc mod_pubsub; do
(cd $A; gmake)
done
echo ''
echo 'now edit ejabberd.cfg'
echo ''
echo 'to start ejabberd: erl -sname ejabberd -s ejabberd'

View File

@ -1,66 +0,0 @@
% jabber.dbc.mtview.ca.us
override_acls.
{acl, admin, {user, "mrose", "jabber.dbc.mtview.ca.us"}}.
{access, announce, [{allow, admin},
{deny, all}]}.
{access, c2s, [{deny, blocked},
{allow, all}]}.
{access, c2s_shaper, [{none, admin},
{normal, all}]}.
{access, configure, [{allow, admin},
{deny, all}]}.
{access, disco_admin, [{allow, admin},
{deny, all}]}.
{access, muc_admin, [{allow, admin},
{deny, all}]}.
{access, register, [{deny, all}]}.
{access, s2s_shaper, [{fast, all}]}.
{auth_method, internal}.
{host, "jabber.dbc.mtview.ca.us"}.
{outgoing_s2s_port, 5269}.
{shaper, normal, {maxrate, 1000}}.
{shaper, fast, {maxrate, 50000}}.
{welcome_message, none}.
{listen, [{5222, ejabberd_c2s,
[{access, c2s},
{shaper, c2s_shaper}]},
{5223, ejabberd_c2s,
[{access, c2s},
{shaper, c2s_shaper},
{ssl, [{certfile, "/etc/openssl/certs/ejabberd.pem"}]}]},
{5269, ejabberd_s2s_in,
[{shaper, s2s_shaper}]}]}.
{modules, [
{mod_register, []},
{mod_roster, []},
{mod_privacy, []},
{mod_configure, []},
{mod_disco, []},
{mod_stats, []},
{mod_vcard, []},
{mod_offline, []},
{mod_echo, [{host, "echo.jabber.dbc.mtview.ca.us"}]},
{mod_private, []},
% {mod_irc, []},
{mod_muc, []},
{mod_pubsub, []},
{mod_time, []},
{mod_last, []},
{mod_version, []}
]}.
% Local Variables:
% mode: erlang
% End:

View File

@ -1,77 +0,0 @@
<!-- aim-transport.xml -->
<jabber>
<!--
You need to add elogger and rlogger entries when using ejabberd.
In this case the transport will do the logging.
-->
<log id='elogger'>
<host/>
<logtype/>
<format>%d: [%t] (%h): %s</format>
<file>/var/log/jabber/aim-transport-error.log</file>
</log>
<log id='rlogger'>
<host/>
<logtype>record</logtype>
<format>%d %h %s</format>
<file>/var/log/jabber/aim-transport-record.log</file>
</log>
<!--
ejabberd do not provide XDB services.
xdb_file.so is loaded in to handle all XDB requests.
-->
<xdb id="xdb">
<host/>
<load>
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file> <!-- This file is part of jabberd-1.4.x. -->
</load>
<xdb_file xmlns="jabber:config:xdb_file">
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
</xdb_file>
</xdb>
<!--
Make sure that all host names here are resolveable via DNS if you
want the transport to be available to the public.
-->
<service id='aim.SERVER.COM'>
<!-- aim-transport configuration. -->
<aimtrans xmlns='jabber:config:aimtrans'>
<vCard>
<FN>AIM/ICQ Transport</FN>
<DESC>This is the AIM/ICQ Transport.</DESC>
<MAIL>EMAIL@ADDRESS.COM</MAIL>
<URL>http://aim-transport.jabberstudio.org/</URL>
</vCard>
<charset>cp1252</charset>
</aimtrans>
<!-- aim-transport module. -->
<load>
<aim_transport>/usr/local/lib/jabber/aim-transport.so</aim_transport>
</load>
</service>
<!--
The settings below have to match the settings you made
in your ejabberd.cfg configuration file.
-->
<service id="icq-linker">
<uplink/>
<connect>
<ip>127.0.0.1</ip>
<port>5233</port>
<secret>SECRET</secret>
</connect>
</service>
<pidfile>/var/run/jabber/aim-transport.pid</pidfile>
</jabber>

View File

@ -1,136 +0,0 @@
<!-- ile.xml -->
<config>
<jabber>
<server>127.0.0.1</server>
<port>5238</port>
<secret>SECRET</secret>
<service>ile.SERVER.COM</service>
<connectsleep>7</connectsleep> <!-- seconds to wait if we get disconnected -->
<language>en</language>
<vCard>
<FN>I Love Email</FN>
<DESC>With this service you can receive email notifications.
Security warning: Be careful when using this. Your password will travel in clear from your client to your jabber server if you don't use SSL and it will probably travel in clear from the jabber server to your email server. Use with care. This shouldn't be an issue in your Intranet, but it is if you use an ILE installed in a foreign jabber server.</DESC>
<MAIL>EMAIL@ADDRESS.COM</MAIL>
<URL>http://ile.jabberstudio.org/</URL>
</vCard>
</jabber>
<debug>
<file>/var/log/jabber/ile.log</file>
<level>1</level> <!-- man Net::Jabber::Debug -->
</debug>
<mail>
<checkinterval>10</checkinterval> <!-- in minutes -->
<timeout>20</timeout> <!-- timeout for IMAP/POP connection, in seconds -->
</mail>
<files>
<users>/var/spool/jabber/ile.SERVER.COM/users.db</users>
<passwords>/var/spool/jabber/ile.SERVER.COM/passwords.db</passwords>
<hosts>/var/spool/jabber/ile.SERVER.COM/hosts.db</hosts>
<types>/var/spool/jabber/ile.SERVER.COM/types.db</types>
<notifyxa>/var/spool/jabber/ile.SERVER.COM/notifyxa.db</notifyxa>
<notifydnd>/var/spool/jabber/ile.SERVER.COM/notifydnd.db</notifydnd>
<urls>/var/spool/jabber/ile.SERVER.COM/urls.db</urls>
</files>
<form>
<en>
<instructions>Please fill in the fields,according to your email account settings and notification preferences</instructions>
<title>ILE: Email notification service</title>
<email_options>Email account settings</email_options>
<user>Username</user>
<pass>Password</pass>
<host>Hostname</host>
<type>Type</type>
<newmail>You have received NUM email messages since last time I checked, which was CHECKINTERVAL minutes ago.</newmail>
<errorcheck>There was an error while trying to check mail for ACCOUNT.</errorcheck>
<notify_options>Notification Options</notify_options>
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
<webmail_url>Webmail URL</webmail_url>
<webmail_login>Login to ACCOUNT</webmail_login>
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
</en>
<es>
<instructions>Por favor, rellene los campos del formulario.</instructions>
<title>ILE: Servicio de notificación de correo</title>
<email_options>Configuración de la cuenta de correo</email_options>
<user>Usuario</user>
<pass>Clave</pass>
<host>Host</host>
<type>Tipo</type>
<newmail>Ha recibido NUM email(s) desde la última comprobación que fue hace CHECKINTERVAL minutos</newmail>
<errorcheck>Ha habido un error en la comprobación del correo para la cuenta ACCOUNT.</errorcheck>
<notify_options>Opciones de notificación</notify_options>
<notifyxa>Notificar incluso si muy ausente (XA)</notifyxa>
<notifydnd>Notificar incluso si no molestar (DND)</notifydnd>
<webmail_url>Webmail URL</webmail_url>
<webmail_login>Leer correo de ACCOUNT</webmail_login>
<iledesc>ILE: un notificador de nuevo email - http://ile.jabberstudio.org</iledesc>
</es>
<ca>
<instructions>Ompli els camps del formulari.</instructions>
<title>ILE: Servei de notificació de nou email</title>
<email_options>Dades del compte de mail</email_options>
<user>Usuari</user>
<pass>Clau</pass>
<host>Host</host>
<type>Tipus</type>
<newmail>Ha rebut NUM email(s) des de la última comprobació que va ser fa CHECKINTERVAL minuts.</newmail>
<errorcheck>S'ha produit un error en la comprobació del correu per al compte ACCOUNT.</errorcheck>
<notify_options>Opcions de notificació</notify_options>
<notifyxa>Notificar si molt absent (XA)</notifyxa>
<notifydnd>Notificar si no molestar (DND)</notifydnd>
<webmail_url>Webmail URL</webmail_url>
<webmail_login>Llegir correu de ACCOUNT</webmail_login>
<iledesc>ILE: un notificador de nou email - http://ile.jabberstudio.org</iledesc>
</ca>
<ro>
<!-- Contributed by Adrian Rappa -->
<instructions>Va rog completati urmatoarele campuri</instructions>
<title>I Love Email: new email notification service</title>
<email_options>Email account settings</email_options>
<user>Nume utilizator</user>
<pass>Parola</pass>
<host>Nume gazda</host>
<type>Tip</type>
<newmail>Ati primit NUM mesaj(e) de la ultima verificare, care a fost acum CHECKINTERVAL minute.</newmail>
<errorcheck>A fost eroare in timp ce incercam sa verific posta pentru ACCOUNT.</errorcheck>
<notify_options>Notification Options</notify_options>
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
<webmail_url>Webmail URL</webmail_url>
<webmail_login>Login to ACCOUNT</webmail_login>
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
</ro>
<nl>
<!-- Contributed by Sander Devrieze -->
<instructions>Vul volgende velden in.</instructions>
<title>ILE: Dienst voor e-mailnotificaties</title>
<email_options>Instellingen van e-mailaccount</email_options>
<user>Gebruikersnaam</user>
<pass>Wachtwoord</pass>
<host>Inkomende mailserver</host>
<type>Type verbinding</type>
<newmail>U hebt NUM berichten ontvangen sinds CHECKINTERVAL minuten geleden.</newmail>
<errorcheck>Fout tijdens controle op nieuwe e-mails bij ACCOUNT. ILE zal deze account niet meer opnieuw controleren tot u uw registratiegegevens wijzigt of opnieuw aanmeldt.</errorcheck>
<notify_options>Notificatie-instellingen</notify_options>
<notifyxa>Notificeer ook in de status Niet Beschikbaar (XA)</notifyxa>
<notifydnd>Notificeer ook in de status Niet Storen (DND)</notifydnd>
<webmail_url>URL van webmail</webmail_url>
<webmail_login>Aanmelden op ACCOUNT</webmail_login>
<iledesc>ILE: een dienst om e-mailnotificaties te ontvangen: http://ile.jabberstudio.org</iledesc>
</nl>
</form>
</config>

View File

@ -1,149 +0,0 @@
<jggtrans>
<service jid="gg.SERVER.COM"/>
<!-- This connects the jabber-gg-transport process to ejabberd. -->
<connect id="gglinker">
<ip>127.0.0.1</ip>
<port>5237</port>
<secret>SECRET</secret>
</connect>
<register>
<!-- This tag contains the message displayed to users at registration time.
You can use <p/> and/or <br/> to break lines. Multiple spaces and newlines
are converted to just one, so formatting of config file doesn't really matter. -->
<instructions>
Fill in your GG number (after "username")
and password to register on the transport.
<p/>To change your information in the GaduGadu directory you need to fill in the other fields.
<p/>To remove registration you need to leave the form blank.
</instructions>
</register>
<search>
<!-- This tag contains the message displayed to users at search time. -->
<instructions>
To search people:<br/>
First fill in surname or family name, nickname, city, birthyear or range of birthyears (eg. 1950-1960)
and gender (you may fill in more fields at once).<br/>
or<br/>
Fill in phone number<br/>
or<br/>
Fill in the GG number of the person you are searching.
</instructions>
</search>
<gateway>
<!-- This is message, that may be displayed to user when adding gg contact. -->
<desc>Please fill in the GaduGadu number of the person you want to add.</desc>
<!-- And this is the prompt for GG number. -->
<prompt>GG Nummer</prompt>
</gateway>
<vCard>
<FN>Gadu-Gadu Transport</FN>
<DESC>This is the Gadu-Gadu Transport.</DESC>
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
<URL>http://www.jabberstudio.org/projects/jabber-gg-transport/</URL>
</vCard>
<!-- Default user locale (language).
Empty means system locale setting,
no (or commented-out) <default_locale> tag means no translations. -->
<!-- <default_locale>pl_PL</default_locale> -->
<!-- Logger configuration.
You may configure one logger of type "syslog" and/or one of type "file".
You may also not configure logging at all. -->
<log type="syslog" facility="local0"/>
<log type="file">/var/log/jabber/jabber-gg-transport.log</log>
<!-- Uncomment this, if you want proxy to be used for Gadu-Gadu connection. -->
<!--
<proxy>
<ip>127.0.0.1</ip>
<port>8080</port>
</proxy>
-->
<!-- You can change these values according to your needs. -->
<conn_timeout>60</conn_timeout>
<ping_interval>10</ping_interval>
<!-- Gadu-Gadu server doesn't seem to answer pings anymore :-(
So let's give it 10 year :-) -->
<pong_timeout>315360000</pong_timeout>
<!-- This time after disconnection from Gadu-Gadu server the transport
will try to connect again. -->
<reconnect>300</reconnect>
<!-- How long to wait before restart, after jabber server connection is broken
negative value means, that jggtrans should terminate. -->
<restart_timeout>60</restart_timeout>
<!-- Delay between the unavailable presence is received from user and loggin out
from Gadu-Gadu - for nice <presence type="invisible"/> support. -->
<disconnect_delay>5</disconnect_delay>
<!-- list of Gadu-Gadu servers to use.
<hub/> means "use GG hub to find server"
<server/> tag should contain server address and may contain "port"
attribute with port number. When TLS is available (supported by libgadu)
it will be used unless "tls" attribute is set to "no". Please notice,
that not all servers will accept TLS connections.
Servers (including hub) are tried in order as they appear in <servers/>
element.
A reasonable default server list is hardcoded in jggtrans.
-->
<!--
<servers>
<hub/>
<server port="443">217.17.41.90</server>
<server tls="no">217.17.41.85</server>
<server tls="no">217.17.41.88</server>
</servers>
-->
<!-- Spool directory. This is the place, where user info will be stored. -->
<!-- Be careful about permissions - users' Gadu-Gadu passwords are stored there. -->
<spool>/var/spool/jabber/gg.SERVER.COM/</spool>
<!-- Where to store pid file. This tag is optional. -->
<pidfile>/var/run/jabber/jabber-gg-transport.pid</pidfile>
<!-- jid allowed to do some administrative task (eg. discovering online users).
May be used multiple times. -->
<admin>GG_TRANSPORT_ADMIN@SERVER.COM</admin>
<!-- ACL gives detailed access control to the transport. -->
<acl>
<!-- Example entries: -->
<allow who="admin@SERVER.COM" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
<!-- will allow statistics gathering to admin@SERVER.COM -->
<deny who="*" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
<!-- will deny statistics gathering for anybody else -->
<!-- <allow who="*@SERVER.COM"/> -->
<!-- will allow anything else to users from "SERVER.COM" -->
<!-- <deny what="iq/query?xmlns=jabber:x:register"/> -->
<!-- will deny registration for all other users -->
<!-- <allow what="presence"/> -->
<!-- allow presence from anybody -->
<!-- <allow what="iq"/> -->
<!-- allow iq from anybody -->
<!-- <allow what="message"/> -->
<!-- allow message from anybody -->
<!-- <deny/> -->
<!-- will deny anything else -->
</acl>
</jggtrans>

View File

@ -1,128 +0,0 @@
<!-- jit.xml -->
<jabber>
<!--
You need to add elogger and rlogger entries here when using ejabberd.
In this case the transport will do the logging.
-->
<log id='elogger'>
<host/>
<logtype/>
<file>/var/log/jabber/jit-error</file> <!-- WPJabber logs with date. -->
</log>
<log id='rlogger'>
<host/>
<logtype>record</logtype>
<file>/var/log/jabber/jit-record</file> <!-- WPJabber logs with date. -->
</log>
<!--
ejabberd do not provide XDB services.
xdb_file-jit.so (the renamed xdb_file.so from WPJabber) is
loaded in to handle all XDB requests.
Read also the documentation in xdb_file/README from the JIT package.
-->
<xdb id="xdb">
<host/>
<load>
<xdb_file>/usr/local/lib/jabber/xdb_file.so</xdb_file> <!-- The xdb_file.so from WPJabber/JIT. -->
</load>
<xdb_file xmlns="jabber:config:xdb_file">
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
</xdb_file>
</xdb>
<!--
Make sure that all host names here are resolveable via DNS if you
want the transport to be available to the public.
-->
<service id="icq.SERVER.COM">
<!--
Replace SERVER.COM with the same as above to enable sms.
-->
<host>sms.icq.SERVER.COM</host>
<!-- JIT configuration. -->
<icqtrans xmlns="jabber:config:icqtrans">
<sms>
<host>sms.icq.SERVER.COM</host>
<!-- Status of virtual "sms-contacts". -->
<show>away</show>
<status/>
</sms>
<instructions>Fill in your UIN and password.</instructions>
<search>Search ICQ users.</search>
<vCard>
<FN>ICQ Transport (JIT)</FN>
<DESC>This is the Jabber ICQ Transport.</DESC>
<MAIL>EMAIL@ADDRESS.COM</MAIL>
<URL>http://jit.jabberstudio.org/</URL>
</vCard>
<!-- Hashtable for users. -->
<prime>3907</prime>
<!-- Send messages from ICQ as chat to Jabber clients. -->
<chat/>
<!-- Enable this for ICQ web presence. -->
<web/>
<!--
If you don't want jabber:x:data forms
in reg and search uncomment this tag
(Not recomended).
-->
<no_xdata/>
<!--
This tag is necessary when using ejabberd.
In this way JIT will have its own contact list.
-->
<own_roster/>
<!--
When present, this tag will tell JIT not to try to
get the user's roster (which will take a bit of time
to fail in scenarios described above).
-->
<no_jabber_roster/>
<!-- File with stats. -->
<user_count_file>/var/spool/jabber/jit-count</user_count_file>
<!--
Interval beetween checking sessions: ping, messages, acks.
-->
<session_check>5</session_check>
<!-- Reconnect retries. -->
<reconnects>5</reconnects>
<!--
Time in sec when session can be inactive, 0=disabled.
-->
<session_timeout>18000</session_timeout>
<charset>windows-1252</charset>
<server>
<host port="5190">login.icq.com</host>
</server>
</icqtrans>
<!-- JIT module. -->
<load>
<icqtrans>/usr/local/lib/jabber/jit.so</icqtrans>
</load>
</service>
<!--
The settings below have to match the settings you made
in your ejabberd.cfg configuration file.
-->
<service id="icq-linker">
<host>SERVER.COM</host>
<uplink/>
<connect>
<ip>127.0.0.1</ip>
<port>5234</port>
<secret>SECRET</secret>
</connect>
</service>
<pidfile>/var/run/jabber/jit.pid</pidfile>
</jabber>

View File

@ -1,118 +0,0 @@
<!-- msn-transport.xml -->
<jabber>
<!--
You need to add elogger and rlogger entries here when using ejabberd.
In this case the transport will do the logging.
-->
<log id='elogger'>
<host/>
<logtype/>
<format>%d: [%t] (%h): %s</format>
<file>/var/log/jabber/msn-transport-error.log</file>
</log>
<log id='rlogger'>
<host/>
<logtype>record</logtype>
<format>%d %h %s</format>
<file>/var/log/jabber/msn-transport-record.log</file>
</log>
<!--
ejabberd do not provide XDB services.
xdb_file.so is loaded in to handle all XDB requests.
-->
<xdb id="xdb">
<host/>
<load>
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
</load>
<xdb_file xmlns="jabber:config:xdb_file">
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
</xdb_file>
</xdb>
<!--
Make sure that all host names here are resolveable via DNS if you
want the transport to be available to the public.
-->
<service id="msn.SERVER.COM">
<!-- msn-transport configuration. -->
<msntrans xmlns="jabber:config:msntrans">
<instructions>Fill in your MSN account and password (eg: user1@hotmail.com). A nickname is optional.</instructions>
<vCard>
<FN>MSN Transport</FN>
<DESC>This is the MSN Transport.</DESC>
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
<URL>http://msn-transport.jabberstudio.org/</URL>
</vCard>
<!--
Conference support allows you to create groupchat rooms on the
msn-transport and invite MSN users to join.
-->
<conference id="conference.msn.SERVER.COM">
<!--
This will make MSN transport invite you to a special groupchat
room when more then one user joins a normal one-on-one session.
Joining this room will make MSN transport "switch" the session
into groupchat mode. If you ignore it, MSN transport will
continue to send the messages as one-on-one chats.
-->
<invite>More than one user entered this chat session. Enter this room to switch to groupchat modus.</invite>
<notice>
<join> is available</join>
<leave> has leaved the room</leave>
</notice>
</conference>
<!-- Enable Hotmail inbox notification. -->
<headlines/>
<!--
Enable fancy friendly names
If the user enters a nickname upon registration, and the user has
a status message, their MSN friendly name will be "nickname - status message".
If the user does not enter a nickname on registration, but they do have
a status message, their friendly name will just be their status message.
If the user did enter a nickname on registration, but they have a blank status message,
then their friendly name will just be the registered nickname.
If the user did not enter a nickname on registration, and they have a blank status message,
their nickname will just be the username portion of their JID.
If the above chosen friendly name is too long, then it will be truncated and "..." placed
at the end. MSN only supports friendly names of 128 characters, so this is unavoidable.
If this is disabled, then the registered nick is always sent as the MSN friendly name,
or if that is blank, the username portion of their JID is sent instead.
-->
<fancy_friendly/>
</msntrans>
<!-- msn-transport module. -->
<load>
<msntrans>/usr/local/lib/jabber/msn-transport.so</msntrans>
</load>
</service>
<!--
The settings below have to match the settings you made
in your ejabberd.cfg configuration file.
-->
<service id="msn-linker">
<uplink/>
<connect>
<ip>127.0.0.1</ip>
<port>5235</port>
<secret>SECRET</secret>
</connect>
</service>
<pidfile>/var/run/jabber/msn-transport.pid</pidfile>
</jabber>

View File

@ -1,86 +0,0 @@
<!-- yahoo-transport-2.xml -->
<jabber>
<!--
You need to add the elogger entry here when using ejabberd.
In this case the transport will do the logging.
-->
<log id='elogger'>
<host/>
<logtype/>
<format>%d: [%t] (%h): %s</format>
<file>/var/log/jabber/yahoo-transport-2-error.log</file>
<stderr/>
</log>
<!--
ejabberd do not provide XDB services.
xdb_file.so is loaded in to handle all XDB requests.
-->
<xdb id="xdb">
<host/>
<load>
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
</load>
<xdb_file xmlns="jabber:config:xdb_file">
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
</xdb_file>
</xdb>
<!--
Make sure that all host names here are resolveable via DNS if you
want the transport to be available to the public.
-->
<service id="yahoo.SERVER.COM">
<!-- yahoo-transport-2 configuration. -->
<config xmlns="jabber:config:yahoo">
<vCard>
<NAME>Yahoo! Transport</NAME>
<FN>vCard not implemented in current version</FN>
<DESC>This is the Yahoo! transport.</DESC>
<MAIL>EMAIL@ADDRESS.COM</MAIL>
<URL>http://yahoo-transport-2.jabberstudio.org/</URL>
</vCard>
<instructions>Fill in your YAHOO! Messenger username and password to register on this transport.</instructions>
<server>scs.msg.yahoo.com</server>
<port>5050</port>
<!--
The character map. This provides character set translation from UTF-8
to the indicated character map. See the man page for 'iconv' for available
character maps on your platform. CP1252 is the standard Windows character
set.
-->
<charmap>CP1252</charmap>
<!--
When this element exists, the transport will send new mail notifications as
well as a count of unread messages when the user initially logs in.
-->
<newmail/>
</config>
<!-- yahoo-transport-2 module. -->
<load>
<yahoo_transport>/usr/local/lib/jabber/yahoo-transport-2.so</yahoo_transport>
</load>
</service>
<!--
The settings below have to match the settings you made
in your ejabberd.cfg configuration file.
-->
<service id="yahoo-linker">
<uplink/>
<connect>
<ip>127.0.0.1</ip>
<port>5236</port>
<secret>SECRET</secret>
</connect>
</service>
<pidfile>/var/run/jabber/yahoo-transport-2.pid</pidfile>
</jabber>

View File

@ -1,45 +0,0 @@
#!/bin/sh
#########################################################
#
# aim-transport -- script to start aim-transport.
#
#########################################################
DAEMON=/usr/local/sbin/jabberd-aim-transport
CONF=/etc/jabber/aim-transport.xml
NAME=jabberd-aim-transport
HOME=/etc/jabber/
USER=ejabberd
#########################################################
if [ "`/usr/bin/whoami`" != "$USER" ]; then
echo "You need to be" $USER "user to run this script."
exit 1
fi
case "$1" in
debug)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME in debugging mode."
$DAEMON -D -H $HOME -c $CONF &
;;
start)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME."
$DAEMON -H $HOME -c $CONF &
;;
stop)
echo "Stopping $NAME."
killall $NAME &
;;
restart|reload)
$0 stop
sleep 3
$0 start
;;
*)
echo "Usage: $0 {debug|start|stop|restart}"
exit 1
esac

View File

@ -1,43 +0,0 @@
#!/bin/sh
#########################################################
#
# ile -- script to start ILE.
#
#########################################################
DAEMON=/usr/local/sbin/ile.pl
NAME=ile.pl
CONF=/etc/jabber/ile.xml
USER=ejabberd
#########################################################
if [ "`/usr/bin/whoami`" != "$USER" ]; then
echo "You need to be" $USER "user to run this script."
exit 1
fi
case "$1" in
debug)
echo "Not implemented yet. Starting in normal mode"
$0 start
;;
start)
test -f $DAEMON || exit 0
echo "Starting $NAME."
$DAEMON $CONF &
;;
stop)
echo "Stopping $NAME."
killall $NAME &
;;
restart|reload)
$0 stop
sleep 3
$0 start
;;
*)
echo "Usage: $0 {debug|start|stop|status|restart}"
exit 1
esac

View File

@ -1,47 +0,0 @@
#!/bin/sh
#########################################################
#
# jabber-gg-transport -- script to start jabber-gg-transport.
#
#########################################################
DAEMON=/usr/local/sbin/jggtrans
CONF=/etc/jabber/jabber-gg-transport.xml
NAME=jggtrans
HOME=/etc/jabber/
USER=ejabberd
#########################################################
if [ "`/usr/bin/whoami`" != "$USER" ]; then
echo "You need to be" $USER "user to run this script."
exit 1
fi
case "$1" in
debug)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME in debugging mode."
$DAEMON -D -H $HOME -c $CONF &
;;
start)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME."
$DAEMON $CONF &
;;
stop)
echo "Stopping $NAME."
killall $NAME &
rm /var/run/jabber/jabber-gg-transport.pid
;;
restart|reload)
$0 stop
sleep 3
$0 start
;;
*)
echo "Usage: $0 {debug|start|stop|restart}"
exit 1
esac

View File

@ -1,45 +0,0 @@
#!/bin/sh
#########################################################
#
# jit -- script to start JIT.
#
#########################################################
DAEMON=/usr/local/sbin/wpjabber-jit
CONF=/etc/jabber/jit.xml
NAME=wpjabber-jit
HOME=/etc/jabber/
USER=ejabberd
#########################################################
if [ "`/usr/bin/whoami`" != "$USER" ]; then
echo "You need to be" $USER "user to run this script."
exit 1
fi
case "$1" in
debug)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME in debugging mode."
$DAEMON -D -H $HOME -c $CONF &
;;
start)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME."
$DAEMON -H $HOME -c $CONF &
;;
stop)
echo "Stopping $NAME."
killall $NAME &
;;
restart|reload)
$0 stop
sleep 3
$0 start
;;
*)
echo "Usage: $0 {debug|start|stop|restart}"
exit 1
esac

View File

@ -1,50 +0,0 @@
#!/bin/sh
#########################################################
#
# msn-transport -- script to start MSN Transport.
#
#########################################################
DAEMON=/usr/local/sbin/jabberd-msn-transport
CONF=/etc/jabber/msn-transport.xml
NAME=jabberd-msn-transport
HOME=/etc/jabber/
USER=ejabberd
#########################################################
if [ "`/usr/bin/whoami`" != "$USER" ]; then
echo "You need to be" $USER "user to run this script."
exit 1
fi
case "$1" in
strace)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME in strace mode."
strace -o /opt/ejabberd/var/log/jabber/strace.log $DAEMON -H $HOME -c $CONF &
;;
debug)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME in debugging mode."
$DAEMON -D -H $HOME -c $CONF &
;;
start)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME."
$DAEMON -H $HOME -c $CONF &
;;
stop)
echo "Stopping $NAME."
killall $NAME &
;;
restart|reload)
$0 stop
sleep 3
$0 start
;;
*)
echo "Usage: $0 {debug|start|stop|restart}"
exit 1
esac

View File

@ -1,45 +0,0 @@
#!/bin/sh
##############################################################
#
# yahoo-transport-2 -- script to start Yahoo-transport-2.
#
#############################################################
DAEMON=/usr/local/sbin/jabberd-yahoo-transport-2
CONF=/etc/jabber/yahoo-transport-2.xml
NAME=jabberd-yahoo-transport-2
HOME=/etc/jabber/
USER=ejabberd
#############################################################
if [ "`/usr/bin/whoami`" != "$USER" ]; then
echo "You need to be" $USER "user to run this script."
exit 1
fi
case "$1" in
debug)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME in debugging mode."
$DAEMON -D -H $HOME -c $CONF &
;;
start)
test -f $DAEMON -a -f $CONF || exit 0
echo "Starting $NAME."
$DAEMON -H $HOME -c $CONF &
;;
stop)
echo "Stopping $NAME."
killall $NAME &
;;
restart|reload)
$0 stop
sleep 3
$0 start
;;
*)
echo "Usage: $0 {debug|start|stop|restart}"
exit 1
esac

View File

@ -166,7 +166,7 @@ $(ERLSHLIBS): %.so: %.c
$(DYNAMIC_LIB_CFLAGS)
translations:
../contrib/extract_translations/prepare-translation.sh -updateall
../tools/extract_translations/prepare-translation.sh -updateall
install: all
#
@ -248,9 +248,11 @@ install: all
#
# Documentation
install -d $(DOCDIR)
install -m 644 ../doc/dev.html $(DOCDIR)
install -m 644 ../doc/guide.html $(DOCDIR)
install -m 644 ../doc/*.png $(DOCDIR)
[ -f ../doc/guide.html ] \
&& install -m 644 ../doc/dev.html $(DOCDIR) \
&& install -m 644 ../doc/guide.html $(DOCDIR) \
&& install -m 644 ../doc/*.png $(DOCDIR) \
|| echo "No ../doc/guide.html was built"
install -m 644 ../doc/*.txt $(DOCDIR)
[ -f ../doc/guide.pdf ] \
&& install -m 644 ../doc/guide.pdf $(DOCDIR) \

563
src/cache_tab.erl Normal file
View File

@ -0,0 +1,563 @@
%%%-------------------------------------------------------------------
%%% File : cache_tab.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Description : Caching key-value table
%%%
%%% Created : 29 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(cache_tab).
-define(GEN_SERVER, gen_server).
-behaviour(?GEN_SERVER).
%% API
-export([start_link/4, new/2, delete/1, delete/3, lookup/3,
insert/4, info/2, tab2list/1, setopts/2,
dirty_lookup/3, dirty_insert/4, dirty_delete/3,
all/0, test/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-record(state, {tab = treap:empty(),
name,
size = 0,
owner,
max_size,
life_time,
warn,
hits = 0,
miss = 0,
procs_num,
cache_missed,
shrink_size}).
-define(PROCNAME, ?MODULE).
-define(CALL_TIMEOUT, 60000).
%% Defaults
-define(MAX_SIZE, 1000).
-define(WARN, true).
-define(CACHE_MISSED, true).
-define(LIFETIME, 600). %% 10 minutes
%%====================================================================
%% API
%%====================================================================
start_link(Proc, Tab, Opts, Owner) ->
?GEN_SERVER:start_link({local, Proc}, ?MODULE,
[Tab, Opts, get_proc_num(), Owner], []).
new(Tab, Opts) ->
Res = lists:flatmap(
fun(Proc) ->
Spec = {{Tab, Proc},
{?MODULE, start_link,
[Proc, Tab, Opts, self()]},
permanent,
brutal_kill,
worker,
[?MODULE]},
case supervisor:start_child(cache_tab_sup, Spec) of
{ok, _Pid} ->
[ok];
R ->
[R]
end
end, get_all_procs(Tab)),
case lists:filter(fun(ok) -> false; (_) -> true end, Res) of
[] ->
ok;
Err ->
{error, Err}
end.
delete(Tab) ->
lists:foreach(
fun(Proc) ->
supervisor:terminate_child(cache_tab_sup, {Tab, Proc}),
supervisor:delete_child(cache_tab_sup, {Tab, Proc})
end, get_all_procs(Tab)).
delete(Tab, Key, F) ->
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {delete, Key, F}, ?CALL_TIMEOUT).
dirty_delete(Tab, Key, F) ->
F(),
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {dirty_delete, Key}, ?CALL_TIMEOUT).
lookup(Tab, Key, F) ->
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {lookup, Key, F}, ?CALL_TIMEOUT).
dirty_lookup(Tab, Key, F) ->
Proc = get_proc_by_hash(Tab, Key),
case ?GEN_SERVER:call(Proc, {dirty_lookup, Key}, ?CALL_TIMEOUT) of
{ok, '$cached_mismatch'} ->
error;
{ok, Val} ->
{ok, Val};
_ ->
case F() of
{ok, Val} ->
?GEN_SERVER:call(
Proc, {dirty_insert, Key, Val}, ?CALL_TIMEOUT),
{ok, Val};
_ ->
error
end
end.
insert(Tab, Key, Val, F) ->
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {insert, Key, Val, F}, ?CALL_TIMEOUT).
dirty_insert(Tab, Key, Val, F) ->
F(),
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {dirty_insert, Key, Val}, ?CALL_TIMEOUT).
info(Tab, Info) ->
case lists:map(
fun(Proc) ->
?GEN_SERVER:call(Proc, {info, Info}, ?CALL_TIMEOUT)
end, get_all_procs(Tab)) of
Res when Info == size ->
{ok, lists:sum(Res)};
Res when Info == all ->
{ok, Res};
Res when Info == ratio ->
{H, M} = lists:foldl(
fun({Hits, Miss}, {HitsAcc, MissAcc}) ->
{HitsAcc + Hits, MissAcc + Miss}
end, {0, 0}, Res),
{ok, [{hits, H}, {miss, M}]};
_ ->
{error, badarg}
end.
setopts(Tab, Opts) ->
lists:foreach(
fun(Proc) ->
?GEN_SERVER:call(Proc, {setopts, Opts}, ?CALL_TIMEOUT)
end, get_all_procs(Tab)).
tab2list(Tab) ->
lists:flatmap(
fun(Proc) ->
?GEN_SERVER:call(Proc, tab2list, ?CALL_TIMEOUT)
end, get_all_procs(Tab)).
all() ->
lists:usort(
[Tab || {{Tab, _}, _, _, _} <- supervisor:which_children(cache_tab_sup)]).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Tab, Opts, N, Pid]) ->
State = #state{procs_num = N,
owner = Pid,
name = Tab},
{ok, do_setopts(State, Opts)}.
handle_call({lookup, Key, F}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
Hits = State#state.hits,
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
case Val of
'$cached_mismatch' ->
{reply, error, NewState};
_ ->
{reply, {ok, Val}, NewState}
end;
_ ->
case catch F() of
{ok, Val} ->
Miss = State#state.miss,
NewState = treap_insert(Key, Val, State),
{reply, {ok, Val}, NewState#state{miss = Miss + 1}};
{'EXIT', Reason} ->
print_error(lookup, [Key], Reason, State),
{reply, error, State};
_ ->
Miss = State#state.miss,
NewState = State#state{miss = Miss + 1},
if State#state.cache_missed ->
{reply, error,
treap_insert(Key, '$cached_mismatch', NewState)};
true ->
{reply, error, NewState}
end
end
end;
handle_call({dirty_lookup, Key}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
Hits = State#state.hits,
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
{reply, {ok, Val}, NewState};
_ ->
Miss = State#state.miss,
NewState = State#state{miss = Miss + 1},
if State#state.cache_missed ->
{reply, error,
treap_insert(Key, '$cached_mismatch', NewState)};
true ->
{reply, error, NewState}
end
end;
handle_call({insert, Key, Val, F}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
{reply, ok, State};
Res ->
case catch F() of
{'EXIT', Reason} ->
print_error(insert, [Key, Val], Reason, State),
{reply, ok, State};
_ ->
NewState = case Res of
{ok, _, _} ->
treap_update(Key, Val, State);
_ ->
treap_insert(Key, Val, State)
end,
{reply, ok, NewState}
end
end;
handle_call({dirty_insert, Key, Val}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
{reply, ok, State};
{ok, _, _} ->
{reply, ok, treap_update(Key, Val, State)};
_ ->
{reply, ok, treap_insert(Key, Val, State)}
end;
handle_call({delete, Key, F}, _From, State) ->
NewState = treap_delete(Key, State),
case catch F() of
{'EXIT', Reason} ->
print_error(delete, [Key], Reason, State);
_ ->
ok
end,
{reply, ok, NewState};
handle_call({dirty_delete, Key}, _From, State) ->
NewState = treap_delete(Key, State),
{reply, ok, NewState};
handle_call({info, Info}, _From, State) ->
Res = case Info of
size ->
State#state.size;
ratio ->
{State#state.hits, State#state.miss};
all ->
[{max_size, State#state.max_size},
{life_time, State#state.life_time},
{shrink_size, State#state.shrink_size},
{size, State#state.size},
{owner, State#state.owner},
{hits, State#state.hits},
{miss, State#state.miss},
{cache_missed, State#state.cache_missed},
{warn, State#state.warn}];
_ ->
badarg
end,
{reply, Res, State};
handle_call(tab2list, _From, #state{tab = T} = State) ->
Res = treap:fold(
fun({Key, _, Val}, Acc) ->
[{Key, Val}|Acc]
end, [], T),
{reply, Res, State};
handle_call({setopts, Opts}, _From, State) ->
{reply, ok, do_setopts(State, Opts)};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_setopts(#state{procs_num = N} = State, Opts) ->
MaxSize = case {proplists:get_value(max_size, Opts),
State#state.max_size} of
{MS, _} when is_integer(MS), MS > 0 ->
round(MS/N);
{unlimited, _} ->
unlimited;
{_, undefined} ->
round(?MAX_SIZE/N);
{_, MS} ->
MS
end,
LifeTime = case {proplists:get_value(life_time, Opts),
State#state.life_time} of
{LT, _} when is_integer(LT), LT > 0 ->
LT*1000*1000;
{unlimited, _} ->
unlimited;
{_, undefined} ->
?LIFETIME*1000*1000;
{_, LT} ->
LT
end,
ShrinkSize = case {proplists:get_value(shrink_size, Opts),
State#state.shrink_size} of
{SS, _} when is_integer(SS), SS > 0 ->
round(SS/N);
_ when is_integer(MaxSize) ->
round(MaxSize/2);
_ ->
unlimited
end,
Warn = case {proplists:get_value(warn, Opts),
State#state.warn} of
{true, _} ->
true;
{false, _} ->
false;
{_, undefined} ->
?WARN;
{_, W} ->
W
end,
CacheMissed = case proplists:get_value(
cache_missed, Opts, State#state.cache_missed) of
false ->
false;
true ->
true;
_ ->
?CACHE_MISSED
end,
State#state{max_size = MaxSize,
warn = Warn,
life_time = LifeTime,
cache_missed = CacheMissed,
shrink_size = ShrinkSize}.
get_proc_num() ->
erlang:system_info(logical_processors).
get_proc_by_hash(Tab, Term) ->
N = erlang:phash2(Term, get_proc_num()) + 1,
get_proc(Tab, N).
get_proc(Tab, N) ->
list_to_atom(atom_to_list(?PROCNAME) ++ "_" ++
atom_to_list(Tab) ++ "_" ++ integer_to_list(N)).
get_all_procs(Tab) ->
[get_proc(Tab, N) || N <- lists:seq(1, get_proc_num())].
now_priority() ->
{MSec, Sec, USec} = now(),
-((MSec*1000000 + Sec)*1000000 + USec).
treap_update(Key, Val, #state{tab = T} = State) ->
Priority = now_priority(),
NewT = treap:insert(Key, Priority, Val, T),
State#state{tab = NewT}.
treap_insert(Key, Val, State) ->
State1 = clean_treap(State),
#state{size = Size} = State2 = shrink_treap(State1),
treap_update(Key, Val, State2#state{size = Size+1}).
treap_delete(Key, #state{tab = T, size = Size} = State) ->
case treap:lookup(Key, T) of
{ok, _, _} ->
NewT = treap:delete(Key, T),
clean_treap(State#state{tab = NewT, size = Size-1});
_ ->
State
end.
clean_treap(#state{tab = T, size = Size, life_time = LifeTime} = State) ->
if is_integer(LifeTime) ->
Priority = now_priority(),
{Cleaned, NewT} = clean_treap(T, Priority + LifeTime, 0),
State#state{size = Size - Cleaned, tab = NewT};
true ->
State
end.
clean_treap(Treap, CleanPriority, N) ->
case treap:is_empty(Treap) of
true ->
{N, Treap};
false ->
{_Key, Priority, _Value} = treap:get_root(Treap),
if Priority > CleanPriority ->
clean_treap(treap:delete_root(Treap), CleanPriority, N+1);
true ->
{N, Treap}
end
end.
shrink_treap(#state{tab = T,
max_size = MaxSize,
shrink_size = ShrinkSize,
warn = Warn,
size = Size} = State) when Size >= MaxSize ->
if Warn ->
?WARNING_MSG("shrinking table:~n"
"** Table: ~p~n"
"** Processes Number: ~p~n"
"** Max Size: ~p items~n"
"** Shrink Size: ~p items~n"
"** Life Time: ~p microseconds~n"
"** Hits/Miss: ~p/~p~n"
"** Owner: ~p~n"
"** Cache Missed: ~p~n"
"** Instruction: you have to tune cacheing options"
" if this message repeats too frequently",
[State#state.name, State#state.procs_num,
MaxSize, ShrinkSize, State#state.life_time,
State#state.hits, State#state.miss,
State#state.owner, State#state.cache_missed]);
true ->
ok
end,
{Shrinked, NewT} = shrink_treap(T, ShrinkSize, 0),
State#state{tab = NewT, size = Size - Shrinked};
shrink_treap(State) ->
State.
shrink_treap(T, ShrinkSize, ShrinkSize) ->
{ShrinkSize, T};
shrink_treap(T, ShrinkSize, N) ->
case treap:is_empty(T) of
true ->
{N, T};
false ->
shrink_treap(treap:delete_root(T), ShrinkSize, N+1)
end.
print_error(Operation, Args, Reason, State) ->
?ERROR_MSG("callback failed:~n"
"** Tab: ~p~n"
"** Owner: ~p~n"
"** Operation: ~p~n"
"** Args: ~p~n"
"** Reason: ~p",
[State#state.name, State#state.owner,
Operation, Args, Reason]).
%%--------------------------------------------------------------------
%%% Tests
%%--------------------------------------------------------------------
-define(lookup, dirty_lookup).
-define(delete, dirty_delete).
-define(insert, dirty_insert).
%% -define(lookup, lookup).
%% -define(delete, delete).
%% -define(insert, insert).
test() ->
LifeTime = 2,
ok = new(test_tbl, [{life_time, LifeTime}, {max_size, unlimited}]),
check([]),
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
check([{"key", "value"}]),
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
check([{"key", "value"}]),
io:format("** waiting for ~p seconds to check if cleaner works fine...~n",
[LifeTime+1]),
timer:sleep(timer:seconds(LifeTime+1)),
ok = ?insert(test_tbl, "key1", "value1", fun() -> ok end),
check([{"key1", "value1"}]),
ok = ?delete(test_tbl, "key1", fun() -> ok end),
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> {ok, "value"} end),
check([{"key", "value"}]),
ok = ?delete(test_tbl, "key", fun() -> ok end),
check([]),
%% io:format("** testing buggy callbacks...~n"),
%% delete(test_tbl, "key", fun() -> erlang:error(badarg) end),
%% insert(test_tbl, "key", "val", fun() -> erlang:error(badarg) end),
%% lookup(test_tbl, "key", fun() -> erlang:error(badarg) end),
check([]),
delete(test_tbl),
test1().
test1() ->
MaxSize = 10,
ok = new(test_tbl, [{max_size, MaxSize}, {shrink_size, 1}, {warn, false}]),
lists:foreach(
fun(N) ->
ok = ?insert(test_tbl, N, N, fun() -> ok end)
end, lists:seq(1, MaxSize*get_proc_num())),
{ok, MaxSize} = info(test_tbl, size),
delete(test_tbl),
io:format("** testing speed, this may take a while...~n"),
test2(1000),
test2(10000),
test2(100000),
test2(1000000).
test2(Iter) ->
ok = new(test_tbl, [{max_size, unlimited}, {life_time, unlimited}]),
L = lists:seq(1, Iter),
T1 = now(),
lists:foreach(
fun(N) ->
ok = ?insert(test_tbl, N, N, fun() -> ok end)
end, L),
io:format("** average insert (size = ~p): ~p usec~n",
[Iter, round(timer:now_diff(now(), T1)/Iter)]),
T2 = now(),
lists:foreach(
fun(N) ->
{ok, N} = ?lookup(test_tbl, N, fun() -> ok end)
end, L),
io:format("** average lookup (size = ~p): ~p usec~n",
[Iter, round(timer:now_diff(now(), T2)/Iter)]),
{ok, Iter} = info(test_tbl, size),
delete(test_tbl).
check(List) ->
Size = length(List),
{ok, Size} = info(test_tbl, size),
List = tab2list(test_tbl).

53
src/cache_tab_sup.erl Normal file
View File

@ -0,0 +1,53 @@
%%%-------------------------------------------------------------------
%%% File : cache_tab_sup.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Description : Cache tables supervisor
%%%
%%% Created : 30 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(cache_tab_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API functions
%%====================================================================
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
init([]) ->
{ok, {{one_for_one,10,1}, []}}.
%%====================================================================
%% Internal functions
%%====================================================================

5977
src/configure vendored

File diff suppressed because it is too large Load Diff

View File

@ -261,8 +261,7 @@ get_sasl_error_logger_type () ->
stop_kindly(DelaySeconds, AnnouncementText) ->
Subject = io_lib:format("Server stop in ~p seconds!", [DelaySeconds]),
WaitingDesc = io_lib:format("Waiting ~p seconds", [DelaySeconds]),
Steps = [
{"Stopping ejabberd port listeners",
Steps = [{"Stopping ejabberd port listeners",
ejabberd_listener, stop_listeners, []},
{"Sending announcement to connected users",
mod_announce, send_announcement_to_all,
@ -388,11 +387,11 @@ import_dir(Path) ->
%%%
delete_expired_messages() ->
{atomic, ok} = mod_offline:remove_expired_messages(),
mod_offline:remove_expired_messages(),
ok.
delete_old_messages(Days) ->
{atomic, _} = mod_offline:remove_old_messages(Days),
mod_offline:remove_old_messages(Days),
ok.

View File

@ -140,8 +140,8 @@ anonymous_protocol(Host) when is_list(Host) ->
%% defaults to false
allow_multiple_connections(Host) when is_list(Host) ->
ejabberd_config:get_local_option(
{allow_multiple_connections, Host}) =:= true.
ejabberd_config:get_local_option({allow_multiple_connections, Host})
=:= true.
%% @spec (User, Server) -> bool()
%% User = string()

View File

@ -440,8 +440,8 @@ is_valid_dn(DN, Server, Attrs, State) ->
end ++ [{"%d", State#state.host}, {"%D", DN}],
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
{ok, EldapFilter} ->
case eldap_pool:search(State#state.eldap_id, [
{base, State#state.base},
case eldap_pool:search(State#state.eldap_id,
[{base, State#state.base},
{filter, EldapFilter},
{attributes, ["dn"]}]) of
#eldap_search_result{entries = [_|_]} ->

View File

@ -71,6 +71,9 @@
-include("ejabberd.hrl").
-include("mod_privacy.hrl").
%% Copied from ejabberd_socket.erl
-record(socket_state, {sockmod, socket, receiver}).
-define(SETS, gb_sets).
-define(DICT, dict).
@ -528,6 +531,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
"(~w) Accepted legacy authentication for ~s by ~s",
[StateData#state.socket,
exmpp_jid:to_binary(JID), AuthModule]),
erlang:link((StateData#state.socket)#socket_state.receiver),
SID = {now(), self()},
Conn = get_conn_type(StateData),
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
@ -664,8 +668,8 @@ wait_for_feature_request({xmlstreamelement, #xmlel{ns = NS, name = Name} = El},
TLSEnabled == false,
SockMod == gen_tcp ->
ServerString = binary_to_list(StateData#state.server),
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile, ServerString}) of
TLSOpts = case ejabberd_config:get_local_option
({domain_certfile, ServerString}) of
undefined ->
StateData#state.tls_options;
CertFile ->
@ -1595,9 +1599,6 @@ get_auth_tags([_ | L], U, P, D, R) ->
get_auth_tags([], U, P, D, R) ->
{U, P, D, R}.
%% Copied from ejabberd_socket.erl
-record(socket_state, {sockmod, socket, receiver}).
get_conn_type(StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp -> c2s;
@ -1606,8 +1607,8 @@ get_conn_type(StateData) ->
if is_pid(StateData#state.socket) ->
unknown;
true ->
case ejabberd_zlib:get_sockmod(
(StateData#state.socket)#socket_state.socket) of
case ejabberd_zlib:get_sockmod
((StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
tls -> c2s_compressed_tls
end

View File

@ -24,6 +24,22 @@
%%%
%%%-------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / captcha
%%% id = string()
%%% pid = pid()
%%% key = string()
%%% tref = any()
%%% args = any()
%%%
%%% 3.0.0-alpha / ets / captcha
%%% id = string()
%%% pid = pid()
%%% key = string()
%%% tref = any()
%%% args = any()
-module(ejabberd_captcha).
-behaviour(gen_server).
@ -47,23 +63,20 @@
-define(VFIELD(Type, Var, Value),
#xmlel{name = 'field',
attrs = [
#xmlattr{
name = 'type',
#xmlattr{name = 'type',
value = Type
},
#xmlattr{
name = 'var',
#xmlattr{name = 'var',
value = Var
}
],
children = [
#xmlel{
name = 'value',
#xmlel{name = 'value',
children = [Value]
}
]}).
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
-define(CAPTCHA_TEXT(Lang), list_to_binary(translate:translate(Lang, "Enter the text you see"))).
-define(CAPTCHA_LIFETIME, 120000). % two minutes
-define(RPC_TIMEOUT, 5000).
@ -81,13 +94,13 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
create_captcha(SID, From, To, Lang, Args)
when is_binary(Lang), is_list(SID) ->
when is_binary(Lang), is_binary(SID) ->
case create_image() of
{ok, Type, Key, Image} ->
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
B64Image = jlib:encode_base64(binary_to_list(Image)),
B64Image = list_to_binary(jlib:encode_base64(binary_to_list(Image))),
JID = exmpp_jid:to_list(From),
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
CID = list_to_binary(["sha1+", sha:sha(Image), "@bob.xmpp.org"]),
% Data = {xmlelement, "data",
% [{"xmlns", ?NS_BOB}, {"cid", CID},
% {"max-age", "0"}, {"type", Type}],
@ -97,16 +110,13 @@ create_captcha(SID, From, To, Lang, Args)
name = 'data',
ns = ?NS_BOB,
attrs = [
#xmlattr{
name = 'cid',
#xmlattr{name = 'cid',
value = CID
},
#xmlattr{
name = 'max-age',
value = "0"
#xmlattr{name = 'max-age',
value = <<"0">>
},
#xmlattr{
name = 'type',
#xmlattr{name = 'type',
value = Type
}
],
@ -128,47 +138,39 @@ create_captcha(SID, From, To, Lang, Args)
name = 'captcha',
ns = ?NS_CAPTCHA,
children = [
#xmlel{
name = 'x',
#xmlel{name = 'x',
ns = ?NS_DATA_FORMS_s,
attrs = [
#xmlattr{
name = 'type',
value = "form"
#xmlattr{name = 'type',
value = <<"form">>
}
],
children = [
?VFIELD("hidden", "FORM_TYPE", #xmlcdata{cdata = ?NS_CAPTCHA}),
?VFIELD("hidden", "from", #xmlcdata{cdata = exmpp_jid:to_list(To)}),
?VFIELD("hidden", "challenge", #xmlcdata{cdata = Id}),
?VFIELD("hidden", "sid", #xmlcdata{cdata = SID}),
#xmlel{
name = 'field',
?VFIELD(<<"hidden">>, <<"FORM_TYPE">>, #xmlcdata{cdata = ?NS_CAPTCHA_b}),
?VFIELD(<<"hidden">>, <<"from">>, #xmlcdata{cdata = exmpp_jid:to_binary(To)}),
?VFIELD(<<"hidden">>, <<"challenge">>, #xmlcdata{cdata = list_to_binary(Id)}),
?VFIELD(<<"hidden">>, <<"sid">>, #xmlcdata{cdata = SID}),
#xmlel{name = 'field',
attrs = [
#xmlattr{
name = 'var',
value = "ocr"
#xmlattr{name = 'var',
value = <<"ocr">>
},
#xmlattr{
name = 'label',
#xmlattr{name = 'label',
value = ?CAPTCHA_TEXT(Lang)
}
],
children = [
#xmlel{
name = 'media',
#xmlel{name = 'media',
ns = ?NS_DATA_FORMS_MEDIA_s,
children = [
#xmlel{
name = 'uri',
#xmlel{name = 'uri',
attrs = [
#xmlattr{
name = 'type',
#xmlattr{name = 'type',
value = Type
}
],
children = [
#xmlcdata{cdata = "cid:" ++ CID}
#xmlcdata{cdata = list_to_binary(["cid:", CID])}
]
}
]
@ -182,24 +184,19 @@ create_captcha(SID, From, To, Lang, Args)
BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
Body =
#xmlel{
name = 'body',
#xmlel{name = 'body',
children = [
#xmlcdata{
cdata = BodyString
#xmlcdata{cdata = list_to_binary(BodyString)
}
]
},
OOB =
#xmlel{
name = 'x',
#xmlel{name = 'x',
ns = ?NS_OOBD_X_s,
children = [
#xmlel{
name = 'url',
#xmlel{name = 'url',
children = [
#xmlcdata{
cdata = get_url(Id)}
#xmlcdata{cdata = list_to_binary(get_url(Id))}
]
}
]
@ -227,12 +224,10 @@ build_captcha_html(Id, Lang) ->
{ok, _} ->
%ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
ImgEl =
#xmlel{
name = 'img',
#xmlel{name = 'img',
attrs = [
#xmlattr{
name = 'src',
value = get_url(Id ++ "/image")
#xmlattr{name = 'src',
value = list_to_binary(get_url(Id ++ "/image"))
}
]
},
@ -242,20 +237,16 @@ build_captcha_html(Id, Lang) ->
% {"name", "id"},
% {"value", Id}], []},
IdEl =
#xmlel{
name = 'input',
#xmlel{name = 'input',
attrs = [
#xmlattr{
name = 'type',
value = "hidden"
#xmlattr{name = 'type',
value = <<"hidden">>
},
#xmlattr{
name = 'name',
value = "id"
#xmlattr{name = 'name',
value = <<"id">>
},
#xmlattr{
name = 'value',
value = Id
#xmlattr{name = 'value',
value = list_to_binary(Id)
}
]
},
@ -263,20 +254,16 @@ build_captcha_html(Id, Lang) ->
% {"name", "key"},
% {"size", "10"}], []},
KeyEl =
#xmlel{
name = 'input',
#xmlel{name = 'input',
attrs = [
#xmlattr{
name = 'type',
value = "text"
#xmlattr{name = 'type',
value = <<"text">>
},
#xmlattr{
name = 'name',
value = "key"
#xmlattr{name = 'name',
value = <<"key">>
},
#xmlattr{
name = 'size',
value = "10"
#xmlattr{name = 'size',
value = <<"10">>
}
]
},
@ -295,50 +282,39 @@ build_captcha_html(Id, Lang) ->
% {"value", "OK"}], []}
% ]},
FormEl =
#xmlel{
name = 'form',
#xmlel{name = 'form',
attrs = [
#xmlattr{
name = 'action',
value = get_url(Id)
#xmlattr{name = 'action',
value = list_to_binary(get_url(Id))
},
#xmlattr{
name = 'name',
value = "captcha"
#xmlattr{name = 'name',
value = <<"captcha">>
},
#xmlattr{
name = 'method',
value = "POST"
#xmlattr{name = 'method',
value = <<"POST">>
}
],
children = [
ImgEl,
#xmlel{
name = 'br'
#xmlel{name = 'br'
},
TextEl,
#xmlel{
name = 'br'
#xmlel{name = 'br'
},
IdEl,
KeyEl,
#xmlel{
name = 'br'
#xmlel{name = 'br'
},
#xmlel{
name = 'input',
#xmlel{name = 'input',
attrs = [
#xmlattr{
name = 'type',
value = "submit"
#xmlattr{name = 'type',
value = <<"submit">>
},
#xmlattr{
name = 'name',
value = "enter"
#xmlattr{name = 'name',
value = <<"enter">>
},
#xmlattr{
name = 'value',
value = "OK"
#xmlattr{name = 'value',
value = <<"OK">>
}
]
}
@ -399,16 +375,14 @@ process_reply(El) ->
process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
case build_captcha_html(Id, Lang) of
{FormEl, _} when is_tuple(FormEl) ->
{FormEl, CaptchaTuple} when is_tuple(CaptchaTuple) ->
Form =
%{xmlelement, "div", [{"align", "center"}],
%[FormEl]},
#xmlel{
name = 'div',
#xmlel{name = 'div',
attrs = [
#xmlattr{
name = 'align',
value = "center"
#xmlattr{name = 'align',
value = <<"center">>
}
],
children = [FormEl]
@ -444,11 +418,9 @@ process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
% [{xmlcdata,
% translate:translate(Lang, "The captcha is valid.")
% }]},
#xmlel{
name = 'p',
#xmlel{name = 'p',
children = [
#xmlcdata{
cdata = translate:translate(Lang, "The captcha is valid.")}
#xmlcdata{cdata = translate:translate(Lang, "The captcha is valid.")}
]
},
ejabberd_web:make_xhtml([Form]);
@ -502,7 +474,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%% Function: create_image() -> {ok, Type, Key, Image} | {error, Reason}
%% Type = "image/png" | "image/jpeg" | "image/gif"
%% Key = string()
%% Key = binary()
%% Image = binary()
%% Reason = atom()
%%--------------------------------------------------------------------
@ -516,11 +488,11 @@ create_image(Key) ->
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
case cmd(Cmd) of
{ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
{ok, "image/png", Key, Img};
{ok, <<"image/png">>, Key, Img};
{ok, <<16#ff, 16#d8, _/binary>> = Img} ->
{ok, "image/jpeg", Key, Img};
{ok, <<"image/jpeg">>, Key, Img};
{ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
{ok, "image/gif", Key, Img};
{ok, <<"image/gif">>, Key, Img};
{error, enodata = Reason} ->
?ERROR_MSG("Failed to process output from \"~s\". "
"Maybe ImageMagick's Convert program is not installed.",
@ -547,6 +519,7 @@ get_prog_name() ->
throw({error, option_not_configured_captcha_cmd})
end.
%% @doc (Str::string()) -> string()
get_url(Str) ->
case ejabberd_config:get_local_option(captcha_host) of
Host when is_list(Host) ->

View File

@ -88,8 +88,8 @@ get_db_used() ->
DBs = lists:foldr(
fun([Domain, DB], Acc) ->
case check_odbc_option(
ejabberd_config:get_local_option(
{auth_method, Domain})) of
ejabberd_config:get_local_option
({auth_method, Domain})) of
true -> [get_db_type(DB)|Acc];
_ -> Acc
end

View File

@ -438,8 +438,7 @@ print_usage() ->
print_usage(dual, MaxC, ShCode).
print_usage(HelpMode, MaxC, ShCode) ->
AllCommands =
[
{"status", [], "Get ejabberd status"},
[{"status", [], "Get ejabberd status"},
{"stop", [], "Stop ejabberd"},
{"restart", [], "Restart ejabberd"},
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},

View File

@ -64,9 +64,8 @@
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(PREFIXED_NS,
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
%%====================================================================
%% API

View File

@ -440,14 +440,12 @@ do_reset_stream(#state{xml_stream_state = XMLStreamState}) ->
new_xmlstream(C2SPid, MaxStanzaSize) ->
Parser = exmpp_xml:start_parser([
{names_as_atom, true},
Parser = exmpp_xml:start_parser([{names_as_atom, true},
{check_nss, xmpp},
{check_elems, xmpp},
{check_attrs, xmpp},
{max_size, MaxStanzaSize}
]),
exmpp_xmlstream:start(
{gen_fsm, C2SPid}, Parser,
exmpp_xmlstream:start({gen_fsm, C2SPid}, Parser,
[{xmlstreamstart, new}]
).

View File

@ -427,8 +427,8 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
drop
end;
Rs ->
Value = case ejabberd_config:get_local_option(
{domain_balancing, LDstDomain}) of
Value = case ejabberd_config:get_local_option
({domain_balancing, LDstDomain}) of
undefined -> now();
random -> now();
source -> jlib:short_prepd_jid(From);
@ -477,8 +477,8 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
end.
get_component_number(LDomain) ->
case ejabberd_config:get_local_option(
{domain_balancing_component_number, LDomain}) of
case ejabberd_config:get_local_option
({domain_balancing_component_number, LDomain}) of
N when is_integer(N),
N > 1 ->
N;

View File

@ -61,9 +61,8 @@
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(PREFIXED_NS,
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
-record(s2s, {fromto, pid, key}).
-record(state, {}).

View File

@ -89,9 +89,8 @@
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_SERVER).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(PREFIXED_NS,
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
%%%----------------------------------------------------------------------
%%% API
@ -524,8 +523,7 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) ->
[D || {{D, _}, established} <-
dict:to_list(Connections)]
end,
Infos = [
{direction, in},
Infos = [{direction, in},
{statename, StateName},
{addr, Addr},
{port, Port},

View File

@ -106,9 +106,8 @@
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_SERVER).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(PREFIXED_NS,
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
@ -606,9 +605,8 @@ wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
?DEBUG("starttls: ~p", [{StateData#state.myname,
StateData#state.server}]),
Socket = StateData#state.socket,
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile,
StateData#state.server}) of
TLSOpts = case ejabberd_config:get_local_option
({domain_certfile, StateData#state.server}) of
undefined ->
StateData#state.tls_options;
CertFile ->
@ -763,8 +761,7 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) ->
_:_ ->
{unknown,unknown}
end,
Infos = [
{direction, out},
Infos = [{direction, out},
{statename, StateName},
{addr, Addr},
{port, Port},
@ -862,8 +859,8 @@ terminate(Reason, StateName, StateData) ->
false ->
ok;
Key ->
ejabberd_s2s:remove_connection(
{StateData#state.myname, StateData#state.server}, self(), Key)
ejabberd_s2s:remove_connectio
({StateData#state.myname, StateData#state.server}, self(), Key)
end,
%% bounce queue manage by process and Erlang message queue
bounce_queue(StateData#state.queue, 'remote-server-not-found'),
@ -953,8 +950,8 @@ send_db_request(StateData) ->
Server = StateData#state.server,
New = case StateData#state.new of
false ->
case ejabberd_s2s:try_register(
{StateData#state.myname, Server}) of
case ejabberd_s2s:try_register
({StateData#state.myname, Server}) of
{key, Key} ->
Key;
false ->

View File

@ -349,6 +349,9 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
Err = exmpp_stanza:reply_with_error(Packet, 'not-allowed'),
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.

View File

@ -80,9 +80,8 @@
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(PREFIXED_NS,
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
-define(IS_BINARY_OR_UNDEF(X),
@ -211,10 +210,12 @@ get_user_info(User, Server, Resource)
end,
if is_list(Ss), Ss /= [] ->
Session = lists:max(Ss),
N = node(element(2, Session#session.sid)),
Conn = proplists:get_value(conn, Session#session.info),
IP = proplists:get_value(ip, Session#session.info),
[{node, N}, {conn, Conn}, {ip, IP}];
Priority = Session#session.priority, %% integer()
{CreationNow, Pid} = Session#session.sid,
CreationString = jlib:now_to_utc_string(CreationNow),
[{node, Node}, {conn, Conn}, {ip, IP}, {priority, Priority}, {pid, Pid}, {creation, CreationString}];
true ->
offline
end.

View File

@ -191,6 +191,13 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_cluster]},
CacheTabSupervisor =
{cache_tab_sup,
{cache_tab_sup, start_link, []},
permanent,
infinity,
supervisor,
[cache_tab_sup]},
{ok, {{one_for_one, 10, 1},
[Hooks,
GlobalRouter,
@ -212,6 +219,7 @@ init([]) ->
IQSupervisor,
STUNSupervisor,
FrontendSocketSupervisor,
CacheTabSupervisor,
Listener]}}.

View File

@ -423,8 +423,8 @@ get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
%%----------------------------------------------------------------------
init([]) ->
case get_config() of
{ok, Hosts, Rootdn, Passwd, Opts} ->
init({Hosts, Rootdn, Passwd, Opts});
{ok, Hosts, Port, Rootdn, Passwd, Opts} ->
init({Hosts, Port, Rootdn, Passwd, Opts});
{error, Reason} ->
{stop, Reason}
end;
@ -440,8 +440,6 @@ init({Hosts, Port, Rootdn, Passwd, Opts}) ->
case Encrypt of
tls ->
?LDAPS_PORT;
starttls ->
?LDAP_PORT;
_ ->
?LDAP_PORT
end;
@ -886,14 +884,9 @@ cancel_timer(Timer) ->
%%% Sanity check of received packet
check_tag(Data) ->
case asn1rt_ber_bin:decode_tag(Data) of
{_Tag, Data1, _Rb} ->
case asn1rt_ber_bin:decode_length(Data1) of
{{_Len,_Data2}, _Rb2} -> ok;
_ -> throw({error,decoded_tag_length})
end;
_ -> throw({error,decoded_tag})
end.
{_Tag, Data1, _Rb} = asn1rt_ber_bin:decode_tag(Data),
{{_Len,_Data2}, _Rb2} = asn1rt_ber_bin:decode_length(Data1),
ok.
close_and_retry(S, Timeout) ->
catch (S#eldap.sockmod):close(S#eldap.fd),

View File

@ -246,6 +246,8 @@ get_hosts(Opts, Prefix) ->
Hosts
end.
get_module_proc(Host, Base) when is_binary(Host) ->
get_module_proc(binary_to_list(Host), Base);
get_module_proc(global, Base) ->
list_to_atom(atom_to_list(Base) ++ "__global");
get_module_proc(Host, {frontend, Base}) ->

View File

@ -58,14 +58,21 @@ behaviour_info(_) ->
-include("ejabberd.hrl"). % This is used for ERROR_MSG
%% Returns all hosts where the table Tab is defined
-spec all_table_hosts(atom()) ->
-spec all_table_hosts(storage_table()) ->
[storage_host()].
all_table_hosts(Tab) ->
mnesia:dirty_select(table, [{{table, {'$1', '$2'}, '_', '_'},
TT = setelement(2, {table, {<<"hidding_from_dialyzer">>, '$2'}, '_', '_'}, {'$1', '$2'}),
Res = (catch mnesia:dirty_select(table, [{TT,
[{'=:=', '$2', {const, Tab}}],
['$1']}]).
['$1']}])),
case Res of
Res when is_list(Res) ->
[HostB || HostB <- Res, is_binary(HostB)];
_ ->
[]
end.
-spec table_info(storage_host, storage_table, atom()) ->
-spec table_info(storage_host(), storage_table(), atom()) ->
any().
table_info(Host, Tab, InfoKey) ->
Info =
@ -108,7 +115,7 @@ table_info(Host, Tab, InfoKey) ->
%% option() is any mnesia option
%% columndef() defaults to text for all unspecified attributes
-spec create_table(atom(), storage_host(), storage_table(), #table{}) ->
-spec create_table(atom(), storage_host(), storage_table(), list()) ->
tuple().
create_table(mnesia, Host, Tab, Def) ->
@ -130,9 +137,9 @@ define_table(Backend, Host, Name, Def) ->
backend = Backend,
def = Def}).
%% @spec (#table{}) -> [{atom(), any()}]
%% @spec (list()) -> [{atom(), any()}]
-spec filter_mnesia_tabdef(#table{}) ->
-spec filter_mnesia_tabdef(list()) ->
[any()].
filter_mnesia_tabdef(TabDef) ->
@ -202,6 +209,7 @@ dirty_read(Host, Tab, Key) ->
| {'or', matchrule(), matchrule()}
| {'orelse', matchrule(), matchrule()}
| {'=', Attribute::atom(), matchvalue()}
| {'<', Attribute::atom(), matchvalue()}
| {'=/=', Attribute::atom(), matchvalue()}
| {like, Attribute::atom(), matchvalue()}).

View File

@ -22,7 +22,7 @@
attributes :: [string()], % Columns
columns :: string(), % "\"col1\", \"col2\" ,..."
column_names :: [{string(), [string()]}], % [{string(), [string()]}] (already quoted)
types :: [{string(), atom()}],
types :: [{string(), atom() | tuple()}],
host :: string()
}).
-record(odbc_cont, {tabdef, sql, offset = 0, limit}).

View File

@ -99,11 +99,12 @@ get_features(#caps{node = Node, version = Version, exts = Exts}) ->
SubNodes = [Version | Exts],
lists:foldl(
fun(SubNode, Acc) ->
case mnesia:dirty_read({caps_features,
node_to_binary(Node, SubNode)}) of
[] ->
BinaryNode = node_to_binary(Node, SubNode),
case cache_tab:lookup(caps_features, BinaryNode,
caps_read_fun(BinaryNode)) of
error ->
Acc;
[#caps_features{features = Features}] ->
{ok, Features} ->
binary_to_features(Features) ++ Acc
end
end, [], SubNodes).
@ -189,13 +190,23 @@ disco_info(Acc, _Host, _Module, _Node, _Lang) ->
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, _Opts]) ->
init([Host, Opts]) ->
case catch mnesia:table_info(caps_features, storage_type) of
{'EXIT', _} ->
ok;
disc_only_copies ->
ok;
_ ->
mnesia:delete_table(caps_features)
end,
mnesia:create_table(caps_features,
[{disc_copies, [node()]},
[{disc_only_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), disc_copies),
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000),
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
HostB = list_to_binary(Host),
ejabberd_hooks:add(user_send_packet, HostB, ?MODULE, user_send_packet, 75),
ejabberd_hooks:add(c2s_stream_features, HostB,
@ -245,8 +256,9 @@ code_change(_OldVsn, State, _Extra) ->
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node,
BinaryNode = node_to_binary(Node, SubNode),
case mnesia:dirty_read({caps_features, BinaryNode}) of
[] ->
case cache_tab:lookup(caps_features, BinaryNode,
caps_read_fun(BinaryNode)) of
error ->
IQ = #iq{type = get,
iq_ns = ?NS_JABBER_CLIENT,
payload = #xmlel{ns = ?NS_DISCO_INFO, name = 'query',
@ -272,8 +284,10 @@ feature_response(#iq{type = result, payload = El},
[]
end, El#xmlel.children),
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
mnesia:dirty_write(#caps_features{node_pair = BinaryNode,
features = features_to_binary(Features)}),
BinaryFeatures = features_to_binary(Features),
cache_tab:insert(
caps_features, BinaryNode, BinaryFeatures,
caps_write_fun(BinaryNode, BinaryFeatures)),
feature_request(Host, From, Caps, SubNodes);
feature_response(timeout, _Host, _From, _Caps, _SubNodes) ->
ok;
@ -281,7 +295,8 @@ feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) ->
%% We got type=error or invalid type=result stanza, so
%% we cache empty feature not to probe the client permanently
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
cache_tab:insert(caps_features, BinaryNode, [],
caps_write_fun(BinaryNode, [])),
feature_request(Host, From, Caps, SubNodes).
node_to_binary(Node, SubNode) ->
@ -290,6 +305,23 @@ node_to_binary(Node, SubNode) ->
features_to_binary(L) -> [list_to_binary(I) || I <- L].
binary_to_features(L) -> [binary_to_list(I) || I <- L].
caps_read_fun(Node) ->
fun() ->
case mnesia:dirty_read({caps_features, Node}) of
[#caps_features{features = Features}] ->
{ok, Features};
_ ->
error
end
end.
caps_write_fun(Node, Features) ->
fun() ->
mnesia:dirty_write(
#caps_features{node_pair = Node,
features = Features})
end.
make_my_disco_hash(Host) ->
JID = exmpp_jid:make(Host),
case {ejabberd_hooks:run_fold(disco_local_features,

View File

@ -232,8 +232,8 @@ normal_state({route, From, undefined,
message_time = Now,
message_shaper = MessageShaper,
message = Packet},
RoomQueue = queue:in(
{message, From},
RoomQueue =
queue:in({message, From},
StateData#state.room_queue),
StateData2 =
store_user_activity(
@ -1684,7 +1684,7 @@ add_new_user(From, Nick, Packet, StateData) ->
StateData;
captcha_required ->
SID = case exmpp_stanza:get_id(Packet) of
undefined -> "";
undefined -> <<"">>;
SID1 -> SID1
end,
RoomJID = StateData#state.jid,
@ -1693,7 +1693,7 @@ add_new_user(From, Nick, Packet, StateData) ->
SID, RoomJID, To, Lang, From) of
{ok, ID, CaptchaEls} ->
MsgPkt = #xmlel{name = 'message',
attrs = [#xmlattr{name = 'id', value = ID}],
attrs = [#xmlattr{name = 'id', value = list_to_binary(ID)}],
children = CaptchaEls},
Robots = ?DICT:store(From,
{Nick, Packet}, StateData#state.robots),
@ -3042,8 +3042,8 @@ get_config(Lang, StateData, From) ->
true ->
[#xmlel{name = 'option', attrs = [?XMLATTR('label',
translate:translate(Lang, "No limit"))],
children = [#xmlel{name = 'value', children = [#xmlcdata{
cdata = <<"none">>}]}]}]
children = [#xmlel{name = 'value',
children = [#xmlcdata{cdata = <<"none">>}]}]}]
end ++
[#xmlel{name = 'option', attrs = [?XMLATTR('label', N)],
children = [#xmlel{name = 'value', children = [

View File

@ -157,8 +157,8 @@ start(Host, Opts) ->
{match_presence_out, atom}
]}]),
update_tables(Host, Backend),
gen_storage:add_table_index(Host, privacy_list, name),
gen_storage:add_table_index(Host, privacy_list_data, name),
gen_storage:add_table_index(HostB, privacy_list, name),
gen_storage:add_table_index(HostB, privacy_list_data, name),
ejabberd_hooks:add(privacy_iq_get, HostB,
?MODULE, process_iq_get, 50),
ejabberd_hooks:add(privacy_iq_set, HostB,
@ -215,20 +215,21 @@ process_iq_get(_, From, _To, #iq{payload = SubEl},
process_lists_get(LUser, LServer, Active) ->
LServerB = list_to_binary(LServer),
F = fun() ->
Default =
case gen_storage:read(LServer, {privacy_default_list, {LUser, LServer}}) of
case gen_storage:read(LServerB, {privacy_default_list, {LUser, LServer}}) of
[#privacy_default_list{name = Name}] ->
Name;
_ ->
none
end,
Lists = [List#privacy_list.name
|| List <- gen_storage:read(LServer, {privacy_list, {LUser, LServer}})],
|| List <- gen_storage:read(LServerB, {privacy_list, {LUser, LServer}})],
{Default, Lists}
end,
case gen_storage:transaction(LServer, privacy_list, F) of
{'EXIT', _Reason} ->
case gen_storage:transaction(LServerB, privacy_list, F) of
{aborted, _Reason} ->
{error, 'internal-server-error'};
{atomic, {Default, Lists}} ->
case Lists of
@ -258,19 +259,20 @@ process_list_get(_LUser, _LServer, false) ->
{error, 'bad-request'};
process_list_get(LUser, LServer, Name) ->
LServerB = list_to_binary(LServer),
F = fun() ->
case gen_storage:select(LServer, privacy_list,
case gen_storage:select(LServerB, privacy_list,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]) of
[] ->
none;
[#privacy_list{}] ->
gen_storage:select(LServer, privacy_list_data,
gen_storage:select(LServerB, privacy_list_data,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}])
end
end,
case gen_storage:transaction(LServer, privacy_list, F) of
case gen_storage:transaction(LServerB, privacy_list, F) of
{aborted, _Reason} ->
{error, 'internal-server-error'};
{atomic, none} ->
@ -375,10 +377,11 @@ process_iq_set(_, From, _To, #iq{payload = SubEl}) ->
process_default_set(LUser, LServer, false) ->
LServerB = list_to_binary(LServer),
F = fun() ->
gen_storage:delete(LServer, {privacy_default_list, {LUser, LServer}})
gen_storage:delete(LServerB, {privacy_default_list, {LUser, LServer}})
end,
case gen_storage:transaction(LServer, privacy_default_list, F) of
case gen_storage:transaction(LServerB, privacy_default_list, F) of
{atomic, _} ->
{result, []};
_ ->
@ -386,20 +389,21 @@ process_default_set(LUser, LServer, false) ->
end;
process_default_set(LUser, LServer, Name) ->
LServerB = list_to_binary(LServer),
F = fun() ->
case gen_storage:select(LServer, privacy_list,
case gen_storage:select(LServerB, privacy_list,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]) of
[] ->
{error, 'item-not-found'};
[#privacy_list{}] ->
gen_storage:write(LServer,
gen_storage:write(LServerB,
#privacy_default_list{user_host = {LUser, LServer},
name = Name}),
{result, []}
end
end,
case gen_storage:transaction(LServer, privacy_list, F) of
case gen_storage:transaction(LServerB, privacy_list, F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
@ -413,21 +417,25 @@ process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}};
process_active_set(LUser, LServer, Name) ->
LServerB = list_to_binary(LServer),
F = fun() ->
case gen_storage:select(LServer, privacy_list,
case gen_storage:select(LServerB, privacy_list,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]) of
[#privacy_list{}] ->
List = gen_storage:select(LServer, privacy_list_data,
Data = gen_storage:select(LServerB, privacy_list_data,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
List = list_data_to_items(Data),
NeedDb = is_list_needdb(List),
{result, [], #userlist{name = Name,
list = list_data_to_items(List)}};
needdb = NeedDb,
list = List}};
[] ->
{error, 'item-not-found'}
end
end,
case gen_storage:transaction(LServer, privacy_list, F) of
case gen_storage:transaction(LServerB, privacy_list, F) of
{atomic, Res} ->
Res;
_ ->
@ -440,26 +448,27 @@ process_list_set(_LUser, _LServer, false, _Els) ->
{error, 'bad-request'};
process_list_set(LUser, LServer, Name, Els) ->
LServerB = list_to_binary(LServer),
case parse_items(Els) of
false ->
{error, 'bad-request'};
remove ->
F = fun() ->
case gen_storage:read(LServer,
case gen_storage:read(LServerB,
{privacy_default_list, {LUser, LServer}}) of
[#privacy_default_list{name = Default}] when Name == Default ->
{error, 'conflict'};
_ ->
gen_storage:delete_where(LServer, privacy_list,
gen_storage:delete_where(LServerB, privacy_list,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
gen_storage:delete_where(LServer, privacy_list_data,
gen_storage:delete_where(LServerB, privacy_list_data,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
{result, []}
end
end,
case gen_storage:transaction(LServer, privacy_list, F) of
case gen_storage:transaction(LServerB, privacy_list, F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
@ -477,24 +486,24 @@ process_list_set(LUser, LServer, Name, Els) ->
List ->
F = fun() ->
OldData =
gen_storage:select(LServer, privacy_list_data,
gen_storage:select(LServerB, privacy_list_data,
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
lists:foreach(
fun(Data1) ->
gen_storage:delete_object(LServer, Data1)
gen_storage:delete_object(LServerB, Data1)
end, OldData),
gen_storage:write(LServer, #privacy_list{user_host = {LUser, LServer},
gen_storage:write(LServerB, #privacy_list{user_host = {LUser, LServer},
name = Name}),
NewData = list_items_to_data(LUser, LServer, Name, List),
lists:foreach(
fun(Data1) ->
gen_storage:write(LServer, Data1)
gen_storage:write(LServerB, Data1)
end, NewData),
{result, []}
end,
case gen_storage:transaction(LServer, privacy_list, F) of
case gen_storage:transaction(LServerB, privacy_list, F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
@ -647,17 +656,17 @@ is_list_needdb(Items) ->
end
end, Items).
get_user_list(_, User, Server)
when is_binary(User), is_binary(Server) ->
get_user_list(_, User, LServerB)
when is_binary(User), is_binary(LServerB) ->
LUser = binary_to_list(User),
LServer = binary_to_list(Server),
LServer = binary_to_list(LServerB),
F = fun() ->
case gen_storage:read(LServer,
case gen_storage:read(LServerB,
{privacy_default_list, {LUser, LServer}}) of
[] ->
#userlist{};
[#privacy_default_list{name = Default}] ->
Data = gen_storage:select(LServer, privacy_list_data,
Data = gen_storage:select(LServerB, privacy_list_data,
[{'=', user_host, {LUser, LServer}},
{'=', name, Default}]),
List = list_data_to_items(Data),
@ -667,7 +676,7 @@ get_user_list(_, User, Server)
list = List}
end
end,
{atomic, Res} = gen_storage:transaction(LServer, privacy_default_list, F),
{atomic, Res} = gen_storage:transaction(LServerB, privacy_default_list, F),
Res.
@ -768,9 +777,9 @@ is_ptype_match(Item, PType) ->
%% TODO: Investigate this: sometimes Value has binaries, other times has strings
is_type_match(jid, Value, JID, _Subscription, _Groups) ->
{User, Server, Resource} = exmpp_jid:to_lower(exmpp_jid:parse(Value)),
((User == undefined) orelse (User == []) orelse (User == exmpp_jid:prep_node(JID)))
andalso ((Server == undefined) orelse (Server == []) orelse (Server == exmpp_jid:prep_domain(JID)))
andalso ((Resource == undefined) orelse (Resource == []) orelse (Resource == exmpp_jid:prep_resource(JID)));
((User == undefined) orelse (User == exmpp_jid:prep_node(JID)))
andalso ((Server == undefined) orelse (Server == exmpp_jid:prep_domain(JID)))
andalso ((Resource == undefined) orelse (Resource == exmpp_jid:prep_resource(JID)));
is_type_match(subscription, Value, _JID, Subscription, _Groups) ->
Value == Subscription;
is_type_match(group, Value, _JID, _Subscription, Groups) ->
@ -780,10 +789,11 @@ is_type_match(group, Value, _JID, _Subscription, Groups) ->
remove_user(User, Server) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
LServerB = list_to_binary(LServer),
F = fun() ->
gen_storage:delete(LServer, {privacy_list, {LUser, LServer}})
gen_storage:delete(LServerB, {privacy_list, {LUser, LServer}})
end,
case gen_storage:transaction(LServer, privacy_list, F) of
case gen_storage:transaction(LServerB, privacy_list, F) of
{atomic, _} ->
{result, []};
_ ->

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -75,8 +75,7 @@ terminate(Host, ServerHost) ->
node_flat:terminate(Host, ServerHost).
options() ->
[{node_type, __TO_BE_DEFINED__},
{deliver_payloads, true},
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@ -123,8 +122,8 @@ delete_node(Removed) ->
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
node_flat:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -26,8 +26,6 @@
-module(node_buddy).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-behaviour(gen_pubsub_node).
@ -130,8 +128,8 @@ delete_node(Removed) ->
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -27,7 +27,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -128,8 +127,8 @@ delete_node(Removed) ->
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -57,124 +57,413 @@
path_to_node/1]).
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(Host, ServerHost, Opts) ->
node_flat:init(Host, ServerHost, Opts).
-spec(terminate/2 ::
(
Host :: string(),
ServerHost :: string())
-> 'ok'
).
terminate(Host, ServerHost) ->
node_flat:terminate(Host, ServerHost).
-spec(options/0 :: () -> [nodeOption()]).
options() ->
[{node_type, leaf} | node_flat:options()].
[{'node_type', 'leaf'} | node_flat:options()].
-spec(features/0 :: () -> [Feature::string()]).
features() ->
["multi-collection" | node_flat:features()].
create_node_permission(_Host, _ServerHost, _Node, _ParentNode,
_Owner, _Access) ->
-spec(create_node_permission/6 ::
(
Host :: hostPubsub(),
ServerHost :: string(),
NodeId :: nodeId(),
ParentNodeId :: nodeId(),
JID :: jidEntity(),
Access :: atom())
-> {'result', 'true'}
).
create_node_permission(_Host, _ServerHost, _NodeId, _ParentNodeId,
_JID, _Access) ->
{result, true}.
create_node(NodeID, Owner) ->
node_flat:create_node(NodeID, Owner).
delete_node(Removed) ->
node_flat:delete_node(Removed).
-spec(create_node/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', {'default', 'broadcast'}}
).
subscribe_node(NodeID, Sender, Subscriber, AccessModel,
create_node(NodeIdx, JID) ->
node_flat:create_node(NodeIdx, JID).
-spec(delete_node/1 ::
(
Nodes :: [Node::pubsubNode()])
-> {result, {'default', 'broadcast',
Reply :: [{Node :: pubsubNode(),
[{Owner :: bareUsr(),
Subscriptions :: [{Subscription :: subscription(),
SubId :: subId()}]}]}]}}
).
delete_node(Nodes) ->
node_flat:delete_node(Nodes).
-spec(subscribe_node/8 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Subscriber :: jidEntity(),
AccessModel :: accessModel(),
SendLast :: atom(),
PresenceSubscription :: boolean(),
RosterGroup :: boolean(),
Options :: [nodeOption()])
-> {'result', {'default',
Subscription :: 'subscribed',
SubId :: subId()}}
| {'result', {'default',
Subscription :: 'subscribed',
SubId :: subId(),
SendLast ::' send_last'}}
| {'result', {'default',
Subscription :: 'pending',
SubId :: subId()}}
| {'error', _} %% TODO add all error cases
).
subscribe_node(NodeIdx, JID, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat:subscribe_node(NodeID, Sender, Subscriber, AccessModel,
node_flat:subscribe_node(NodeIdx, JID, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup,
Options).
unsubscribe_node(NodeID, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeID, Sender, Subscriber, SubID).
publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) ->
-spec(unsubscribe_node/4 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Subscriber :: jidEntity(),
SubId :: subId())
-> {'result', 'default'} | {'error', _} %% TODO : add all error cases
).
unsubscribe_node(NodeIdx, JID, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeIdx, JID, Subscriber, SubId).
-spec(publish_item/6 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
PublishModel :: atom(), %% TODO : make a generic publishMod() type
MaxItems :: 'unlimited' | integer(),
ItemId :: itemId(),
Payload :: payload())
-> {'result', {'default', 'broadcast', ItemIds :: [] | [itemId()]}}
| {'error', _}
).
publish_item(NodeIdx, JID, PublishModel, MaxItems, ItemId, Payload) ->
%% TODO: should look up the NodeTree plugin here. There's no
%% access to the Host of the request at this level, so for now we
%% just use nodetree_dag.
case nodetree_dag:get_node(NodeID) of
case nodetree_dag:get_node(NodeIdx) of
#pubsub_node{options = Options} ->
case find_opt(node_type, Options) of
collection ->
case find_opt('node_type', Options) of
'collection' ->
{error, mod_pubsub:extended_error('not-allowed', "publish")};
_ ->
node_flat:publish_item(NodeID, Publisher, Model,
MaxItems, ItemID, Payload)
node_flat:publish_item(NodeIdx, JID, PublishModel,
MaxItems, ItemId, Payload)
end;
Err ->
Err
Error ->
Error
end.
-spec(find_opt/2 ::
(
Key :: atom(),
Options :: [] | [Option::nodeOption()])
-> Value :: 'false' | term()
).
find_opt(_, []) -> false;
find_opt(Option, [{Option, Value} | _]) -> Value;
find_opt(Option, [_ | T]) -> find_opt(Option, T).
find_opt(Key, [{Key, Value} | _]) -> Value;
find_opt(Key, [_ | Options]) -> find_opt(Key, Options).
remove_extra_items(NodeID, MaxItems, ItemIDs) ->
node_flat:remove_extra_items(NodeID, MaxItems, ItemIDs).
delete_item(NodeID, Publisher, PublishModel, ItemID) ->
node_flat:delete_item(NodeID, Publisher, PublishModel, ItemID).
-spec(remove_extra_items/3 ::
(
NodeIdx :: nodeIdx(),
MaxItems :: 'unlimited' | integer(),
ItemsIds :: [ItemId::itemId()])
-> {'result',
{OldItems :: [] | [ItemId::itemId()],
NewItems :: [] | [ItemId::itemId()]}}
).
purge_node(NodeID, Owner) ->
node_flat:purge_node(NodeID, Owner).
remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(NodeIdx, MaxItems, ItemIds).
get_entity_affiliations(Host, Owner) ->
node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(NodeID) ->
node_flat:get_node_affiliations(NodeID).
-spec(delete_item/4 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
PublishModel :: atom(),
ItemId :: itemId())
-> {'result', {'default', 'broadcast'}} | {'error', _}
).
get_affiliation(NodeID, Owner) ->
node_flat:get_affiliation(NodeID, Owner).
delete_item(NodeIdx, JID, PublishModel, ItemId) ->
node_flat:delete_item(NodeIdx, JID, PublishModel, ItemId).
set_affiliation(NodeID, Owner, Affiliation) ->
node_flat:set_affiliation(NodeID, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_flat:get_entity_subscriptions(Host, Owner).
-spec(purge_node/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', {'default', 'broadcast'}} | {'error', 'forbidden'}
).
get_node_subscriptions(NodeID) ->
node_flat:get_node_subscriptions(NodeID).
purge_node(NodeIdx, JID) ->
node_flat:purge_node(NodeIdx, JID).
get_subscriptions(NodeID, Owner) ->
node_flat:get_subscriptions(NodeID, Owner).
set_subscriptions(NodeID, Owner, Subscription, SubID) ->
node_flat:set_subscriptions(NodeID, Owner, Subscription, SubID).
-spec(get_entity_affiliations/2 ::
(
Host :: binary(),
JID :: jidEntity())
-> {'result', Reply :: [] | [{Node::pubsubNode(), Affiliation::affiliation()}]}
).
get_pending_nodes(Host, Owner) ->
node_flat:get_pending_nodes(Host, Owner).
get_entity_affiliations(Host, JID) ->
node_flat:get_entity_affiliations(Host, JID).
get_states(NodeID) ->
node_flat:get_states(NodeID).
get_state(NodeID, JID) ->
node_flat:get_state(NodeID, JID).
-spec(get_node_affiliations/1 ::
(
NodeIdx :: nodeIdx())
-> {'result', [] | [{Entity::fullUsr(), Affiliation::affiliation()}]}
).
get_node_affiliations(NodeIdx) ->
node_flat:get_node_affiliations(NodeIdx).
-spec(get_affiliation/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', Affiliation::affiliation()}
).
get_affiliation(NodeIdx, JID) ->
node_flat:get_affiliation(NodeIdx, JID).
-spec(set_affiliation/3 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Affiliation :: affiliation())
-> 'ok' | {error, 'internal-server-error'}
).
set_affiliation(NodeIdx, JID, Affiliation) ->
node_flat:set_affiliation(NodeIdx, JID, Affiliation).
-spec(get_entity_subscriptions/2 ::
(
Host :: hostPubsub(),
JID :: jidEntity())
-> {'result', []
| [{Node :: pubsubNode(),
Subscription :: subscription(),
SubId :: subId(),
Entity :: fullUsr()}]}
).
get_entity_subscriptions(Host, JID) ->
node_flat:get_entity_subscriptions(Host, JID).
-spec(get_node_subscriptions/1 ::
(
NodeIdx :: nodeIdx())
-> {'result', []
| [{Entity::fullUsr(), 'none'}]
| [{Entity::fullUsr(), Subscription::subscription(), SubId::subId()}]}
).
get_node_subscriptions(NodeIdx) ->
node_flat:get_node_subscriptions(NodeIdx).
-spec(get_subscriptions/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', Subscriptions :: [] | [{Subscription::subscription(), SubId::subId()}]}
).
get_subscriptions(NodeIdx, JID) ->
node_flat:get_subscriptions(NodeIdx, JID).
-spec(set_subscriptions/4 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Subscription :: subscription(),
SubId :: subId())
-> 'ok'
| {Subscription::subscription(), SubId::subId()}
| {error, _}
).
set_subscriptions(NodeIdx, JID, Subscription, SubId) ->
node_flat:set_subscriptions(NodeIdx, JID, Subscription, SubId).
-spec(get_pending_nodes/2 ::
(
Host :: hostPubsub(),
JID :: jidEntity())
-> 'false' | {'value', NodeId::nodeId()}
).
get_pending_nodes(Host, JID) ->
node_flat:get_pending_nodes(Host, JID).
-spec(get_states/1 ::
(
NodeIdx :: nodeIdx())
-> {'result', States :: [] | [State::pubsubState()]}
).
get_states(NodeIdx) ->
node_flat:get_states(NodeIdx).
-spec(get_state/2 ::
(
NodeIdx :: nodeIdx(),
Entity :: fullUsr())
-> State::pubsubState()
).
get_state(NodeIdx, Entity) ->
node_flat:get_state(NodeIdx, Entity).
-spec(set_state/1 ::
(
State :: pubsubState())
-> 'ok' | {error, 'internal-server-error'}
).
set_state(State) ->
node_flat:set_state(State).
get_items(NodeID, From) ->
node_flat:get_items(NodeID, From).
get_items(NodeID, JID, AccessModel, PresenceSubscription,
RosterGroup, SubID) ->
node_flat:get_items(NodeID, JID, AccessModel, PresenceSubscription,
RosterGroup, SubID).
-spec(get_items/2 ::
(
NodeIdx :: nodeIdx(),
Entity :: fullUsr())
-> {'result', Items :: [] | [Item::pubsubItem()]}
).
get_item(NodeID, ItemID) ->
node_flat:get_item(NodeID, ItemID).
get_items(NodeIdx, Entity) ->
node_flat:get_items(NodeIdx, Entity).
get_item(NodeID, ItemID, JID, AccessModel, PresenceSubscription,
RosterGroup, SubID) ->
node_flat:get_item(NodeID, ItemID, JID, AccessModel,
PresenceSubscription, RosterGroup, SubID).
-spec(get_items/6 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
AccessModel :: accessModel(),
PresenceSubscription :: boolean(),
RosterGroup :: boolean(),
SubId :: subId())
-> {'result', Items :: [] | [Item::pubsubItem()]}
| {'error', _}
).
get_items(NodeIdx, JID, AccessModel, PresenceSubscription,
RosterGroup, SubId) ->
node_flat:get_items(NodeIdx, JID, AccessModel, PresenceSubscription,
RosterGroup, SubId).
-spec(get_item/2 ::
(
NodeIdx :: nodeIdx(),
ItemId :: itemId())
-> {'result', Item::pubsubItem()} | {'error', 'item-not-found'}
).
get_item(NodeIdx, ItemId) ->
node_flat:get_item(NodeIdx, ItemId).
-spec(get_item/7 ::
(
NodeIdx :: nodeIdx(),
ItemId :: itemId(),
JID :: jidEntity(),
AccessModel :: accessModel(),
PresenceSubscription :: boolean(),
RosterGroup :: boolean(),
SubId :: subId())
-> {'result', Item::pubsubItem()} | {'error', 'item-not-found'}
).
get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription,
RosterGroup, SubId) ->
node_flat:get_item(NodeIdx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
-spec(set_item/1 ::
(
Item :: pubsubItem())
-> 'ok' | {error, 'internal-server-error'}
).
set_item(Item) ->
node_flat:set_item(Item).
get_item_name(Host, Node, ID) ->
node_flat:get_item_name(Host, Node, ID).
get_item_name(Host, Node, ItemId) ->
node_flat:get_item_name(Host, Node, ItemId).
node_to_path(Node) ->
node_flat:node_to_path(Node).

View File

@ -27,7 +27,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -126,13 +125,13 @@ subscribe_node(_NodeId, _Sender, _Subscriber, _AccessModel,
_SendLast, _PresenceSubscription, _RosterGroup, _Options) ->
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubID) ->
unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubId) ->
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
lists:foreach(fun(SubNode) ->
node_flat:publish_item(
SubNode#pubsub_node.id, Publisher, Model,
SubNode#pubsub_node.idx, Publisher, Model,
MaxItems, ItemId, Payload)
end, nodetree_tree:get_subnodes(NodeId, Publisher, Publisher)).

File diff suppressed because it is too large Load Diff

View File

@ -37,12 +37,11 @@
%%% 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>
%%TODO : fix SQL requests with nodeid/id and id/idx changes
-module(node_flat_odbc).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-include("jlib.hrl"). %% for rsm_in and rsm_out records definitions
-define(PUBSUB, mod_pubsub_odbc).
@ -191,9 +190,9 @@ features() ->
%% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool()
%% Host = mod_pubsub:host()
%% ServerHost = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% ParentNode = mod_pubsub:pubsubNode()
%% Owner = mod_pubsub:jid()
%% Node = mod_pubsub:node()
%% ParentNode = mod_pubsub:node()
%% Owner = ljid()
%% Access = all | atom()
%% @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
@ -212,34 +211,27 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
%% @spec (NodeId, Owner) ->
%% {result, Result} | exit
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
%% NodeId = nodeidx()
%% Owner = ljid()
%% @doc <p></p>
create_node(NodeId, Owner) ->
OwnerKey = jlib:short_prepd_bare_jid(Owner),
State = #pubsub_state{stateid = {OwnerKey, NodeId}, affiliation = owner},
State = #pubsub_state{id = {OwnerKey, NodeId}, affiliation = owner},
catch ejabberd_odbc:sql_query_t(
["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values(", state_to_raw(NodeId, State), ");"]),
{result, {default, broadcast}}.
%% @spec (Removed) -> ok
%% Removed = [mod_pubsub:pubsubNode()]
%% Removed = [mod_pubsub:node()]
%% @doc <p>purge items of deleted nodes after effective deletion.</p>
delete_node(Removed) ->
%% pablo: TODO, this is present on trunk/node_flat but not on trunk/node_flat_odbc.
%% check what is the desired behaviour
%% Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
%% lists:map(fun(S) ->
%% {J, S}
%% end, Ss)
%% end,
Reply = lists:map(
fun(#pubsub_node{id = NodeId} = PubsubNode) ->
fun(#pubsub_node{idx = Nidx} = PubsubNode) ->
Subscriptions = case catch ejabberd_odbc:sql_query_t(
["select jid, subscriptions "
"from pubsub_state "
"where nodeid='", NodeId, ";"]) of
"where nodeid='", Nidx, ";"]) of
{selected, ["jid", "subscriptions"], RItems} ->
lists:map(fun({SJID, Subscriptions}) ->
{decode_jid(SJID), decode_subscriptions(Subscriptions)}
@ -345,9 +337,9 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
%% @spec (NodeId, Sender, Subscriber, SubId) ->
%% {error, Reason} | {result, []}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Sender = mod_pubsub:jid()
%% Subscriber = mod_pubsub:jid()
%% NodeId = nodeidx()
%% Sender = ljid()
%% Subscriber = ljid()
%% SubId = mod_pubsub:subid()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
@ -417,11 +409,11 @@ delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscrip
%% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% {true, PubsubItem} | {result, Reply}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Publisher = mod_pubsub:jid()
%% NodeId = nodeidx()
%% Publisher = ljid()
%% PublishModel = atom()
%% MaxItems = integer()
%% ItemId = string()
%% ItemId = item()
%% Payload = term()
%% @doc <p>Publishes the item passed as parameter.</p>
%% <p>The mechanism works as follow:
@ -474,7 +466,7 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% Note: this works cause set_item tries an update before
%% the insert, and the update just ignore creation field.
PubId = {now(), SubKey},
set_item(#pubsub_item{itemid = {ItemId, NodeId},
set_item(#pubsub_item{id = {ItemId, NodeId},
creation = {now(), GenKey},
modification = PubId,
payload = Payload}),
@ -488,10 +480,10 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
end.
%% @spec (NodeId, MaxItems, ItemIds) -> {NewItemIds,OldItemIds}
%% NodeId = mod_pubsub:pubsubNodeId()
%% NodeId = nodeidx()
%% MaxItems = integer() | unlimited
%% ItemIds = [ItemId::string()]
%% NewItemIds = [ItemId::string()]
%% ItemIds = [item()]
%% NewItemIds = [item()]
%% @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
@ -515,10 +507,10 @@ remove_extra_items(NodeId, MaxItems, ItemIds) ->
%% @spec (NodeId, Publisher, PublishModel, ItemId) ->
%% {error, Reason::stanzaError()} |
%% {result, []}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Publisher = mod_pubsub:jid()
%% NodeId = nodeidx()
%% Publisher = ljid()
%% PublishModel = atom()
%% ItemId = string()
%% ItemId = item()
%% @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>
@ -549,8 +541,8 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
%% @spec (NodeId, Owner) ->
%% {error, Reason::stanzaError()} |
%% {result, {default, broadcast}}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
%% NodeId = nodeidx()
%% Owner = ljid()
purge_node(NodeId, Owner) ->
GenKey = jlib:short_prepd_bare_jid(Owner),
GenState = get_state(NodeId, GenKey),
@ -569,7 +561,7 @@ purge_node(NodeId, Owner) ->
%% @spec (Host, JID) -> [{Node,Affiliation}]
%% Host = host()
%% JID = mod_pubsub:jid()
%% JID = ljid()
%% @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
@ -632,7 +624,7 @@ set_affiliation(NodeId, Owner, Affiliation) when ?IS_JID(Owner)->
%% @spec (Host, Owner) -> [{Node,Subscription}]
%% Host = host()
%% Owner = mod_pubsub:jid()
%% Owner = ljid()
%% @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
@ -664,13 +656,13 @@ get_entity_subscriptions(Host, Owner) ->
{selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
lists:foldl(fun({N, T, I, J, S}, Acc) ->
Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
Jid = decode_jid(J),
JID = decode_jid(J),
case decode_subscriptions(S) of
[] ->
[{Node, none, Jid}|Acc];
[{Node, none, JID}|Acc];
Subs ->
lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2];
(Sub, Acc2) -> [{Node, Sub, Jid}|Acc2]
lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, JID}|Acc2];
(Sub, Acc2) -> [{Node, Sub, JID}|Acc2]
end, Acc, Subs)
end
end, [], RItems);
@ -707,13 +699,13 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
{selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
lists:foldl(fun({N, T, I, J, S}, Acc) ->
Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
Jid = decode_jid(J),
JID = decode_jid(J),
case decode_subscriptions(S) of
[] ->
[{Node, none, Jid}|Acc];
[{Node, none, JID}|Acc];
Subs ->
lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2];
(Sub, Acc2) -> [{Node, Sub, Jid}|Acc2]
lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, JID}|Acc2];
(Sub, Acc2) -> [{Node, Sub, JID}|Acc2]
end, Acc, Subs)
end
end, [], RItems);
@ -729,13 +721,13 @@ get_node_subscriptions(NodeId) ->
"where nodeid='", NodeId, "';"]) of
{selected, ["jid", "subscriptions"], RItems} ->
lists:foldl(fun({J, S}, Acc) ->
Jid = decode_jid(J),
JID = decode_jid(J),
case decode_subscriptions(S) of
[] ->
[{Jid, none}|Acc];
[{JID, none}|Acc];
Subs ->
lists:foldl(fun({Sub, SubId}, Acc2) -> [{Jid, Sub, SubId}|Acc2];
(Sub, Acc2) -> [{Jid, Sub}|Acc2]
lists:foldl(fun({Sub, SubId}, Acc2) -> [{JID, Sub, SubId}|Acc2];
(Sub, Acc2) -> [{JID, Sub}|Acc2]
end, Acc, Subs)
end
end, [], RItems);
@ -786,8 +778,8 @@ replace_subscription(NewSub, SubState) ->
replace_subscription(_, [], Acc) ->
Acc;
replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]).
replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
new_subscription(NodeId, Owner, Subscription, SubState) ->
case pubsub_subscription_odbc:subscribe_node(Owner, NodeId, []) of
@ -800,13 +792,13 @@ new_subscription(NodeId, Owner, Subscription, SubState) ->
end.
unsub_with_subid(NodeId, SubId, SubState) ->
pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid,
pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.id,
NodeId, SubId),
NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end,
SubState#pubsub_state.subscriptions),
case {NewSubs, SubState#pubsub_state.affiliation} of
{[], none} ->
del_state(NodeId, element(1, SubState#pubsub_state.stateid));
del_state(NodeId, element(1, SubState#pubsub_state.id));
_ ->
set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
@ -814,21 +806,21 @@ unsub_with_subid(NodeId, SubId, SubState) ->
%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason}
%% Host = host()
%% Owner = jid()
%% Node = pubsubNode()
%% Node = node()
%% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
GenKey = jlib:short_prepd_bare_jid(Owner),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
States = mnesia:match_object(#pubsub_state{id = {GenKey, '_'},
affiliation = owner,
_ = '_'}),
NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States],
Nidxs = [Nidx || #pubsub_state{id = {_, Nidx}} <- States],
NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
[{nodetree, N}] -> N;
_ -> nodetree_tree_odbc
end,
Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) ->
case lists:member(NID, NodeIDs) of
Reply = mnesia:foldl(fun(#pubsub_state{id = {_, Nidx}} = S, Acc) ->
case lists:member(Nidx, Nidxs) of
true ->
case get_nodes_helper(NodeTree, S) of
{value, Node} -> [Node | Acc];
@ -841,7 +833,7 @@ get_pending_nodes(Host, Owner) ->
{result, Reply}.
get_nodes_helper(NodeTree,
#pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
#pubsub_state{id = {_, N}, subscriptions = Subs}) ->
HasPending = fun ({pending, _}) -> true;
(pending) -> true;
(_) -> false
@ -849,7 +841,7 @@ get_nodes_helper(NodeTree,
case lists:any(HasPending, Subs) of
true ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {_, Node}} ->
#pubsub_node{id = {_, Node}} ->
{value, Node};
_ ->
false
@ -859,7 +851,7 @@ get_nodes_helper(NodeTree,
end.
%% @spec (NodeId) -> {result, [State] | []}
%% NodeId = mod_pubsub:pubsubNodeId()
%% NodeId = nodeidx()
%% State = mod_pubsub:pubsubState()
%% @doc Returns the list of stored states for a given node.
%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
@ -878,7 +870,7 @@ get_states(NodeId) ->
"where nodeid='", NodeId, "';"]) of
{selected, ["jid", "affiliation", "subscriptions"], RItems} ->
{result, lists:map(fun({SJID, Affiliation, Subscriptions}) ->
#pubsub_state{stateid = {decode_jid(SJID), NodeId},
#pubsub_state{id = {decode_jid(SJID), NodeId},
items = itemids(NodeId, SJID),
affiliation = decode_affiliation(Affiliation),
subscriptions = decode_subscriptions(Subscriptions)}
@ -889,13 +881,13 @@ get_states(NodeId) ->
%% @spec (NodeId, JID) -> [State] | []
%% NodeId = mod_pubsub:pubsubNodeId()
%% JID = mod_pubsub:jid()
%% State = mod_pubsub:pubsubItems()
%% NodeId = nodeidx()
%% JID = ljid()
%% State = mod_pubsub:pubsub_state()
%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(NodeId, JID) ->
State = get_state_without_itemids(NodeId, JID),
{SJID, _} = State#pubsub_state.stateid,
{SJID, _} = State#pubsub_state.id,
State#pubsub_state{items = itemids(NodeId, SJID)}.
get_state_without_itemids(NodeId, JID) ->
J = encode_jid(JID),
@ -905,25 +897,25 @@ get_state_without_itemids(NodeId, JID) ->
"where jid='", J, "' "
"and nodeid='", NodeId, "';"]) of
{selected, ["jid", "affiliation", "subscriptions"], [{SJID, Affiliation, Subscriptions}]} ->
#pubsub_state{stateid = {decode_jid(SJID), NodeId},
#pubsub_state{id = {decode_jid(SJID), NodeId},
affiliation = decode_affiliation(Affiliation),
subscriptions = decode_subscriptions(Subscriptions)};
_ ->
#pubsub_state{stateid={JID, NodeId}}
#pubsub_state{id={JID, NodeId}}
end.
%% @spec (State) -> ok | {error, Reason::stanzaError()}
%% State = mod_pubsub:pubsubStates()
%% @doc <p>Write a state into database.</p>
set_state(State) when is_record(State, pubsub_state) ->
{_, NodeId} = State#pubsub_state.stateid,
{_, NodeId} = State#pubsub_state.id,
set_state(NodeId, State).
set_state(NodeId, State) ->
%% NOTE: in odbc version, as we do not handle item list,
%% we just need to update affiliation and subscription
%% cause {JID,NodeId} is the key. if it does not exists, then we insert it.
%% MySQL can be optimized using INSERT ... ON DUPLICATE KEY as well
{JID, _} = State#pubsub_state.stateid,
{JID, _} = State#pubsub_state.id,
J = encode_jid(JID),
S = encode_subscriptions(State#pubsub_state.subscriptions),
A = encode_affiliation(State#pubsub_state.affiliation),
@ -941,7 +933,7 @@ set_state(NodeId, State) ->
{result, []}.
%% @spec (NodeId, JID) -> ok | {error, Reason::stanzaError()}
%% NodeId = mod_pubsub:pubsubNodeId()
%% NodeId = nodeidx()
%% @doc <p>Delete a state from database.</p>
del_state(NodeId, JID) ->
J = encode_jid(JID),
@ -952,8 +944,8 @@ del_state(NodeId, JID) ->
ok.
%% @spec (NodeId, From) -> {[Items], RsmOut} | []
%% NodeId = mod_pubsub:pubsubNodeId()
%% Items = mod_pubsub:pubsubItems()
%% NodeId = nodeidx()
%% Items = mod_pubsub:pubsub_item()
%% @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
@ -1100,9 +1092,9 @@ get_last_items(NodeId, _From, Count) ->
end.
%% @spec (NodeId, ItemId) -> [Item] | []
%% NodeId = mod_pubsub:pubsubNodeId()
%% ItemId = string()
%% Item = mod_pubsub:pubsubItems()
%% NodeId = nodeidx()
%% ItemId = item()
%% Item = mod_pubsub:pubsub_item()
%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(NodeId, ItemId) ->
I = ?PUBSUB:escape(ItemId),
@ -1126,7 +1118,7 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
%%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED('bad-request', "subid-required")};
%%InvalidSubID ->
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")};
Affiliation == outcast ->
@ -1152,10 +1144,10 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
end.
%% @spec (Item) -> ok | {error, Reason::stanzaError()}
%% Item = mod_pubsub:pubsubItems()
%% Item = mod_pubsub:pubsub_item()
%% @doc <p>Write an item into database.</p>
set_item(Item) ->
{ItemId, NodeId} = Item#pubsub_item.itemid,
{ItemId, NodeId} = Item#pubsub_item.id,
I = ?PUBSUB:escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
@ -1182,8 +1174,8 @@ set_item(Item) ->
{result, []}.
%% @spec (NodeId, ItemId) -> ok | {error, Reason::stanzaError()}
%% NodeId = mod_pubsub:pubsubNodeId()
%% ItemId = string()
%% NodeId = nodeidx()
%% ItemId = item()
%% @doc <p>Delete an item from database.</p>
del_item(NodeId, ItemId) ->
I = ?PUBSUB:escape(ItemId),
@ -1337,7 +1329,7 @@ encode_subscriptions(Subscriptions) ->
%%% record getter/setter
state_to_raw(NodeId, State) ->
{JID, _} = State#pubsub_state.stateid,
{JID, _} = State#pubsub_state.id,
J = encode_jid(JID),
A = encode_affiliation(State#pubsub_state.affiliation),
S = encode_subscriptions(State#pubsub_state.subscriptions),
@ -1353,7 +1345,7 @@ raw_to_item(NodeId, {ItemId, SJID, Creation, Modification, XML}) ->
{error, _Reason} -> [];
[El] -> [El]
end,
#pubsub_item{itemid = {ItemId, NodeId},
#pubsub_item{id = {ItemId, NodeId},
creation={ToTime(Creation), JID},
modification={ToTime(Modification), JID},
payload = Payload}.

View File

@ -26,7 +26,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -140,8 +139,8 @@ delete_node(Removed) ->
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -26,7 +26,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -111,8 +110,8 @@ delete_node(Removed) ->
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat_odbc:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -36,8 +36,6 @@
-module(node_mb).
-author('eric@ohmforce.com').
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("pubsub.hrl").
@ -137,8 +135,8 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
NodeId, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_pep:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_pep:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_pep:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -29,8 +29,6 @@
-module(node_pep).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("pubsub.hrl").
@ -70,15 +68,35 @@
path_to_node/1
]).
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(Host, ServerHost, Opts) ->
node_flat:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
-spec(terminate/2 ::
(
Host :: string(),
ServerHost :: string())
-> 'ok'
).
terminate(Host, ServerHost) ->
node_flat:terminate(Host, ServerHost),
ok.
-spec(options/0 :: () -> [nodeOption()]).
options() ->
[{deliver_payloads, true},
{notify_config, false},
@ -98,6 +116,9 @@ options() ->
{deliver_notifications, true},
{presence_based_delivery, true}].
-spec(features/0 :: () -> [Feature::string()]).
features() ->
["create-nodes", %*
"auto-create", %*
@ -117,165 +138,429 @@ features() ->
"subscribe" %*
].
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:short_prepd_jid(Owner),
{User, Server, Resource} = LOwner,
Allowed = case LOwner of
-spec(create_node_permission/6 ::
(
Host :: hostPEP(),
ServerHost :: string(),
NodeId :: nodeId(),
ParentNodeId :: nodeId(),
JID :: jidEntity(),
Access :: atom())
-> {'result', IsAllowed::boolean()}
).
create_node_permission(Host, ServerHost, _NodeId, _ParentNodeId, #jid{node = U, domain = S, resource = R} = JID, Access) ->
Owner = {U,S,R},
Allowed = case Owner of
{undefined, Host, undefined} ->
true; % pubsub service always allowed
_ ->
JID = exmpp_jid:make(User, Server, Resource),
case acl:match_rule(ServerHost, Access, JID) of
allow ->
'allow' ->
case Host of
{User, Server, _} -> true;
{_, _, _} -> true;
_ -> false
end;
E ->
?DEBUG("Create not allowed : ~p~n", [E]),
Error ->
?DEBUG("Create not allowed : ~p~n", [Error]),
false
end
end,
{result, Allowed}.
create_node(NodeId, Owner) ->
case node_flat:create_node(NodeId, Owner) of
{result, _} -> {result, []};
Error -> Error
end.
delete_node(Removed) ->
case node_flat:delete_node(Removed) of
{result, {_, _, Removed}} -> {result, {[], Removed}};
Error -> Error
end.
-spec(create_node/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', []}% | {'error', 'internal-server-error'}
).
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
create_node(NodeIdx, JID) ->
{result, _} = node_flat:create_node(NodeIdx, JID),
{result, []}.
% case node_flat:create_node(NodeIdx, JID) of
% {result, _} -> {result, []};
% Error -> Error
% end.
-spec(delete_node/1 ::
(
Nodes :: [Node::pubsubNode()])
-> {result, {[],
Removed :: [] | [{Node :: pubsubNode(),
[{Owner :: bareUsr(),
Subscriptions :: [{Subscription :: subscription(),
SubId :: subId()}]}]}]}}
%| {'error', 'internal-server-error'}
).
delete_node(Nodes) ->
{result, {_, _, Nodes}} = node_flat:delete_node(Nodes),
{result, {[], Nodes}}.
% case node_flat:delete_node(Nodes) of
% {result, {_, _, Nodes}} -> {result, {[], Nodes}};
% Error -> Error
% end.
-spec(subscribe_node/8 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Subscriber :: jidEntity(),
AccessModel :: accessModel(),
SendLast :: atom(),
PresenceSubscription :: boolean(),
RosterGroup :: boolean(),
Options :: [nodeOption()])
-> {'result', {'default',
Subscription :: 'subscribed',
SubId :: subId()}}
| {'result', {'default',
Subscription :: 'subscribed',
SubId :: subId(),
SendLast ::' send_last'}}
| {'result', {'default',
Subscription :: 'pending',
SubId :: subId()}}
| {'error', _} %% TODO add all error cases
).
subscribe_node(NodeIdx, JID, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat:subscribe_node(
NodeId, Sender, Subscriber, AccessModel, SendLast,
NodeIdx, JID, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
case node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
-spec(unsubscribe_node/4 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Subscriber :: jidEntity(),
SubId :: subId())
-> {'result', []} | {'error', _} %% TODO : add all error cases
).
unsubscribe_node(NodeIdx, JID, Subscriber, SubId) ->
case node_flat:unsubscribe_node(NodeIdx, JID, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
node_flat:remove_extra_items(NodeId, MaxItems, ItemIds).
-spec(publish_item/6 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
PublishModel :: atom(), %% TODO : make a generic publishMod() type
MaxItems :: 'unlimited' | integer(),
ItemId :: itemId(),
Payload :: payload())
-> {'result', {'default', 'broadcast', ItemIds :: [] | [itemId()]}}
| {'error', _}
).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
node_flat:delete_item(NodeId, Publisher, PublishModel, ItemId).
publish_item(NodeIdx, JID, PublishModel, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeIdx, JID, PublishModel, MaxItems, ItemId, Payload).
purge_node(NodeId, Owner) ->
node_flat:purge_node(NodeId, Owner).
get_entity_affiliations(_Host, Owner) ->
{_, D, _} = jlib:short_prepd_jid(Owner),
GenKey = jlib:short_prepd_bare_jid(Owner),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = case catch ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
[{nodetree, N}] -> N;
_ -> nodetree_tree
-spec(remove_extra_items/3 ::
(
NodeIdx :: nodeIdx(),
MaxItems :: 'unlimited' | integer(),
ItemsIds :: [ItemId::itemId()])
-> {'result',
{OldItems :: [] | [ItemId::itemId()],
NewItems :: [] | [ItemId::itemId()]}}
).
remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(NodeIdx, MaxItems, ItemIds).
-spec(delete_item/4 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
PublishModel :: atom(),
ItemId :: itemId())
-> {'result', {'default', 'broadcast'}} | {'error', _}
).
delete_item(NodeIdx, JID, PublishModel, ItemId) ->
node_flat:delete_item(NodeIdx, JID, PublishModel, ItemId).
-spec(purge_node/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', {'default', 'broadcast'}} | {'error', 'forbidden'}
).
purge_node(NodeIdx, JID) ->
node_flat:purge_node(NodeIdx, JID).
-spec(get_entity_affiliations/2 ::
(
Host :: binary(),
JID :: jidEntity())
-> {'result', Reply :: [] | [{Node::pubsubNode(), Affiliation::affiliation()}]}
).
get_entity_affiliations(_Host, #jid{node = U, domain = S} = _JID) ->
States = mnesia:match_object(#pubsub_state{id = {_GenKey = {U,S,undefined}, '_'}, _ = '_'}),
NodeTree = case catch ets:lookup(gen_mod:get_module_proc(S, 'config'), 'nodetree') of
[{'nodetree', Tree}] -> Tree;
_ -> 'nodetree_tree'
end,
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {{_, D, _}, _}} = Node -> [{Node, A}|Acc];
Reply = lists:foldl(fun(#pubsub_state{id = {_, NodeIdx}, affiliation = Affiliation}, Acc) ->
case NodeTree:get_node(NodeIdx) of
#pubsub_node{id = {{_, S, _}, _}} = Node -> [{Node, Affiliation}|Acc];
_ -> Acc
end
end, [], States),
{result, Reply}.
get_node_affiliations(NodeId) ->
node_flat:get_node_affiliations(NodeId).
get_affiliation(NodeId, Owner) ->
node_flat:get_affiliation(NodeId, Owner).
-spec(get_node_affiliations/1 ::
(
NodeIdx :: nodeIdx())
-> {'result', [] | [{Entity::fullUsr(), Affiliation::affiliation()}]}
).
set_affiliation(NodeId, Owner, Affiliation) ->
node_flat:set_affiliation(NodeId, Owner, Affiliation).
get_node_affiliations(NodeIdx) ->
node_flat:get_node_affiliations(NodeIdx).
get_entity_subscriptions(_Host, Owner) ->
{U, D, _} = SubKey = jlib:short_prepd_jid(Owner),
GenKey = jlib:short_prepd_bare_jid(Owner),
-spec(get_affiliation/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', Affiliation::affiliation()}
).
get_affiliation(NodeIdx, JID) ->
node_flat:get_affiliation(NodeIdx, JID).
-spec(set_affiliation/3 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Affiliation :: affiliation())
-> 'ok' | {error, 'internal-server-error'}
).
set_affiliation(NodeIdx, JID, Affiliation) ->
node_flat:set_affiliation(NodeIdx, JID, Affiliation).
-spec(get_entity_subscriptions/2 ::
(
Host :: hostPEP(),
JID :: jidEntity())
-> {'result', []
| [{Node :: pubsubNode(),
Subscription :: subscription(),
SubId :: subId(),
Entity :: fullUsr()}]}
).
get_entity_subscriptions(_Host, #jid{node = U, domain = S, resource = R} = _JID) ->
SubKey = {U,S,R},
GenKey = {U,S,undefined},
States = case SubKey of
GenKey -> mnesia:match_object(
#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
#pubsub_state{id = {{U, S, '_'}, '_'}, _ = '_'});
_ -> mnesia:match_object(
#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
#pubsub_state{id = {GenKey, '_'}, _ = '_'})
++ mnesia:match_object(
#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
#pubsub_state{id = {SubKey, '_'}, _ = '_'})
end,
NodeTree = case catch ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
[{nodetree, N}] -> N;
_ -> nodetree_tree
NodeTree = case catch ets:lookup(gen_mod:get_module_proc(S, 'config'), 'nodetree') of
[{nodetree, Tree}] -> Tree;
_ -> 'nodetree_tree'
end,
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {{_, D, _}, _}} = Node ->
lists:foldl(fun({subscribed, SubID}, Acc2) ->
[{Node, subscribed, SubID, J} | Acc2];
({pending, _SubID}, Acc2) ->
[{Node, pending, J} | Acc2];
(S, Acc2) ->
[{Node, S, J} | Acc2]
end, Acc, Ss);
Reply = lists:foldl(fun(#pubsub_state{id = {Entity, NodeIdx}, subscriptions = Subscriptions}, Acc) ->
case NodeTree:get_node(NodeIdx) of
#pubsub_node{id = {{_, S, _}, _}} = Node ->
lists:foldl(fun({subscribed, SubId}, Acc2) ->
[{Node, subscribed, SubId, Entity} | Acc2];
({pending, _SubId}, Acc2) ->
[{Node, pending, Entity} | Acc2];
(Subscription, Acc2) ->
[{Node, Subscription, Entity} | Acc2]
end, Acc, Subscriptions);
_ -> Acc
end
end, [], States),
{result, Reply}.
get_node_subscriptions(NodeId) ->
-spec(get_node_subscriptions/1 ::
(
NodeIdx :: nodeIdx())
-> {'result', []
| [{Entity::fullUsr(), 'none'}]
%| [{Entity::fullUsr(), Subscription::subscription()}] %% still useful case ?
| [{Entity::fullUsr(), Subscription::subscription(), SubId::subId()}]}
).
get_node_subscriptions(NodeIdx) ->
%% note: get_node_subscriptions is used for broadcasting
%% there should not have any subscriptions
%% but that call returns also all subscription to none
%% and this is required for broadcast to occurs
%% DO NOT REMOVE
node_flat:get_node_subscriptions(NodeId).
node_flat:get_node_subscriptions(NodeIdx).
get_subscriptions(NodeId, Owner) ->
node_flat:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
node_flat:set_subscriptions(NodeId, Owner, Subscription, SubId).
-spec(get_subscriptions/2 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity())
-> {'result', Subscriptions :: [] | [{Subscription::subscription(), SubId::subId()}]}
).
get_pending_nodes(Host, Owner) ->
node_flat:get_pending_nodes(Host, Owner).
get_subscriptions(NodeIdx, JID) ->
node_flat:get_subscriptions(NodeIdx, JID).
get_states(NodeId) ->
node_flat:get_states(NodeId).
get_state(NodeId, JID) ->
node_flat:get_state(NodeId, JID).
-spec(set_subscriptions/4 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
Subscription :: subscription(),
SubId :: subId())
-> 'ok'
| {Subscription::subscription(), SubId::subId()}
| {error, _}
).
set_subscriptions(NodeIdx, JID, Subscription, SubId) ->
node_flat:set_subscriptions(NodeIdx, JID, Subscription, SubId).
-spec(get_pending_nodes/2 ::
(
Host :: hostPEP(),
JID :: jidEntity())
-> 'false' | {'value', NodeId::nodeId()}
).
get_pending_nodes(Host, JID) ->
node_flat:get_pending_nodes(Host, JID).
-spec(get_states/1 ::
(
NodeIdx :: nodeIdx())
-> {'result', States :: [] | [State::pubsubState()]}
).
get_states(NodeIdx) ->
node_flat:get_states(NodeIdx).
-spec(get_state/2 ::
(
NodeIdx :: nodeIdx(),
Entity :: fullUsr())
-> State::pubsubState()
).
get_state(NodeIdx, Entity) ->
node_flat:get_state(NodeIdx, Entity).
-spec(set_state/1 ::
(
State :: pubsubState())
-> 'ok' | {'error', 'internal-server-error'}
).
set_state(State) ->
node_flat:set_state(State).
get_items(NodeId, From) ->
node_flat:get_items(NodeId, From).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_flat:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
-spec(get_items/2 ::
(
NodeIdx :: nodeIdx(),
Entity :: fullUsr())
-> {'result', Items :: [] | [Item::pubsubItem()]}
).
get_item(NodeId, ItemId) ->
node_flat:get_item(NodeId, ItemId).
get_items(NodeIdx, Entity) ->
node_flat:get_items(NodeIdx, Entity).
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_flat:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
-spec(get_items/6 ::
(
NodeIdx :: nodeIdx(),
JID :: jidEntity(),
AccessModel :: accessModel(),
PresenceSubscription :: boolean(),
RosterGroup :: boolean(),
SubId :: subId())
-> {'result', Items :: [] | [Item::pubsubItem()]}
| {'error', _}
).
get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_flat:get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
-spec(get_item/2 ::
(
NodeIdx :: nodeIdx(),
ItemId :: itemId())
-> {'result', Item::pubsubItem()} | {'error', 'item-not-found'}
).
get_item(NodeIdx, ItemId) ->
node_flat:get_item(NodeIdx, ItemId).
-spec(get_item/7 ::
(
NodeIdx :: nodeIdx(),
ItemId :: itemId(),
JID :: jidEntity(),
AccessModel :: accessModel(),
PresenceSubscription :: boolean(),
RosterGroup :: boolean(),
SubId :: subId())
-> {'result', Item::pubsubItem()} | {'error', 'item-not-found'}
).
get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_flat:get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
-spec(set_item/1 ::
(
Item :: pubsubItem())
-> 'ok' | {error, 'internal-server-error'}
).
set_item(Item) ->
node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
path_to_node(Path) ->
node_flat:path_to_node(Path).
@ -288,6 +573,12 @@ path_to_node(Path) ->
%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host.
%% Check that the mod_caps module is enabled in that Jabber Host
%% If not, show a warning message in the ejabberd log file.
-spec(complain_if_modcaps_disabled/1 ::
(
ServerHost :: string())
-> 'ok' | true
).
complain_if_modcaps_disabled(ServerHost) ->
Modules = ejabberd_config:get_local_option({modules, ServerHost}),
ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules],

View File

@ -25,12 +25,10 @@
%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
%%TODO : fix SQL requests with nodeid/id and id/idx changes
-module(node_pep_odbc).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("pubsub.hrl").
@ -86,7 +84,6 @@ terminate(Host, ServerHost) ->
options() ->
[{odbc, true},
{node_type, pep},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
@ -163,8 +160,8 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
NodeId, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
case node_flat_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
case node_flat_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.

View File

@ -26,8 +26,6 @@
-module(node_private).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-behaviour(gen_pubsub_node).
@ -133,8 +131,8 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast,
SendLast, PresenceSubscription, RosterGroup,
Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -26,8 +26,6 @@
-module(node_public).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-behaviour(gen_pubsub_node).
@ -82,8 +80,7 @@ terminate(Host, ServerHost) ->
node_flat:terminate(Host, ServerHost).
options() ->
[{node_type, public},
{deliver_payloads, true},
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@ -130,8 +127,8 @@ delete_node(Removed) ->
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_flat:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(NodeId, Sender, Subscriber, SubId).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).

View File

@ -39,7 +39,6 @@
-include("ejabberd.hrl").
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_nodetree).
@ -52,130 +51,265 @@
%%====================================================================
%% API
%%====================================================================
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(Host, ServerHost, Opts) ->
nodetree_tree:init(Host, ServerHost, Opts).
-spec(terminate/2 ::
(
Host :: string(),
ServerHost :: string())
-> 'ok'
).
terminate(Host, ServerHost) ->
nodetree_tree:terminate(Host, ServerHost).
create_node(Key, NodeID, Type, Owner, Options, Parents) ->
OwnerJID = jlib:short_prepd_bare_jid(Owner),
case find_node(Key, NodeID) of
-spec(create_node/6 ::
(
Host :: hostPubsub(),
NodeId :: nodeId(),
Type :: nodeType(),
JID :: jidEntity(),
Options :: [nodeOption()],
ParentNodeIds :: [] | [nodeId()])
-> {'ok', NodeIdx::nodeIdx()} | {'error', _}
).
create_node(Host, NodeId, Type, #jid{node = U, domain = S} = _JID, Options, ParentNodeIds) ->
case find_node(Host, NodeId) of
false ->
ID = pubsub_index:new(node),
N = #pubsub_node{nodeid = oid(Key, NodeID),
id = ID,
NodeIdx = pubsub_index:new(node),
Node = #pubsub_node{id = {Host, NodeId},
idx = NodeIdx,
type = Type,
parents = Parents,
owners = [OwnerJID],
parents = ParentNodeIds,
owners = [_Owner = {U,S,undefined}],
options = Options},
case set_node(N) of
ok -> {ok, ID};
case set_node(Node) of
ok -> {ok, NodeIdx};
Other -> Other
end;
_ ->
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'conflict')}
end.
set_node(#pubsub_node{nodeid = {Key, _},
-spec(set_node/1 ::
(
Node :: pubsubNode())
-> 'ok' | {'error', _}
).
set_node(#pubsub_node{id = {Host, _},
owners = Owners,
options = Options} = Node) ->
Parents = find_opt(collection, ?DEFAULT_PARENTS, Options),
case validate_parentage(Key, Owners, Parents) of
ParentNodeIds = find_opt('collection', ?DEFAULT_PARENTS, Options),
case validate_parentage(Host, Owners, ParentNodeIds) of
true ->
%% Update parents whenever the config changes.
mnesia:write(Node#pubsub_node{parents = Parents});
mnesia:write(Node#pubsub_node{parents = ParentNodeIds});
Other ->
Other
end.
delete_node(Key, NodeID) ->
case find_node(Key, NodeID) of
-spec(delete_node/2 ::
(
Host :: hostPubsub(),
NodeId :: nodeId())
-> [pubsubNode()] | {'error', _}
).
delete_node(Host, NodeId) ->
case find_node(Host, NodeId) of
false ->
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
Node ->
ParentNode ->
%% Find all of N's children, update their configs to
%% remove N from the collection setting.
lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
NewOpts = remove_config_parent(NodeID, Opts),
Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts),
lists:foreach(fun (#pubsub_node{options = Options} = Node) ->
NewOptions = remove_config_parent(NodeId, Options),
Parents = find_opt('collection', ?DEFAULT_PARENTS, NewOptions),
ok = mnesia:write(pubsub_node,
Child#pubsub_node{
Node#pubsub_node{
parents = Parents,
options = NewOpts},
options = NewOptions},
write)
end, get_subnodes(Key, NodeID)),
end, get_subnodes(Host, NodeId)),
%% Remove and return the requested node.
pubsub_index:free(node, Node#pubsub_node.id),
mnesia:delete_object(pubsub_node, Node, write),
[Node]
pubsub_index:free(node, ParentNode#pubsub_node.idx),
mnesia:delete_object(pubsub_node, ParentNode, write),
[ParentNode]
end.
options() ->
nodetree_tree:options().
get_node(Host, NodeID, _From) ->
get_node(Host, NodeID).
-spec(options/0 :: () -> Options::[Option::nodeOption()]).
%Options::[{'virtual_tree', 'false'}])
get_node(Host, NodeID) ->
case find_node(Host, NodeID) of
options() -> nodetree_tree:options().
-spec(get_node/3 ::
(
Host :: hostPubsub(),
NodeId :: nodeId(),
JID :: jidEntity())
-> pubsubNode() | {'error', _}
).
get_node(Host, NodeId, _JID) ->
get_node(Host, NodeId).
-spec(get_node/2 ::
(
Host :: hostPubsub(),
NodeId :: nodeId())
-> pubsubNode() | {'error', _}
).
get_node(Host, NodeId) ->
case find_node(Host, NodeId) of
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
Node -> Node
end.
get_node(NodeId) ->
nodetree_tree:get_node(NodeId).
get_nodes(Key, From) ->
nodetree_tree:get_nodes(Key, From).
-spec(get_node/1 ::
(
NodeIdx :: nodeIdx())
-> pubsubNode() | {'error', 'item-not-found'}
).
get_nodes(Key) ->
nodetree_tree:get_nodes(Key).
get_node(NodeIdx) ->
nodetree_tree:get_node(NodeIdx).
get_parentnodes(Host, NodeID, _From) ->
case find_node(Host, NodeID) of
-spec(get_nodes/2 ::
(
Host :: hostPubsub(),
JID :: jidEntity())
-> Nodes :: [] | [Node::pubsubNode()]
).
get_nodes(Host, JID) ->
nodetree_tree:get_nodes(Host, JID).
-spec(get_nodes/1 ::
(
Host :: hostPubsub())
-> Nodes :: [] | [Node::pubsubNode()]
).
get_nodes(Host) ->
nodetree_tree:get_nodes(Host).
-spec(get_parentnodes/3 ::
(
Host :: hostPubsub(),
NodeId :: nodeId(),
JID :: jidEntity())
-> ParentNodes :: [] | [ParentNode::pubsubNode()]
).
get_parentnodes(Host, NodeId, _JID) ->
case find_node(Host, NodeId) of
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
#pubsub_node{parents = Parents} ->
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N <- mnesia:table(pubsub_node),
Parent <- Parents,
#pubsub_node{parents = ParentNodeIds} ->
Q = qlc:q([Node || #pubsub_node{id = {NHost, NNodeId}} = Node
<- mnesia:table(pubsub_node),
ParentNodeId <- ParentNodeIds,
Host == NHost,
Parent == NNode]),
ParentNodeId == NNodeId]),
qlc:e(Q)
end.
get_parentnodes_tree(Host, NodeID, _From) ->
Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) ->
NID == NNodeID
end,
-spec(get_parentnodes_tree/3 ::
(
Host :: hostPubsub(),
NodeId :: nodeId(),
JID :: jidEntity())
-> [] | [{Depth::integer(), Nodes :: [] | [Node::pubsubNode()]}]
).
get_parentnodes_tree(Host, NodeId, _JID) ->
Pred = fun (Name, #pubsub_node{id = {_, NodeName}}) -> Name == NodeName end,
Tr = fun (#pubsub_node{parents = Parents}) -> Parents end,
traversal_helper(Pred, Tr, Host, [NodeID]).
traversal_helper(Pred, Tr, Host, [NodeId]).
get_subnodes(Host, NodeID, _From) ->
get_subnodes(Host, NodeID).
get_subnodes(Host, <<>>) ->
get_subnodes_helper(Host, <<>>);
get_subnodes(Host, NodeID) ->
case find_node(Host, NodeID) of
-spec(get_subnodes/3 ::
(
ParentNodeHost :: hostPubsub(),
ParentNodeId :: nodeId(),
JID :: jidEntity())
-> [] | [Node::pubsubNode()] | {'error', _}
).
get_subnodes(Host, Node, _JID) ->
get_subnodes(Host, Node).
-spec(get_subnodes/2 ::
(
ParentNodeHost :: hostPubsub(),
ParentNodeId :: nodeId())
-> [] | [Node::pubsubNode()] | {'error', _}
).
get_subnodes(ParentNodeHost, <<>>) ->
get_subnodes_helper(ParentNodeHost, <<>>);
get_subnodes(ParentNodeHost, ParentNodeId) ->
case find_node(ParentNodeHost, ParentNodeId) of
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
_ -> get_subnodes_helper(Host, NodeID)
_ -> get_subnodes_helper(ParentNodeHost, ParentNodeId)
end.
get_subnodes_helper(Host, NodeID) ->
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
parents = Parents} = Node <- mnesia:table(pubsub_node),
Host == NHost,
lists:member(NodeID, Parents)]),
-spec(get_subnodes_helper/2 ::
(
ParentNodeHost :: hostPubsub(),
ParentNodeId :: nodeId())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes_helper(ParentNodeHost, ParentNodeId) ->
Q = qlc:q([Node || #pubsub_node{id = {Host, _},
parents = ParentNodeIds} = Node <- mnesia:table(pubsub_node),
ParentNodeHost == Host,
lists:member(ParentNodeId, ParentNodeIds)]),
qlc:e(Q).
get_subnodes_tree(Host, NodeID, From) ->
Pred = fun (NID, #pubsub_node{parents = Parents}) ->
lists:member(NID, Parents)
-spec(get_subnodes_tree/3 ::
(
ParentNodeHost :: hostPubsub(),
ParentNodeId :: nodeId(),
JID :: jidEntity())
-> [] | [{Depth::integer(), Nodes :: [] | [Node::pubsubNode()]}]
).
get_subnodes_tree(Host, ParentNodeId, JID) ->
Pred = fun(NodeId, #pubsub_node{parents = ParentNodeIds}) ->
lists:member(NodeId, ParentNodeIds)
end,
Tr = fun (#pubsub_node{nodeid = {_, N}}) -> [N] end,
traversal_helper(Pred, Tr, 1, Host, [NodeID],
[{0, [get_node(Host, NodeID, From)]}]).
Tr = fun (#pubsub_node{id = {_, NodeId}}) -> [NodeId] end,
traversal_helper(Pred, Tr, 1, Host, [ParentNodeId],
[{0, [get_node(Host, ParentNodeId, JID)]}]).
%%====================================================================
%% Internal functions
@ -183,9 +317,16 @@ get_subnodes_tree(Host, NodeID, From) ->
oid(Key, Name) -> {Key, Name}.
%% Key = jlib:jid() | host()
%% NodeID = string()
find_node(Key, NodeID) ->
case mnesia:read(pubsub_node, oid(Key, NodeID), read) of
%% Node = string()
-spec(find_node/2 ::
(
Host :: hostPubsub(),
NodeId :: nodeId())
-> pubsubNode() | 'false'
).
find_node(Host, NodeId) ->
case mnesia:read({pubsub_node, {Host, NodeId}}) of
[] -> false;
[Node] -> Node
end.
@ -193,53 +334,109 @@ find_node(Key, NodeID) ->
%% Key = jlib:jid() | host()
%% Default = term()
%% Options = [{Key = atom(), Value = term()}]
-spec(find_opt/3 ::
(
Key :: atom(),
Default :: term(),
Options :: [Option::nodeOption()])
-> Value::term()
).
find_opt(Key, Default, Options) ->
case lists:keysearch(Key, 1, Options) of
{value, {Key, Val}} -> Val;
{value, {Key, Value}} -> Value;
_ -> Default
end.
traversal_helper(Pred, Tr, Host, NodeIDs) ->
traversal_helper(Pred, Tr, 0, Host, NodeIDs, []).
-spec(traversal_helper/4 ::
(
Pred :: fun(),
Tr :: fun(),
ParentNodeHost :: hostPubsub(),
ParentNodeIds :: [] | [ParentNodeId::nodeId()])
-> [] | [{Depth::integer(), Nodes :: [] | [Node::pubsubNode()]}]
).
traversal_helper(Pred, Tr, ParentNodeHost, ParentNodeIds) ->
traversal_helper(Pred, Tr, 0, ParentNodeHost, ParentNodeIds, []).
-spec(traversal_helper/6 ::
(
Pred :: fun(),
Tr :: fun(),
Depth :: integer(),
ParentNodeHost :: hostPubsub(),
ParentNodeIds :: [] | [ParentNodeId::nodeId()],
Acc :: [] | [{Depth::integer(), Nodes :: [] | [Node::pubsubNode()]}])
-> [] | [{Depth::integer(), Nodes :: [] | [Node::pubsubNode()]}]
).
traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) ->
Acc;
traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) ->
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}} = Node <- mnesia:table(pubsub_node),
NodeID <- NodeIDs,
Host == NHost,
Pred(NodeID, Node)]),
traversal_helper(Pred, Tr, Depth, ParentNodeHost, ParentNodeIds, Acc) ->
Q = qlc:q([Node || #pubsub_node{id = {Host, _}} = Node <- mnesia:table(pubsub_node),
ParentNodeId <- ParentNodeIds,
ParentNodeHost == Host,
Pred(ParentNodeId, Node)]),
Nodes = qlc:e(Q),
IDs = lists:flatmap(Tr, Nodes),
traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]).
Ids = lists:flatmap(Tr, Nodes),
traversal_helper(Pred, Tr, Depth + 1, ParentNodeHost, Ids, [{Depth, Nodes} | Acc]).
remove_config_parent(NodeID, Options) ->
remove_config_parent(NodeID, Options, []).
remove_config_parent(_NodeID, [], Acc) ->
-spec(remove_config_parent/2 ::
(
NodeId :: nodeId(),
Options :: [Option::nodeOption()])
-> [Option::nodeOption()]
).
remove_config_parent(NodeId, Options) ->
remove_config_parent(NodeId, Options, []).
-spec(remove_config_parent/3 ::
(
NodeId :: nodeId(),
Options :: [] | [Option::nodeOption()],
Acc :: [Option::nodeOption()])
-> [Option::nodeOption()]
).
remove_config_parent(_NodeId, [], Acc) ->
lists:reverse(Acc);
remove_config_parent(NodeID, [{collection, Parents} | T], Acc) ->
remove_config_parent(NodeID, T,
[{collection, lists:delete(NodeID, Parents)} | Acc]);
remove_config_parent(NodeID, [H | T], Acc) ->
remove_config_parent(NodeID, T, [H | Acc]).
remove_config_parent(NodeId, [{'collection', ParentNodeIds} | Options], Acc) ->
remove_config_parent(NodeId, Options,
[{'collection', lists:delete(NodeId, ParentNodeIds)} | Acc]);
remove_config_parent(NodeId, [Option | Options], Acc) ->
remove_config_parent(NodeId, Options, [Option | Acc]).
validate_parentage(_Key, _Owners, []) ->
-spec(validate_parentage/3 ::
(
Host :: hostPubsub(),
Owners :: [Owner::bareUsr()],
ParentNodeIds :: [] | [ParentNodeId::nodeId()] | [ParentNodeId :: nodeId() | []])
-> 'true' | {'error', _}
).
validate_parentage(_Host, _Owners, []) ->
true;
validate_parentage(Key, Owners, [[] | T]) ->
validate_parentage(Key, Owners, T);
validate_parentage(Key, Owners, [<<>> | T]) ->
validate_parentage(Key, Owners, T);
validate_parentage(Key, Owners, [ParentID | T]) ->
case find_node(Key, ParentID) of
validate_parentage(Host, Owners, [[] | ParentNodeIds]) ->
validate_parentage(Host, Owners, ParentNodeIds);
validate_parentage(Host, Owners, [<<>> | ParentNodeIds]) ->
validate_parentage(Host, Owners, ParentNodeIds);
validate_parentage(Host, Owners, [ParentNodeId | ParentNodeIds]) ->
case find_node(Host, ParentNodeId) of
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item_not_found')};
#pubsub_node{owners = POwners, options = POptions} ->
NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
MutualOwners = [O || O <- Owners, PO <- POwners,
O == PO],
#pubsub_node{owners = ParentNodeOwners, options = Options} ->
NodeType = find_opt('node_type', ?DEFAULT_NODETYPE, Options),
MutualOwners = [Owner || Owner <- Owners, ParentNodeOwner <- ParentNodeOwners,
Owner == ParentNodeOwner],
case {MutualOwners, NodeType} of
{[], _} -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')};
{_, collection} -> validate_parentage(Key, Owners, T);
{_, 'collection'} -> validate_parentage(Host, Owners, ParentNodeIds);
{_, _} -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-allowed')}
end
end.
@ -247,6 +444,12 @@ validate_parentage(Key, Owners, [ParentID | T]) ->
%% @spec (Host) -> jid()
%% Host = host()
%% @doc <p>Generate pubsub service JID.</p>
-spec(service_jid/1 ::
(
Host :: hostPubsub())
-> ServiceJID :: jidContact() | jidComponent() %% should only return jidContact()
).
service_jid(Host) ->
case Host of
{U,S,_} -> exmpp_jid:make(U, S);

View File

@ -37,13 +37,13 @@
-author('christophe.romain@process-one.net').
-include_lib("stdlib/include/qlc.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-behaviour(gen_pubsub_nodetree).
-export([init/3,
-export([
init/3,
terminate/2,
options/0,
set_node/1,
@ -61,6 +61,7 @@
]).
%% ================
%% API definition
%% ================
@ -74,182 +75,309 @@
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(_Host, _ServerHost, _Opts) ->
mnesia:create_table(pubsub_node,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]),
mnesia:add_table_index(pubsub_node, id),
mnesia:add_table_index(pubsub_node, idx),
NodesFields = record_info(fields, pubsub_node),
case mnesia:table_info(pubsub_node, attributes) of
NodesFields -> ok;
_ ->
ok
%% mnesia:transform_table(pubsub_state, ignore, StatesFields)
_ -> ok %% mnesia:transform_table(pubsub_state, ignore, StatesFields)
end,
ok.
-spec(terminate/2 ::
(
Host :: string(),
ServerHost :: string())
-> 'ok'
).
terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc Returns the default pubsub node tree options.
options() ->
[{virtual_tree, false}].
-spec(options/0 :: () -> Options::[{'virtual_tree', 'false'}]).
options() -> [{'virtual_tree', 'false'}].
%% @spec (NodeRecord) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node()
set_node(Record) when is_record(Record, pubsub_node) ->
mnesia:write(Record);
set_node(_) ->
{error, 'internal-server-error'}.
%(
% Node :: pubsubNode() -> 'ok' | {'error', Reason::_};
% Node :: any() -> {'error', 'internal-server-error'}
%).
%% -spec breaks compilation
set_node(#pubsub_node{} = Node) -> mnesia:write(Node);
set_node(_) -> {error, 'internal-server-error'}.
%% @spec (Host, Node, From) -> pubsubNode() | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
case catch mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) -> Record;
%% Node = node()
-spec(get_node/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> pubsubNode() | {error, 'item-not-found'} | any()
).
get_node(Host, NodeId, _JID) ->
get_node(Host, NodeId).
-spec(get_node/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId())
-> pubsubNode() | {error, 'item-not-found'} | any()
).
get_node(Host, NodeId) ->
case catch mnesia:read({pubsub_node, {Host, NodeId}}) of
[#pubsub_node{} = Node] -> Node;
[] -> {error, 'item-not-found'};
Error -> Error
end.
get_node(NodeId) ->
case catch mnesia:index_read(pubsub_node, NodeId, #pubsub_node.id) of
[Record] when is_record(Record, pubsub_node) -> Record;
-spec(get_node/1 ::
(
NodeIdx :: nodeIdx())
-> pubsubNode() | {'error', 'item-not-found'}
).
get_node(NodeIdx) ->
case catch mnesia:index_read(pubsub_node, NodeIdx, #pubsub_node.idx) of
[#pubsub_node{} = Node] -> Node;
[] -> {error, 'item-not-found'};
Error -> Error
end.
%% @spec (Host, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
get_nodes(Host, _From) ->
%% Host = mod_pubsub:host() | ljid()
-spec(get_nodes/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
JID :: jidEntity())
-> Nodes :: [] | [Node::pubsubNode()]
).
get_nodes(Host, _JID) ->
get_nodes(Host).
-spec(get_nodes/1 ::
(
Host :: host()) % hostPubsub | hostPEP()
-> Nodes :: [] | [Node::pubsubNode()]
).
get_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
mnesia:match_object(#pubsub_node{id = {Host, '_'}, _ = '_'}).
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
%% From = ljid()
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
get_parentnodes(_Host, _Node, _From) ->
-spec(get_parentnodes/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> ParentNodes :: []
).
get_parentnodes(_Host, _NodeId, _JID) ->
[].
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
%% From = ljid()
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return a list
%% containing just this node.</p>
get_parentnodes_tree(Host, Node, From) ->
case get_node(Host, Node, From) of
N when is_record(N, pubsub_node) -> [{0, [N]}];
-spec(get_parentnodes_tree/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> ParentNodesTree :: [] | [{0, [ParentNode::pubsubNode()]}]
).
get_parentnodes_tree(Host, NodeId, JID) ->
case get_node(Host, NodeId, JID) of
#pubsub_node{} = ParentNode -> [{0, [ParentNode]}];
_Error -> []
end.
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, <<>>) ->
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
parents = Parents} = N <- mnesia:table(pubsub_node),
Host == NHost,
Parents == []]),
%% Node = node()
%% From = ljid()
-spec(get_subnodes/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes(Host, NodeId, _JID) ->
get_subnodes(Host, NodeId).
-spec(get_subnodes/2 ::
(
ParentNodeHost :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes(ParentNodeHost, <<>>) ->
Q = qlc:q(
[Node
|| #pubsub_node{id = {Host, _}, parents = ParentNodeIds} = Node
<- mnesia:table(pubsub_node),
ParentNodeHost == Host,
ParentNodeIds == []]),
qlc:e(Q);
get_subnodes(Host, Node) ->
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
parents = Parents} = N <- mnesia:table(pubsub_node),
Host == NHost,
lists:member(Node, Parents)]),
get_subnodes(ParentNodeHost, ParentNodeId) ->
Q = qlc:q(
[Node
|| #pubsub_node{id = {Host, _}, parents = ParentNodeIds} = Node
<- mnesia:table(pubsub_node),
ParentNodeHost == Host,
lists:member(ParentNodeId, ParentNodeIds)]),
qlc:e(Q).
%% @spec (Host, Index, From) -> [pubsubNodeIdx()] | {error, Reason}
%% @spec (Host, Index, From) -> [nodeidx()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
case get_node(Host, Node) of
{error, _} ->
[];
Rec ->
BasePlugin = list_to_atom("node_"++Rec#pubsub_node.type),
BasePath = BasePlugin:node_to_path(Node),
mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}} = R, Acc) ->
Plugin = list_to_atom("node_"++R#pubsub_node.type),
Path = Plugin:node_to_path(N),
case lists:prefix(BasePath, Path) and (H == Host) of
true -> [R | Acc];
%% Node = node()
%% From = ljid()
-spec(get_subnodes_tree/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId(),
JID :: jidEntity())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes_tree(Host, ParentNodeId, _JID) ->
get_subnodes_tree(Host, ParentNodeId).
-spec(get_subnodes_tree/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes_tree(ParentNodeHost, ParentNodeId) ->
case get_node(ParentNodeHost, ParentNodeId) of
{error, _} -> [];
#pubsub_node{type = ParentNodeType} = _ParentNode ->
BasePlugin = list_to_atom("node_"++ParentNodeType),
BasePath = BasePlugin:node_to_path(ParentNodeId),
mnesia:foldl(fun
(#pubsub_node{id = {Host, NodeId}, type = Type} = Node, Acc) ->
Plugin = list_to_atom("node_"++Type),
Path = Plugin:node_to_path(NodeId),
case lists:prefix(BasePath, Path) and (Host == ParentNodeHost) of
true -> [Node | Acc];
false -> Acc
end
end, [], pubsub_node)
end.
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% NodeType = mod_pubsub:nodeType()
%% Owner = mod_pubsub:jid()
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
%% NodeType = nodeType()
%% Owner = ljid()
%% Options = list()
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jlib:short_prepd_bare_jid(Owner),
case mnesia:read({pubsub_node, {Host, Node}}) of
-spec(create_node/6 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
Type :: nodeType(),
JID :: jidEntity(),
Options :: [nodeOption()],
ParentNodeIds :: [] | [nodeId()])
-> {'ok', NodeIdx::nodeIdx()}
| {'error', 'conflict' | 'forbidden'}
).
create_node(Host, NodeId, Type, #jid{node = U, domain = S} = _JID, Options, ParentNodeIds) ->
Owner = {U,S,undefined},
case mnesia:read({pubsub_node, {Host, NodeId}}) of
[] ->
ParentExists =
case Host of
{_U, _S, _R} ->
%% This is special case for PEP handling
%% PEP does not uses hierarchy
true;
ParentExists = case Host of
%% This is special case for PEP handling, PEP does not uses hierarchy
{_, _, _} -> true;
_ ->
case Parents of
case ParentNodeIds of
[] -> true;
[Parent | _] ->
BHost = list_to_binary(Host),
case mnesia:read({pubsub_node, {Host, Parent}}) of
[#pubsub_node{owners = [{undefined, BHost, undefined}]}] -> true;
[#pubsub_node{owners = Owners}] -> lists:member(BJID, Owners);
[ParentNodeId | _] ->
case mnesia:read({pubsub_node, {Host, ParentNodeId}}) of
[#pubsub_node{owners = [{undefined, Host, undefined}]}] -> true;
[#pubsub_node{owners = Owners}] -> lists:member(Owner, Owners);
_ -> false
end;
_ ->
false
_ -> false
end
end,
case ParentExists of
true ->
NodeId = pubsub_index:new(node),
mnesia:write(#pubsub_node{nodeid = {Host, Node},
id = NodeId,
parents = Parents,
NodeIdx = pubsub_index:new(node),
mnesia:write(
#pubsub_node{id = {Host, NodeId},
idx = NodeIdx,
parents = ParentNodeIds,
type = Type,
owners = [BJID],
owners = [Owner],
options = Options}),
{ok, NodeId};
false ->
%% Requesting entity is prohibited from creating nodes
{ok, NodeIdx};
false -> %% Requesting entity is prohibited from creating nodes
{error, 'forbidden'}
end;
_ ->
%% NodeID already exists
_ -> %% Node already exists
{error, 'conflict'}
end.
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
delete_node(Host, Node) ->
Removed = get_subnodes_tree(Host, Node),
lists:foreach(fun(#pubsub_node{nodeid = {_, N}, id = I}) ->
pubsub_index:free(node, I),
mnesia:delete({pubsub_node, {Host, N}})
end, Removed),
Removed.
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
-spec(delete_node/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId())
-> DeletedNodes :: [] | [Node::pubsubNode()]
).
delete_node(Host, ParentNodeId) ->
DeletedNodes = get_subnodes_tree(Host, ParentNodeId),
lists:foreach(fun(#pubsub_node{id = {_, NodeId}, idx = NodeIdx}) ->
pubsub_index:free(node, NodeIdx),
mnesia:delete({pubsub_node, {Host, NodeId}})
end, DeletedNodes),
DeletedNodes.

View File

@ -32,12 +32,10 @@
%%% 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(nodetree_tree_odbc).
-author('christophe.romain@process-one.net').
-include_lib("stdlib/include/qlc.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
@ -84,16 +82,16 @@ terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% Option = nodetreeOption()
%% @doc Returns the default pubsub node tree options.
options() ->
[{virtual_tree, false},
{odbc, true}].
%% @spec (Host, Node, From) -> pubsubNode() | {error, Reason}
%% @spec (Host, Node, From) -> node() | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Node = node()
get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
@ -111,22 +109,22 @@ get_node(Host, Node) ->
_ ->
{error, 'item_not_found'}
end.
get_node(NodeId) ->
get_node(Nidx) ->
case catch ejabberd_odbc:sql_query_t(
["select host, node, parent, type "
"from pubsub_node "
"where nodeid='", NodeId, "';"])
"where nodeid='", Nidx, "';"])
of
{selected, ["host", "node", "parent", "type"], [{Host, Node, Parent, Type}]} ->
raw_to_node(Host, {Node, Parent, Type, NodeId});
raw_to_node(Host, {Node, Parent, Type, Nidx});
{'EXIT', _Reason} ->
{error, 'internal_server_error'};
_ ->
{error, 'item_not_found'}
end.
%% @spec (Host, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% @spec (Host, From) -> [node()] | {error, Reason}
%% Host = mod_pubsub:host() | ljid()
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
@ -143,21 +141,21 @@ get_nodes(Host) ->
end.
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Depth = integer()
%% Record = pubsubNode()
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
%% From = ljid()
%% Depth = int()
%% Record = mod_pubsub:pubsub_node()
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
get_parentnodes(_Host, _Node, _From) ->
[].
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Depth = integer()
%% Record = pubsubNode()
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
%% From = ljid()
%% Depth = int()
%% Record = mod_pubsub:pubsub_node()
%% @doc <p>Default node tree does not handle parents, return a list
%% containing just this node.</p>
get_parentnodes_tree(Host, Node, From) ->
@ -182,10 +180,10 @@ get_subnodes(Host, Node) ->
[]
end.
%% @spec (Host, Index, From) -> [pubsubNodeIdx()] | {error, Reason}
%% @spec (Host, Index, From) -> [nodeidx()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Node = node()
%% From = ljid()
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
@ -203,10 +201,10 @@ get_subnodes_tree(Host, Node) ->
end.
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% NodeType = mod_pubsub:nodeType()
%% Owner = mod_pubsub:jid()
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
%% NodeType = nodeType()
%% Owner = ljid()
%% Options = list()
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jlib:short_prepd_bare_jid(Owner),
@ -223,9 +221,9 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
true;
[Parent | _] ->
case nodeid(Host, Parent) of
{result, PNodeId} ->
{result, Pidx} ->
BHost = list_to_binary(Host),
case nodeowners(PNodeId) of
case nodeowners(Pidx) of
[{undefined, BHost, undefined}] -> true;
Owners -> lists:member(BJID, Owners)
end;
@ -238,12 +236,11 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
end,
case ParentExists of
true ->
case set_node(#pubsub_node{
nodeid={Host, Node},
case set_node(#pubsub_node{id={Host, Node},
parents=Parents,
type=Type,
options=Options}) of
{result, NodeId} -> {ok, NodeId};
{result, Nidx} -> {ok, Nidx};
Other -> Other
end;
false ->
@ -251,16 +248,16 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
{error, 'forbidden'}
end;
{result, _} ->
%% NodeID already exists
%% Node already exists
{error, 'conflict'};
Error ->
Error
end.
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% @spec (Host, Node) -> [node()]
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
delete_node(Host, Node) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
@ -273,11 +270,11 @@ delete_node(Host, Node) ->
%% helpers
raw_to_node(Host, {Node, Parent, Type, NodeId}) ->
raw_to_node(Host, {Node, Parent, Type, Nidx}) ->
Options = case catch ejabberd_odbc:sql_query_t(
["select name,val "
"from pubsub_node_option "
"where nodeid='", NodeId, "';"])
"where nodeid='", Nidx, "';"])
of
{selected, ["name", "val"], ROptions} ->
DbOpts = lists:map(fun({Key, Value}) ->
@ -295,16 +292,16 @@ raw_to_node(Host, {Node, Parent, Type, NodeId}) ->
[]
end,
#pubsub_node{
nodeid = {Host, ?PUBSUB:string_to_node(Node)},
id = {Host, ?PUBSUB:string_to_node(Node)},
parents = [?PUBSUB:string_to_node(Parent)],
id = NodeId,
idx = Nidx,
type = Type,
options = Options}.
%% @spec (NodeRecord) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node()
set_node(Record) ->
{Host, Node} = Record#pubsub_node.nodeid,
{Host, Node} = Record#pubsub_node.id,
Parent = case Record#pubsub_node.parents of
[] -> <<>>;
[First | _] -> First
@ -313,29 +310,29 @@ set_node(Record) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
P = ?PUBSUB:escape(?PUBSUB:node_to_string(Parent)),
NodeId = case nodeid(Host, Node) of
{result, OldNodeId} ->
Nidx = case nodeid(Host, Node) of
{result, OldNidx} ->
catch ejabberd_odbc:sql_query_t(
["delete from pubsub_node_option "
"where nodeid='", OldNodeId, "';"]),
"where nodeid='", OldNidx, "';"]),
catch ejabberd_odbc:sql_query_t(
["update pubsub_node "
"set host='", H, "' "
"node='", N, "' "
"parent='", P, "' "
"type='", Type, "' "
"where nodeid='", OldNodeId, "';"]),
OldNodeId;
"where nodeid='", OldNidx, "';"]),
OldNidx;
_ ->
catch ejabberd_odbc:sql_query_t(
["insert into pubsub_node(host, node, parent, type) "
"values('", H, "', '", N, "', '", P, "', '", Type, "');"]),
case nodeid(Host, Node) of
{result, NewNodeId} -> NewNodeId;
_ -> none % this should not happen
{result, NewNidx} -> NewNidx;
_ -> none % this should not happe
end
end,
case NodeId of
case Nidx of
none ->
{error, 'internal_server_error'};
_ ->
@ -344,9 +341,9 @@ set_node(Record) ->
SValue = ?PUBSUB:escape(lists:flatten(io_lib:fwrite("~p",[Value]))),
catch ejabberd_odbc:sql_query_t(
["insert into pubsub_node_option(nodeid, name, val) "
"values('", NodeId, "', '", SKey, "', '", SValue, "');"])
"values('", Nidx, "', '", SKey, "', '", SValue, "');"])
end, Record#pubsub_node.options),
{result, NodeId}
{result, Nidx}
end.
nodeid(Host, Node) ->
@ -357,16 +354,16 @@ nodeid(Host, Node) ->
"from pubsub_node "
"where host='", H, "' and node='", N, "';"])
of
{selected, ["nodeid"], [{NodeId}]} ->
{result, NodeId};
{selected, ["nodeid"], [{Nidx}]} ->
{result, Nidx};
{'EXIT', _Reason} ->
{error, 'internal_server_error'};
_ ->
{error, 'item_not_found'}
end.
nodeowners(NodeId) ->
{result, Res} = node_flat_odbc:get_node_affiliations(NodeId),
nodeowners(Nidx) ->
{result, Res} = node_flat_odbc:get_node_affiliations(Nidx),
lists:foldl(fun({LJID, owner}, Acc) -> [LJID|Acc];
(_, Acc) -> Acc
end, [], Res).

View File

@ -33,8 +33,6 @@
-module(nodetree_virtual).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-behaviour(gen_pubsub_nodetree).
@ -86,18 +84,19 @@ set_node(_NodeRecord) ->
%% @spec (Host, Node, From) -> pubsubNode()
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Node = node()
%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
%% as existing. Node record contains default values.</p>
get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
#pubsub_node{nodeid = {Host, Node}, id = {Host, Node}, owners = [{undefined, list_to_binary(Host), undefined}]}.
%% TODO : to fix idx
#pubsub_node{id = {Host, Node}, idx = {Host, Node}, owners = [{undefined, list_to_binary(Host), undefined}]}.
get_node({Host, _} = NodeId) ->
#pubsub_node{nodeid = NodeId, id = NodeId, owners = [{undefined, list_to_binary(Host), undefined}]}.
#pubsub_node{id = NodeId, idx = NodeId, owners = [{undefined, list_to_binary(Host), undefined}]}.
%% @spec (Host, From) -> [pubsubNode()]
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Host = mod_pubsub:host() | ljid()
%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
%% as existing. Nodes list can not be determined.</p>
get_nodes(Host, _From) ->
@ -107,8 +106,8 @@ get_nodes(_Host) ->
%% @spec (Host, Node, From) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Node = node()
%% From = ljid()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
@ -117,7 +116,7 @@ get_subnodes(_Host, _Node) ->
%% @spec (Host, Index, From) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Node = node()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
@ -126,9 +125,9 @@ get_subnodes_tree(_Host, _Node) ->
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Type = mod_pubsub:nodeType()
%% Owner = mod_pubsub:jid()
%% Node = node()
%% Type = nodeType()
%% Owner = ljid()
%% Options = list()
%% @doc <p>No node record is stored on database. Any valid node
%% is considered as already created.</p>
@ -143,7 +142,7 @@ create_node(Host, Node, _Type, Owner, _Options, _Parents) ->
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Node = node()
%% @doc <p>Virtual node tree does not handle parent/child.
%% node deletion just affects the corresponding node.</p>
delete_node(Host, Node) ->

View File

@ -21,6 +21,11 @@
%%% This file contains pubsub types definition.
%%% ====================================================================
-include_lib("exmpp/include/exmpp.hrl").
-include_lib("exmpp/include/exmpp_jid.hrl").
%% -------------------------------
%% Pubsub constants
-define(ERR_EXTENDED(E,C), mod_pubsub:extended_error(E,C)).
@ -32,19 +37,176 @@
%% Would be nice to have it configurable.
-define(MAX_PAYLOAD_SIZE, 60000).
%% -------------------------------
%% ------------
%% Pubsub types
%% ------------
%%% @type host() = string().
%%% <p><tt>host</tt> is the name of the PubSub service. For example, it can be
%%% <tt>"pubsub.localhost"</tt>.</p>
%%% @type hostPubsub() = binary().
%%%
%%% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be
%%% <tt>pubsub.localhost</tt>.</p>
-type(hostPubsub() :: binary()).
%%% @type hostPEP() = {User, Server, Resource}
%%% User = binary()
%%% Server = binary()
%%% Resource = undefined.
-type(hostPEP() :: {User::binary(), Server::binary, Resource::undefined}).
%%% @type host() = hostPubsub() | hostPEP().
-type(host() :: hostPubsub() | hostPEP()).
%% TODO : move upper in exmpp
%%% @type nodeId() = binary().
%%%
%%% <p>A <tt>nodeId</tt> is the name of a Node. It can be anything and may represent
%%% some hierarchical tree depending of the node type.
%%% For example:
%%% /home/localhost/user/node
%%% princely_musings
%%% http://jabber.org/protocol/tune
%%% My-Own_Node</p>
-type(nodeId() :: binary()).
%%% @type itemId() = binary().
%%%
%%% <p>An <tt>itemId</tt> is the name of an Item. It can be anything.
%%% For example:
%%% 38964
%%% my-tune
%%% FD6SBE6a27d</p>
-type(itemId() :: binary()).
%%% @type subId() = binary().
-type(subId() :: binary()).
%%% @type nodeType() = string().
%%%
%%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
%%% plugin to use to manage a given node. For example, it can be
%%% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p>
-type(nodeType() :: string()).
%%% @type ljid() = {User, Server, Resource}
%%% User = undefined | binary()
%%% Server = binary()
%%% Resource = undefined | binary().
-type(ljid() :: {User::binary(), Server::binary(), Resource::binary()}).
%% TODO : move upper in exmpp
%%% @type jidComponent() = {jid, Raw, Node, Domain, Resource}
%%% Raw = binary()
%%% Node = undefined
%%% Domain = binary()
%%% Resource = undefined.
-type(jidComponent() ::
#jid{raw::binary(), node::undefined, domain::binary(), resource::undefined}).
%% TODO : move upper in exmpp
%%% @type jidContact() = {jid, Raw, Node, Domain, Resource}
%%% Raw = binary()
%%% Node = binary()
%%% Domain = binary()
%%% Resource = undefined.
-type(jidContact() ::
#jid{raw::binary(), node::binary(), domain::binary(), resource::undefined}).
%% TODO : move upper in exmpp
%%% @type jidEntity() = {jid, Raw, Node, Domain, Resource}
%%% Raw = binary()
%%% Node = undefined | binary()
%%% Domain = binary()
%%% Resource = undefined | binary().
-type(jidEntity() ::
%% Contact bare JID
#jid{raw::binary(), node::binary(), domain::binary(), resource::undefined} |
%% Contact full JID
#jid{raw::binary(), node::binary(), domain::binary(), resource::binary()} |
%% Component bare JID
#jid{raw::binary(), node::undefined, domain::binary(), resource::undefined} |
%% Component full JID
#jid{raw::binary(), node::undefined, domain::binary(), resource::binary()}).
%%% @type bareUsr() = {User, Server, Resource}
%%% User = undefined | binary()
%%% Server = binary()
%%% Resource = undefined.
-type(bareUsr() :: {User::binary(), Server::binary(), Resource::undefined}
| {User::undefined, Server::binary(), Resource::undefined}).
%%% @type fullUsr() = {User, Server, Resource}
%%% User = undefined | binary()
%%% Server = binary()
%%% Resource = undefined | binary().
-type(fullUsr() :: {User::binary(), Server::binary(), Resource::undefined}
| {User::binary(), Server::binary(), Resource::binary()}
| {User::undefined, Server::binary(), Resource::undefined}
| {User::undefined, Server::binary(), Resource::binary()}).
%%% @type nodeIdx() = integer().
-type(nodeIdx() :: integer()).
%%% @type now() = {Megaseconds, Seconds, Microseconds}
%%% Megaseconds = integer()
%%% Seconds = integer()
%%% Microseconds = integer().
-type(now() :: {Megaseconds::integer(), Seconds::integer(), Microseconds::integer()}).
%%% @type affiliation() = 'none' | 'owner' | 'publisher' |'publish-only' | 'member' | 'outcast'.
-type(affiliation() :: 'none' | 'owner' | 'publisher' |'publish-only' | 'member' | 'outcast').
%%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
-type(subscription() :: 'none' | 'pending' | 'unconfigured' | 'subscribed').
%%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'.
-type(accessModel() :: 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist').
%%% @type payload() = [] | [#xmlel{}].
-type(payload() :: [] | [#xmlel{}]).
%%% @type pubsubNode() = [string()].
%%% <p>A node is defined by a list of its ancestors. The last element is the name
%%% of the current node. For example:
%%% ```["home", "localhost", "cromain", "node1"]'''</p>
%%% @type stanzaError() = #xmlel{}.
%%%
%%% Example:
%%% ```#xmlel{name = 'error'
%%% ns = ?NS_STANZAS,
@ -62,7 +224,11 @@
%%% }
%%% ]}'''
-type(stanzaError() :: #xmlel{}).
%%% @type pubsubIQResponse() = #xmlel{}.
%%%
%%% Example:
%%% ```#xmlel{name = 'pubsub',
%%% ns = ?NS_PUBSUB,
@ -73,89 +239,145 @@
%%% ]
%%% }'''
%%% @type nodeOption() = {Option::atom(), Value::term()}.
-type(pubsubIQResponse() :: #xmlel{}).
%%% @type features() = [Feature]
%%% Feature = string().
-type(features() :: [Feature::string()]).
%%% @type nodeOption() = {Option, Value}.
%%% Option = atom()
%%% Value = term().
%%%
%%% Example:
%%% ```{deliver_payloads, true}'''
%%% @type nodeType() = string().
%%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
%%% plugin to use to manage a given node. For example, it can be
%%% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p>
-type(nodeOption() :: {Option::atom(), Value::term()}).
%%% @type jid() = #jid{
%%% user = string(),
%%% server = string(),
%%% resource = string(),
%%% luser = string(),
%%% lserver = string(),
%%% lresource = string()}.
%%% @type ljid() = {User::string(), Server::string(), Resource::string()}.
%%% @type subOption() = {Option, Value}.
%%% Option = atom()
%%% Value = term().
%%% @type affiliation() = none | owner | publisher | outcast.
%%% @type subscription() = none | pending | unconfigured | subscribed.
-type(subOption() :: {Option::atom(), Value::term()}).
%%% internal pubsub index table
-record(pubsub_index, {index, last, free}).
%%% @type pubsubNode() = #pubsub_node{
%%% nodeid = {Host::host(), Node::pubsubNode()},
%%% parentid = Node::pubsubNode(),
%%% nodeidx = int(),
%%% type = nodeType(),
%%% options = [nodeOption()]}.
%%% @type pubsubIndex() = {pubsub_index, Index, Last, Free}.
%%% Index = atom()
%%% Last = nodeIdx()
%%% Free = [nodeIdx()].
%%%
%%% Internal pubsub index table.
-record(pubsub_index,
{
index :: atom(),
last :: integer(),
free :: [integer()]
}).
-type(pubsubIndex() :: #pubsub_index{}).
%%% @type pubsubNode() = {pubsub_node, Id, Idx, Parents, Type, Owners, Options}
%%% Id = {host(), nodeId()}
%%% Idx = nodeIdx()
%%% Parents = [nodeId()]
%%% Type = nodeType()
%%% Owners = [bareUsr()]
%%% Options = [nodeOption()].
%%%
%%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
%%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
%%% <p><tt>nodeidx</tt> can be anything you want.</p>
-record(pubsub_node, {nodeid,
id,
parents = [],
type = "flat",
owners = [],
options = []
%%% <p>The <tt>parents</tt> and <tt>type</tt> fields are indexed.</p>
%%% <p><tt>idx</tt> is an integer.</p>
-record(pubsub_node,
{
id :: {host(), nodeId()},
idx :: nodeIdx(),
parents = [] :: [nodeId()],
type = "flat" :: nodeType(),
owners = [] :: [bareUsr()],
options = [] :: [nodeOption()]
}).
%%% @type pubsubState() = #pubsub_state{
%%% stateid = {ljid(), nodeidx()},
%%% items = [ItemId::string()],
%%% affiliation = affiliation(),
%%% subscriptions = [subscription()]}.
-type(pubsubNode() :: #pubsub_node{}).
%%% @type pubsubState() = {pubsub_state, Id, Items, Affiliation, Subscriptions}
%%% Id = {fullUsr(), nodeIdx()}
%%% Items = [itemId()]
%%% Affiliation = affiliation()
%%% Subscriptions = [{subscription(), subId()}].
%%%
%%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
%%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
-record(pubsub_state, {stateid,
items = [],
affiliation = none,
subscriptions = []
}).
%%% @type pubsubItem() = #pubsub_item{
%%% itemid = {ItemId::string(), nodeidx()},
%%% creation = {now(), ljid()},
%%% modification = {now(), ljid()},
%%% payload = XMLContent::string()}.
%%% <p>This is the format of the <tt>published items</tt> table. The type of the
%%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
-record(pubsub_item, {itemid,
creation = {unknown,unknown},
modification = {unknown,unknown},
payload = []
-record(pubsub_state,
{
id :: {fullUsr(), nodeIdx()},
items = [] :: [itemId()],
affiliation = 'none' :: affiliation(),
subscriptions = [] :: [{subscription(), subId()}]
}).
%% @type pubsubSubscription() = #pubsub_subscription{
%% subid = string(),
%% state_key = {ljid(), pubsubNodeId()},
%% options = [{atom(), term()}]
%% }.
-type(pubsubState() :: #pubsub_state{}).
%%% @type pubsubItem() = {pubsub_item, Id, Creation, Modification, Payload}
%%% Id = {itemId(), nodeIdx()}
%%% Creation = {now(), bareUsr()}
%%% Modification = {now(), fullUsr()}
%%% Payload = payload().
%%%
%%% <p>This is the format of the <tt>published items</tt> table. The type of the
%%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
-record(pubsub_item,
{
id :: {itemId(), nodeIdx()},
creation = {unknown,unknown} :: {now(), bareUsr()},
modification = {unknown,unknown} :: {now(), fullUsr()},
payload = [] :: payload()
}).
-type(pubsubItem() :: #pubsub_item{}).
%%% @type pubsubSubscription() = {pubsub_subscription, SubId, Options}
%%% SubId = subId()
%%% Options = [nodeOption()].
%%%
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
-record(pubsub_subscription, {subid, options}).
%% @type pubsubLastItem() = #pubsub_last_item{
%% nodeid = nodeidx(),
%% itemid = string(),
%% creation = {now(), ljid()},
%% payload = XMLContent::string()}.
-record(pubsub_subscription,
{
subid :: subId(),
options :: [subOption()]
}).
-type(pubsubSubscription() :: #pubsub_subscription{}).
%%% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload}
%%% NodeId = nodeIdx()
%%% ItemId = itemId()
%%% Creation = {now(), bareUsr()}
%%% Payload = payload().
%%%
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
%% for every node</p>
-record(pubsub_last_item, {nodeid, itemid, creation, payload}).
-record(pubsub_last_item,
{
nodeid :: nodeIdx(),
itemid :: itemId(),
creation :: {now(), bareUsr()},
payload :: payload()
}).
-type(pubsubLastItem() :: #pubsub_last_item{}).

View File

@ -32,27 +32,27 @@
%% Those -spec lines produce errors in old Erlang versions.
%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher.
-spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound.
read_subscription(SubID) ->
-spec read_subscription(SubId :: string()) -> {ok, #pubsub_subscription{}} | notfound.
read_subscription(SubId) ->
case ejabberd_odbc:sql_query_t(
["select opt_name, opt_value "
"from pubsub_subscription_opt "
"where subid = '", ejabberd_odbc:escape(SubID), "'"]) of
"where subid = '", ejabberd_odbc:escape(SubId), "'"]) of
{selected, ["opt_name", "opt_value"], []} ->
notfound;
{selected, ["opt_name", "opt_value"], Options} ->
{ok, #pubsub_subscription{subid = SubID,
{ok, #pubsub_subscription{subid = SubId,
options = lists:map(fun subscription_opt_from_odbc/1, Options)}}
end.
-spec delete_subscription(SubID :: string()) -> ok.
delete_subscription(SubID) ->
-spec delete_subscription(SubId :: string()) -> ok.
delete_subscription(SubId) ->
ejabberd_odbc:sql_query_t(["delete from pubsub_subscription_opt "
"where subid = '", ejabberd_odbc:escape(SubID), "'"]),
"where subid = '", ejabberd_odbc:escape(SubId), "'"]),
ok.

View File

@ -31,35 +31,58 @@
-include("pubsub.hrl").
-export([init/3, new/1, free/2]).
-export([
init/3,
new/1,
free/2
]).
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(_Host, _ServerHost, _Opts) ->
mnesia:create_table(pubsub_index,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_index)}]).
-spec(new/1 ::
(
Index::atom())
-> Idx::integer()
).
new(Index) ->
case mnesia:read({pubsub_index, Index}) of
[I] ->
case I#pubsub_index.free of
[] ->
Id = I#pubsub_index.last + 1,
mnesia:write(I#pubsub_index{last = Id}),
Id;
[Id|Free] ->
mnesia:write(I#pubsub_index{free = Free}),
Id
end;
[#pubsub_index{free = [], last = Last} = PubsubIndex] ->
Idx = Last + 1,
mnesia:write(PubsubIndex#pubsub_index{last = Idx}),
Idx;
[#pubsub_index{free = [Idx|Free]} = PubsubIndex] ->
mnesia:write(PubsubIndex#pubsub_index{free = Free}),
Idx;
_ ->
mnesia:write(#pubsub_index{index = Index, last = 1, free = []}),
1
end.
free(Index, Id) ->
-spec(free/2 ::
(
Index :: atom(),
Idx :: integer())
-> 'ok'
).
free(Index, Idx) ->
case mnesia:read({pubsub_index, Index}) of
[I] ->
Free = I#pubsub_index.free,
mnesia:write(I#pubsub_index{free = [Id|Free]});
_ ->
ok
[#pubsub_index{free = Free} = PubsubIndex] ->
mnesia:write(PubsubIndex#pubsub_index{free = [Idx|Free]});
_ -> ok
end.

View File

@ -1,5 +1,5 @@
--- mod_pubsub.erl 2010-08-04 18:28:18.000000000 +0200
+++ mod_pubsub_odbc.erl 2010-08-04 18:29:41.000000000 +0200
--- mod_pubsub.erl 2010-09-23 10:43:43.000000000 +0200
+++ mod_pubsub_odbc.erl 2010-09-29 11:46:22.000000000 +0200
@@ -42,7 +42,7 @@
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
%%% XEP-0060 section 12.18.
@ -9,7 +9,7 @@
-author('christophe.romain@process-one.net').
-version('1.13-0').
@@ -55,9 +55,9 @@
@@ -53,9 +53,9 @@
-include("adhoc.hrl").
-include("pubsub.hrl").
@ -22,7 +22,7 @@
%% exports for hooks
-export([presence_probe/3,
@@ -104,7 +104,7 @@
@@ -102,7 +102,7 @@
string_to_affiliation/1,
extended_error/2,
extended_error/3,
@ -31,7 +31,7 @@
]).
%% API and gen_server callbacks
@@ -123,7 +123,7 @@
@@ -121,7 +121,7 @@
-export([send_loop/1
]).
@ -40,7 +40,7 @@
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
-define(PLUGIN_PREFIX, "node_").
-define(TREE_PREFIX, "nodetree_").
@@ -220,8 +220,6 @@
@@ -218,8 +218,6 @@
ok
end,
ejabberd_router:register_route(Host),
@ -49,7 +49,7 @@
init_nodes(Host, ServerHost, NodeTree, Plugins),
State = #state{host = Host,
server_host = ServerHost,
@@ -280,206 +278,15 @@
@@ -278,209 +276,14 @@
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
%% TODO, this call should be done plugin side
@ -72,18 +72,18 @@
- ?INFO_MSG("upgrade node pubsub tables",[]),
- F = fun() ->
- {Result, LastIdx} = lists:foldl(
- fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) ->
- fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, Nidx}) ->
- ItemsList =
- lists:foldl(
- fun({item, IID, Publisher, Payload}, Acc) ->
- fun({item, ItemName, Publisher, Payload}, Acc) ->
- C = {unknown, Publisher},
- M = {now(), Publisher},
- mnesia:write(
- #pubsub_item{itemid = {IID, NodeIdx},
- #pubsub_item{id = {ItemName, Nidx},
- creation = C,
- modification = M,
- payload = Payload}),
- [{Publisher, IID} | Acc]
- [{Publisher, ItemName} | Acc]
- end, [], Items),
- Owners =
- dict:fold(
@ -97,7 +97,7 @@
- end
- end, [], ItemsList),
- mnesia:write({pubsub_state,
- {JID, NodeIdx},
- {JID, Nidx},
- UsrItems,
- Aff,
- Sub}),
@ -107,12 +107,12 @@
- end
- end, [], Entities),
- mnesia:delete({pubsub_node, NodeId}),
- {[#pubsub_node{nodeid = NodeId,
- id = NodeIdx,
- {[#pubsub_node{id = NodeId,
- idx = Nidx,
- parents = [element(2, ParentId)],
- owners = Owners,
- options = Options} |
- RecList], NodeIdx + 1}
- RecList], Nidx + 1}
- end, {[], 1},
- mnesia:match_object(
- {pubsub_node, {Host, '_'}, '_', '_'})),
@ -137,32 +137,33 @@
- [nodeid, parentid, type, owners, options] ->
- F = fun({pubsub_node, NodeId, {_, Parent}, Type, Owners, Options}) ->
- #pubsub_node{
- nodeid = NodeId,
- id = 0,
- id = NodeId,
- idx = 0,
- parents = [Parent],
- type = Type,
- owners = Owners,
- options = Options}
- end,
- %% TODO : to change nodeid/id and id/idx or not to change ?
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
- FNew = fun() ->
- LastIdx = lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
- mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
- lists:foreach(fun(#pubsub_state{stateid = StateId} = State) ->
- LastIdx = lists:foldl(fun(#pubsub_node{id = NodeId} = PubsubNode, Nidx) ->
- mnesia:write(PubsubNode#pubsub_node{idx = Nidx}),
- lists:foreach(fun(#pubsub_state{id = StateId} = State) ->
- {JID, _} = StateId,
- mnesia:delete({pubsub_state, StateId}),
- mnesia:write(State#pubsub_state{stateid = {JID, NodeIdx}})
- end, mnesia:match_object(#pubsub_state{stateid = {'_', NodeId}, _ = '_'})),
- lists:foreach(fun(#pubsub_item{itemid = ItemId} = Item) ->
- {IID, _} = ItemId,
- mnesia:write(State#pubsub_state{id = {JID, Nidx}})
- end, mnesia:match_object(#pubsub_state{id = {'_', NodeId}, _ = '_'})),
- lists:foreach(fun(#pubsub_item{id = ItemId} = Item) ->
- {ItemName, _} = ItemId,
- {M1, M2} = Item#pubsub_item.modification,
- {C1, C2} = Item#pubsub_item.creation,
- mnesia:delete({pubsub_item, ItemId}),
- mnesia:write(Item#pubsub_item{itemid = {IID, NodeIdx},
- mnesia:write(Item#pubsub_item{id = {ItemName, Nidx},
- modification = {M2, M1},
- creation = {C2, C1}})
- end, mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'})),
- NodeIdx + 1
- end, mnesia:match_object(#pubsub_item{id = {'_', NodeId}, _ = '_'})),
- Nidx + 1
- end, 1, mnesia:match_object(
- {pubsub_node, {Host, '_'}, '_', '_', '_', '_', '_'})
- ++ mnesia:match_object(
@ -179,13 +180,14 @@
- [nodeid, id, parent, type, owners, options] ->
- F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) ->
- #pubsub_node{
- nodeid = NodeId,
- id = Id,
- id = NodeId,
- idx = Id,
- parents = [Parent],
- type = Type,
- owners = Owners,
- options = Options}
- end,
- %% TODO : to change nodeid/id and id/idx or not to change ?
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
- rename_default_nodeplugin();
- _ ->
@ -203,7 +205,7 @@
- [<<>>] -> [];
- Parents -> Parents
- end,
- mnesia:write(Node#pubsub_node{nodeid={H, BN}, parents=BP}),
- mnesia:write(Node#pubsub_node{id={H, BN}, parents=BP}),
- mnesia:delete({pubsub_node, {H, N}});
- (_) ->
- ok
@ -222,15 +224,15 @@
- case catch mnesia:table_info(pubsub_state, attributes) of
- [stateid, items, affiliation, subscription] ->
- ?INFO_MSG("upgrade state pubsub tables", []),
- F = fun ({pubsub_state, {JID, NodeID}, Items, Aff, Sub}, Acc) ->
- F = fun ({pubsub_state, {JID, NodeId}, Items, Aff, Sub}, Acc) ->
- Subs = case Sub of
- none ->
- [];
- _ ->
- {result, SubID} = pubsub_subscription:subscribe_node(JID, NodeID, []),
- [{Sub, SubID}]
- {result, SubId} = pubsub_subscription:subscribe_node(JID, NodeId, []),
- [{Sub, SubId}]
- end,
- NewState = #pubsub_state{stateid = {JID, NodeID},
- NewState = #pubsub_state{id = {JID, NodeId},
- items = Items,
- affiliation = Aff,
- subscriptions = Subs},
@ -256,11 +258,11 @@
- _ ->
- ok
- end.
+
-
send_loop(State) ->
receive
@@ -491,7 +298,10 @@
{presence, JID, Pid} ->
@@ -491,7 +294,10 @@
%% for each node From is subscribed to
%% and if the node is so configured, send the last published item to From
lists:foreach(fun(PType) ->
@ -272,37 +274,37 @@
lists:foreach(
fun({Node, subscribed, _, SubJID}) ->
if (SubJID == LJID) or (SubJID == BJID) ->
@@ -616,7 +426,8 @@
@@ -616,7 +422,8 @@
[#xmlel{name = 'identity', ns = ?NS_DISCO_INFO,
attrs = [?XMLATTR('category', <<"pubsub">>), ?XMLATTR('type', <<"pep">>)]}];
disco_identity(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
+ Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Nidx),
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
{result, _} ->
{result,
@@ -647,7 +458,8 @@
@@ -647,7 +454,8 @@
[?NS_PUBSUB_s
| [?NS_PUBSUB_s++"#"++Feature || Feature <- features("pep")]];
disco_features(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
+ Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Nidx),
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
{result, _} ->
{result, [?NS_PUBSUB_s
@@ -666,7 +478,8 @@
@@ -666,7 +474,8 @@
{result, disco_items(To, Node, From) ++ OtherItems}.
disco_items(Host, <<>>, From) ->
- Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx, owners = Owners}, Acc) ->
+ Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx}, Acc) ->
+ Owners = node_owners_call(Type, Idx),
case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- Action = fun(#pubsub_node{id ={_, NodeId}, options = Options, type = Type, idx = Nidx, owners = Owners}, Acc) ->
+ Action = fun(#pubsub_node{id ={_, NodeId}, options = Options, type = Type, idx = Nidx}, Acc) ->
+ Owners = node_owners_call(Type, Nidx),
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
{result, _} ->
[#xmlel{name = 'item', ns = ?NS_DISCO_INFO,
@@ -680,13 +493,14 @@
@@ -680,13 +489,14 @@
_ -> Acc
end
end,
@ -313,26 +315,26 @@
end;
disco_items(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
+ Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Nidx),
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
{result, Items} ->
{result,
@@ -771,10 +585,10 @@
@@ -772,10 +582,10 @@
lists:foreach(fun(PType) ->
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
lists:foreach(fun
- ({#pubsub_node{options = Options, owners = Owners, id = NodeId}, subscribed, _, JID}) ->
+ ({#pubsub_node{options = Options, id = NodeId}, subscribed, _, JID}) ->
- ({#pubsub_node{options = Options, owners = Owners, idx = Nidx}, subscribed, _, JID}) ->
+ ({#pubsub_node{options = Options, idx = Nidx}, subscribed, _, JID}) ->
case get_option(Options, access_model) of
presence ->
- case lists:member(BJID, Owners) of
+ case lists:member(BJID, node_owners(Host, PType, NodeId)) of
+ case lists:member(BJID, node_owners(Host, PType, Nidx)) of
true ->
node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
node_action(Host, PType, unsubscribe_node, [Nidx, Entity, JID, all]);
false ->
@@ -949,10 +763,11 @@
@@ -950,10 +760,11 @@
end,
ejabberd_router:route(To, From, Res);
#iq{type = get, ns = ?NS_DISCO_ITEMS,
@ -346,16 +348,16 @@
{result, IQRes} ->
Result = #xmlel{ns = ?NS_DISCO_ITEMS,
name = 'query', attrs = QAttrs,
@@ -1089,7 +904,7 @@
@@ -1090,7 +901,7 @@
[] ->
["leaf"]; %% No sub-nodes: it's a leaf node
_ ->
- case node_call(Type, get_items, [NodeId, From]) of
+ case node_call(Type, get_items, [NodeId, From, none]) of
- case node_call(Type, get_items, [Nidx, From]) of
+ case node_call(Type, get_items, [Nidx, From, none]) of
{result, []} -> ["collection"];
{result, _} -> ["leaf", "collection"];
_ -> []
@@ -1105,8 +920,9 @@
@@ -1106,8 +917,9 @@
[];
true ->
[#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s)]} |
@ -367,7 +369,7 @@
end, features(Type))]
end,
%% TODO: add meta-data info (spec section 5.4)
@@ -1135,8 +951,9 @@
@@ -1136,8 +948,9 @@
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_ADHOC_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_VCARD_s)]}] ++
@ -379,7 +381,7 @@
end, features(Host, Node))};
?NS_ADHOC_b ->
command_disco_info(Host, Node, From);
@@ -1146,7 +963,7 @@
@@ -1147,7 +960,7 @@
node_disco_info(Host, Node, From)
end.
@ -388,7 +390,7 @@
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
Nodes when is_list(Nodes) ->
{result, lists:map(
@@ -1163,7 +980,7 @@
@@ -1164,7 +977,7 @@
Other ->
Other
end;
@ -397,7 +399,7 @@
%% TODO: support localization of this string
CommandItems = [
#xmlel{ns = ?NS_DISCO_ITEMS, name = 'item',
@@ -1172,19 +989,20 @@
@@ -1173,19 +986,20 @@
?XMLATTR('name', "Get Pending")
]}],
{result, CommandItems};
@ -408,22 +410,22 @@
-iq_disco_items(Host, Item, From) ->
+iq_disco_items(Host, Item, From, RSM) ->
case string:tokens(Item, "!") of
[_SNode, _ItemID] ->
[_SNode, _ItemId] ->
{result, []};
[SNode] ->
Node = string_to_node(SNode),
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
- NodeItems = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
+ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
- NodeItems = case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
+ Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options}) ->
+ Owners = node_owners_call(Type, Nidx),
+ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) of
{result, R} -> R;
- _ -> []
+ _ -> {[], none}
end,
Nodes = lists:map(
fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
@@ -1202,7 +1020,7 @@
fun(#pubsub_node{id = {_, SubNode}, options = SubOptions}) ->
@@ -1203,7 +1017,7 @@
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
#xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', attrs = [?XMLATTR('jid', Host), ?XMLATTR('name', Name)]}
end, NodeItems),
@ -432,30 +434,29 @@
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> {result, Result};
@@ -1210,12 +1028,6 @@
@@ -1211,12 +1025,6 @@
end
end.
-get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
-get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) ->
- AccessModel = get_option(Options, access_model),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
- node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]).
- node_call(Type, get_items, [Nidx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]).
-
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) ->
if (AccessModel == presence) or (AccessModel == roster) ->
case Host of
@@ -1333,7 +1145,8 @@
@@ -1334,7 +1142,7 @@
(_, Acc) ->
Acc
end, [], exmpp_xml:remove_cdata_from_list(Els)),
- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
+ RSM = jlib:rsm_decode(SubEl),
+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
- get_items(Host, Node, From, SubId, MaxItems, ItemIds);
+ get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(SubEl));
{get, 'subscriptions'} ->
get_subscriptions(Host, Node, From, Plugins);
{get, 'affiliations'} ->
@@ -1490,7 +1303,8 @@
@@ -1491,7 +1299,8 @@
_ -> []
end
end,
@ -465,36 +466,36 @@
sync_dirty) of
{result, Res} -> Res;
Err -> Err
@@ -1534,7 +1348,7 @@
@@ -1535,7 +1344,7 @@
%%% authorization handling
-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) ->
+send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) ->
-send_authorization_request(#pubsub_node{owners = Owners, id = {Host, Node}}, Subscriber) ->
+send_authorization_request(#pubsub_node{id = {Host, Node}, type = Type, idx = Nidx}, Subscriber) ->
Lang = <<"en">>, %% TODO fix
{U, S, R} = Subscriber,
Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children =
@@ -1564,7 +1378,7 @@
@@ -1565,7 +1374,7 @@
lists:foreach(fun(Owner) ->
{U, S, R} = Owner,
ejabberd_router:route(service_jid(Host), exmpp_jid:make(U, S, R), Stanza)
- end, Owners).
+ end, node_owners(Host, Type, NodeId)).
+ end, node_owners(Host, Type, Nidx)).
find_authorization_response(Packet) ->
Els = Packet#xmlel.children,
@@ -1623,8 +1437,8 @@
@@ -1624,8 +1433,8 @@
"true" -> true;
_ -> false
end,
- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
- Action = fun(#pubsub_node{type = Type, owners = Owners, idx = Nidx}) ->
- IsApprover = lists:member(jlib:short_prepd_bare_jid(From), Owners),
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ IsApprover = lists:member(jlib:short_prepd_bare_jid(From), node_owners_call(Type, NodeId)),
{result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
+ Action = fun(#pubsub_node{type = Type, idx = Nidx}) ->
+ IsApprover = lists:member(jlib:short_prepd_bare_jid(From), node_owners_call(Type, Nidx)),
{result, Subscriptions} = node_call(Type, get_subscriptions, [Nidx, Subscriber]),
if
not IsApprover ->
@@ -1823,7 +1637,7 @@
@@ -1824,7 +1633,7 @@
end,
Reply = #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children =
[#xmlel{ns = ?NS_PUBSUB, name = 'create', attrs = nodeAttr(Node)}]},
@ -503,7 +504,7 @@
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
case Result of
@@ -1927,7 +1741,7 @@
@@ -1928,7 +1737,7 @@
%%<li>The node does not exist.</li>
%%</ul>
subscribe_node(Host, Node, From, JID, Configuration) ->
@ -512,50 +513,50 @@
{result, GoodSubOpts} -> GoodSubOpts;
_ -> invalid
end,
@@ -1937,7 +1751,7 @@
@@ -1938,7 +1747,7 @@
_:_ ->
{undefined, undefined, undefined}
end,
- Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId}) ->
+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, idx = Nidx}) ->
+ Action = fun(#pubsub_node{options = Options, type = Type, idx = Nidx}) ->
Features = features(Type),
SubscribeFeature = lists:member("subscribe", Features),
OptionsFeature = lists:member("subscription-options", Features),
@@ -1946,6 +1760,7 @@
@@ -1947,6 +1756,7 @@
AccessModel = get_option(Options, access_model),
SendLast = get_option(Options, send_last_published_item),
AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ Owners = node_owners_call(Type, NodeId),
+ Owners = node_owners_call(Type, Nidx),
{PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups),
if
not SubscribeFeature ->
@@ -2290,7 +2105,7 @@
@@ -2291,7 +2101,7 @@
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
-get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) ->
+get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) ->
-get_items(Host, Node, From, SubId, SMaxItems, ItemIds) ->
+get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
MaxItems =
if
SMaxItems == "" -> get_max_items_node(Host);
@@ -2304,12 +2119,13 @@
@@ -2305,12 +2115,13 @@
{error, Error} ->
{error, Error};
_ ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId, owners = Owners}) ->
+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Action = fun(#pubsub_node{options = Options, type = Type, idx = Nidx, owners = Owners}) ->
+ Action = fun(#pubsub_node{options = Options, type = Type, idx = Nidx}) ->
Features = features(Type),
RetreiveFeature = lists:member("retrieve-items", Features),
PersistentFeature = lists:member("persistent-items", Features),
AccessModel = get_option(Options, access_model),
AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ Owners = node_owners_call(Type, NodeId),
+ Owners = node_owners_call(Type, Nidx),
{PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
if
not RetreiveFeature ->
@@ -2322,11 +2138,11 @@
@@ -2323,11 +2134,11 @@
node_call(Type, get_items,
[NodeId, From,
[Nidx, From,
AccessModel, PresenceSubscription, RosterGroup,
- SubId])
+ SubId, RSM])
@ -564,10 +565,10 @@
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} ->
+ {result, {_, Items, RSMOut}} ->
SendItems = case ItemIDs of
SendItems = case ItemIds of
[] ->
Items;
@@ -2339,7 +2155,7 @@
@@ -2340,7 +2151,7 @@
%% number of items sent to MaxItems:
{result, #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children =
[#xmlel{ns = ?NS_PUBSUB, name = 'items', attrs = nodeAttr(Node), children =
@ -576,7 +577,7 @@
Error ->
Error
end
@@ -2360,6 +2176,17 @@
@@ -2363,6 +2174,17 @@
{result, {_, Items}} -> Items;
Error -> Error
end.
@ -594,7 +595,7 @@
%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any()
%% Host = pubsubHost()
@@ -2371,16 +2198,29 @@
@@ -2374,16 +2196,29 @@
%% @doc <p>Resend the items of a node to the user.</p>
%% @todo use cache-last-item feature
send_items(Host, Node, NodeId, Type, LJID, last) ->
@ -630,21 +631,21 @@
send_items(Host, Node, NodeId, Type, {LU, LS, LR} = LJID, Number) ->
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
{result, []} ->
@@ -2507,7 +2347,8 @@
@@ -2510,7 +2345,8 @@
error ->
{error, 'bad-request'};
_ ->
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ Owners = node_owners_call(Type, NodeId),
- Action = fun(#pubsub_node{owners = Owners, type = Type, idx = Nidx}=N) ->
+ Action = fun(#pubsub_node{type = Type, idx = Nidx}) ->
+ Owners = node_owners_call(Type, Nidx),
case lists:member(Owner, Owners) of
true ->
OwnerJID = exmpp_jid:make(Owner),
@@ -2517,24 +2358,8 @@
@@ -2520,24 +2356,8 @@
end,
lists:foreach(
fun({JID, Affiliation}) ->
- {result, _} = node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
- {result, _} = node_call(Type, set_affiliation, [Nidx, JID, Affiliation]),
- case Affiliation of
- owner ->
- NewOwner = jlib:short_prepd_bare_jid(JID),
@ -663,16 +664,16 @@
- ok
- end
+ % TODO, check if nothing missing here about new owners
+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
+ node_call(Type, set_affiliation, [Nidx, JID, Affiliation])
end, FilteredEntities),
{result, []};
_ ->
@@ -2589,11 +2414,11 @@
@@ -2592,11 +2412,11 @@
end.
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
- case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of
+ case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of
read_sub(Subscriber, Node, NodeId, SubId, Lang) ->
- case pubsub_subscription:get_subscription(Subscriber, NodeId, SubId) of
+ case pubsub_subscription_odbc:get_subscription(Subscriber, NodeId, SubId) of
{error, notfound} ->
{error, extended_error('not-acceptable', "invalid-subid")};
{result, #pubsub_subscription{options = Options}} ->
@ -680,37 +681,37 @@
+ {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options),
OptionsEl = #xmlel{ns = ?NS_PUBSUB, name = 'options',
attrs = [ ?XMLATTR('jid', exmpp_jid:to_binary(Subscriber)),
?XMLATTR('subid', SubID) | nodeAttr(Node)],
@@ -2620,7 +2445,7 @@
?XMLATTR('subid', SubId) | nodeAttr(Node)],
@@ -2623,7 +2443,7 @@
end.
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
set_options_helper(Configuration, JID, NodeId, SubId, Type) ->
- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
+ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
{result, GoodSubOpts} -> GoodSubOpts;
_ -> invalid
end,
@@ -2650,7 +2475,7 @@
write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
@@ -2653,7 +2473,7 @@
write_sub(_Subscriber, _NodeId, _SubId, invalid) ->
{error, extended_error('bad-request', "invalid-options")};
write_sub(Subscriber, NodeID, SubID, Options) ->
- case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of
+ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of
write_sub(Subscriber, NodeId, SubId, Options) ->
- case pubsub_subscription:set_subscription(Subscriber, NodeId, SubId, Options) of
+ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeId, SubId, Options) of
{error, notfound} ->
{error, extended_error('not-acceptable', "invalid-subid")};
{result, _} ->
@@ -2824,8 +2649,8 @@
@@ -2827,8 +2647,8 @@
?XMLATTR('subsription', subscription_to_string(Sub)) | nodeAttr(Node)]}]}]},
ejabberd_router:route(service_jid(Host), JID, Stanza)
end,
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
- Action = fun(#pubsub_node{owners = Owners, type = Type, idx = Nidx}) ->
- case lists:member(Owner, Owners) of
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
+ Action = fun(#pubsub_node{type = Type, idx = Nidx}) ->
+ case lists:member(Owner, node_owners_call(Type, Nidx)) of
true ->
Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
@@ -3174,7 +2999,7 @@
@@ -3177,7 +2997,7 @@
{Depth, [{N, get_node_subs(N)} || N <- Nodes]}
end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))}
end,
@ -719,19 +720,16 @@
{result, CollSubs} -> CollSubs;
_ -> []
end.
@@ -3188,9 +3013,9 @@
@@ -3190,7 +3010,7 @@
get_options_for_subs(NodeID, Subs) ->
lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
- case pubsub_subscription:read_subscription(JID, NodeID, SubID) of
+ case pubsub_subscription_odbc:get_subscription(JID, NodeID, SubID) of
{error, notfound} -> [{JID, SubID, []} | Acc];
- #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc];
+ {result, #pubsub_subscription{options = Options}} -> [{JID, SubID, Options} | Acc];
get_options_for_subs(Nidx, Subs) ->
lists:foldl(fun({JID, subscribed, SubId}, Acc) ->
- case pubsub_subscription:read_subscription(JID, Nidx, SubId) of
+ case pubsub_subscription_odbc:read_subscription(JID, Nidx, SubId) of
{error, notfound} -> [{JID, SubId, []} | Acc];
#pubsub_subscription{options = Options} -> [{JID, SubId, Options} | Acc];
_ -> Acc
end;
(_, Acc) ->
@@ -3412,6 +3237,30 @@
@@ -3414,6 +3234,30 @@
Result
end.
@ -762,7 +760,7 @@
%% @spec (Host, Options) -> MaxItems
%% Host = host()
%% Options = [Option]
@@ -3815,7 +3664,13 @@
@@ -3817,7 +3661,13 @@
tree_action(Host, Function, Args) ->
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
Fun = fun() -> tree_call(Host, Function, Args) end,
@ -777,7 +775,7 @@
%% @doc <p>node plugin call.</p>
node_call(Type, Function, Args) ->
@@ -3835,13 +3690,13 @@
@@ -3837,13 +3687,13 @@
node_action(Host, Type, Function, Args) ->
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
@ -793,7 +791,7 @@
case tree_call(Host, get_node, [Host, Node]) of
N when is_record(N, pubsub_node) ->
case Action(N) of
@@ -3854,13 +3709,20 @@
@@ -3856,13 +3706,20 @@
end
end, Trans).
@ -818,7 +816,7 @@
{result, Result} -> {result, Result};
{error, Error} -> {error, Error};
{atomic, {result, Result}} -> {result, Result};
@@ -3868,6 +3730,15 @@
@@ -3870,6 +3727,15 @@
{aborted, Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
{error, 'internal-server-error'};
@ -834,7 +832,7 @@
{'EXIT', Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
{error, 'internal-server-error'};
@@ -3876,6 +3747,16 @@
@@ -3878,6 +3744,16 @@
{error, 'internal-server-error'}
end.

View File

@ -24,22 +24,25 @@
-author("bjc@kublai.com").
%% API
-export([init/0,
-export([
init/0,
subscribe_node/3,
unsubscribe_node/3,
get_subscription/3,
set_subscription/4,
get_options_xform/2,
parse_options_xform/1]).
parse_options_xform/1
]).
% Internal function also exported for use in transactional bloc from pubsub plugins
-export([add_subscription/3,
% Internal function also exported for use in transactional bloc from pubsub plugins
-export([
add_subscription/3,
delete_subscription/3,
read_subscription/3,
write_subscription/4]).
write_subscription/4
]).
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-define(PUBSUB_DELIVER, "pubsub#deliver").
-define(PUBSUB_DIGEST, "pubsub#digest").
@ -86,45 +89,83 @@
%%====================================================================
%% API
%%====================================================================
-spec(init/0 :: () -> 'ok').
init() ->
ok = create_table().
subscribe_node(JID, NodeID, Options) ->
try mnesia:sync_dirty(fun add_subscription/3,
[JID, NodeID, Options]) of
-spec(subscribe_node/3 ::
(
Entity :: jidEntity() | fullUsr(),
NodeIdx :: nodeIdx(),
Options :: [nodeOption()])
-> {'result', SubId::subId()} | {'error', _}
).
subscribe_node(#jid{node = U, domain = S, resource = R}, NodeIdx, Options) ->
subscribe_node({U,S,R}, NodeIdx, Options);
subscribe_node(Entity, NodeIdx, Options) ->
try mnesia:sync_dirty(fun add_subscription/3, [Entity, NodeIdx, Options]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
catch
Error -> Error
end.
unsubscribe_node(JID, NodeID, SubID) ->
try mnesia:sync_dirty(fun delete_subscription/3,
[JID, NodeID, SubID]) of
-spec(unsubscribe_node/3 ::
(
JID :: jidEntity(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> {'result','ok'} | {'error', _}
).
unsubscribe_node(#jid{node = U, domain = S, resource = R} = _JID, NodeIdx, SubId) ->
try mnesia:sync_dirty(fun delete_subscription/3, [{U,S,R}, NodeIdx, SubId]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
catch
Error -> Error
end.
get_subscription(JID, NodeID, SubID) ->
try mnesia:sync_dirty(fun read_subscription/3,
[JID, NodeID, SubID]) of
-spec(get_subscription/3 ::
(
JID :: jidEntity(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> {'result', pubsubSubscription()} | {'error', _}
).
get_subscription(#jid{node = U, domain = S, resource = R} = _JID, NodeIdx, SubId) ->
try mnesia:sync_dirty(fun read_subscription/3, [{U,S,R}, NodeIdx, SubId]) of
{error, Error} -> {error, Error};
Subscription -> {result, Subscription}
catch
Error -> Error
end.
-spec(set_subscription/4 ::
(
JID :: jidEntity(),
NodeIdx :: nodeIdx(),
SubId :: subId(),
Options :: [nodeOption()])
-> {'result', 'ok'} | {'error', _}
).
set_subscription(#jid{node = U, domain = S, resource = R} = _JID, NodeIdx, SubId, Options) ->
try mnesia:sync_dirty(fun write_subscription/4, [{U,S,R}, NodeIdx, SubId, Options]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
catch
Error -> Error
end.
set_subscription(JID, NodeID, SubID, Options) ->
try mnesia:sync_dirty(fun write_subscription/4,
[JID, NodeID, SubID, Options]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
catch
Error -> Error
end.
%% TODO : check input type data
get_options_xform(Lang, Options) ->
Keys = [deliver, digest, digest_frequency, expire, include_body, show_values, subscription_type, subscription_depth],
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
@ -137,6 +178,7 @@ get_options_xform(Lang, Options) ->
name = 'value',
children = [?XMLCDATA(?NS_PUBSUB_SUBSCRIBE_OPTIONS_s)]}]}] ++ XFields}}.
%% TODO : check input type data
parse_options_xform(XFields) ->
case XFields of
[] -> {result, []};
@ -160,39 +202,81 @@ parse_options_xform(XFields) ->
%%====================================================================
%% Internal functions
%%====================================================================
-spec(create_table/0 :: () -> 'ok').
create_table() ->
case mnesia:create_table(pubsub_subscription,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_subscription)},
{type, set}]) of
{type, set}])
of
{atomic, ok} -> ok;
{aborted, {already_exists, _}} -> ok;
Other -> Other
end.
add_subscription(_JID, _NodeID, Options) ->
SubID = make_subid(),
mnesia:write(#pubsub_subscription{subid = SubID, options = Options}),
SubID.
-spec(add_subscription/3 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
Options :: [nodeOption()])
-> SubId::subId()
).
delete_subscription(_JID, _NodeID, SubID) ->
mnesia:delete({pubsub_subscription, SubID}).
add_subscription(_Entity, _NodeIdx, Options) ->
SubId = make_subid(),
mnesia:write(#pubsub_subscription{subid = SubId, options = Options}),
SubId.
read_subscription(_JID, _NodeID, SubID) ->
case mnesia:read({pubsub_subscription, SubID}) of
[Sub] -> Sub;
_ -> {error, notfound}
-spec(delete_subscription/3 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> 'ok'
).
delete_subscription(_Entity, _NodeIdx, SubId) ->
mnesia:delete({pubsub_subscription, SubId}).
-spec(read_subscription/3 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> pubsubSubscription() | {'error', 'notfound'}
).
read_subscription(_Entity, _NodeIdx, SubId) ->
case mnesia:read({pubsub_subscription, SubId}) of
[Subscription] -> Subscription;
_ -> {'error', 'notfound'}
end.
write_subscription(JID, NodeID, SubID, Options) ->
case read_subscription(JID, NodeID, SubID) of
{error, notfound} -> {error, notfound};
Sub -> mnesia:write(Sub#pubsub_subscription{options = Options})
-spec(write_subscription/4 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
SubId :: subId(),
Options :: [nodeOption()])
-> 'ok' | {'error', 'notfound'}
).
write_subscription(Entity, NodeIdx, SubId, Options) ->
case read_subscription(Entity, NodeIdx, SubId) of
{error, 'notfound'} -> {error, 'notfound'};
Subscription -> mnesia:write(Subscription#pubsub_subscription{options = Options})
end.
-spec(make_subid/0 :: () -> SubId::subId()).
make_subid() ->
{T1, T2, T3} = now(),
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
list_to_binary(lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]))).
%%
%% Subscription XForm processing.
@ -200,6 +284,7 @@ make_subid() ->
%% Return processed options, with types converted and so forth, using
%% Opts as defaults.
%% TODO : check input type data
set_xoption([], Opts) ->
Opts;
set_xoption([{Var, Value} | T], Opts) ->
@ -212,33 +297,36 @@ set_xoption([{Var, Value} | T], Opts) ->
end,
set_xoption(T, NewOpts).
%% Return the options list's key for an XForm var.
var_xfield(?PUBSUB_DELIVER) -> deliver;
var_xfield(?PUBSUB_DIGEST) -> digest;
var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
var_xfield(?PUBSUB_EXPIRE) -> expire;
var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
var_xfield(_) -> {error, badarg}.
var_xfield(?PUBSUB_DELIVER) -> 'deliver';
var_xfield(?PUBSUB_DIGEST) -> 'digest';
var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> 'digest_frequency';
var_xfield(?PUBSUB_EXPIRE) -> 'expire';
var_xfield(?PUBSUB_INCLUDE_BODY) -> 'include_body';
var_xfield(?PUBSUB_SHOW_VALUES) -> 'show_values';
var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> 'subscription_type';
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> 'subscription_depth';
var_xfield(_) -> {error, 'badarg'}.
%% Convert Values for option list's Key.
val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
val_xfield(digest, [Val]) -> xopt_to_bool(Val);
val_xfield(digest_frequency, [Val]) -> list_to_integer(Val);
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
val_xfield(show_values, Vals) -> Vals;
val_xfield(subscription_type, ["items"]) -> items;
val_xfield(subscription_type, ["nodes"]) -> nodes;
val_xfield(subscription_depth, ["all"]) -> all;
val_xfield(subscription_depth, [Depth]) ->
val_xfield('deliver', [Val]) -> xopt_to_bool(Val);
val_xfield('digest', [Val]) -> xopt_to_bool(Val);
val_xfield('digest_frequency', [Val]) -> list_to_integer(Val);
val_xfield('expire', [Val]) -> jlib:datetime_string_to_timestamp(Val);
val_xfield('include_body', [Val]) -> xopt_to_bool(Val);
val_xfield('show_values', Vals) -> Vals;
val_xfield('subscription_type', ["items"]) -> items;
val_xfield('subscription_type', ["nodes"]) -> nodes;
val_xfield('subscription_depth', ["all"]) -> all;
val_xfield('subscription_depth', [Depth]) ->
case catch list_to_integer(Depth) of
N when is_integer(N) -> N;
Integer when is_integer(Integer) -> Integer;
_ -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acceptable')}
end.
%% Convert XForm booleans to Erlang booleans.
xopt_to_bool("0") -> false;
xopt_to_bool("1") -> true;
@ -248,6 +336,7 @@ xopt_to_bool(_) -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acce
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
%% TODO : check input type data
get_option_xfield(Lang, Key, Options) ->
Var = xfield_var(Key),
Label = xfield_label(Key),
@ -263,11 +352,13 @@ get_option_xfield(Lang, Key, Options) ->
attrs = [?XMLATTR('var', Var), ?XMLATTR('type', Type), ?XMLATTR('label', translate:translate(Lang, Label))],
children = OptEls ++ Vals}.
%% TODO : check input type data
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
type_and_options(Type, _Lang) ->
{Type, []}.
%% TODO : check input type data
tr_xfield_options({Value, Label}, Lang) ->
#xmlel{ns = ?NS_DATA_FORMS,
name = 'option',
@ -276,26 +367,27 @@ tr_xfield_options({Value, Label}, Lang) ->
name = 'value',
children = [?XMLCDATA(Value)]}]}.
%% TODO : check input type data
tr_xfield_values(Value) ->
#xmlel{ns = ?NS_DATA_FORMS, name ='value', children = [?XMLCDATA(Value)]}.
%% Return the XForm variable name for a subscription option key.
xfield_var(deliver) -> ?PUBSUB_DELIVER;
xfield_var(digest) -> ?PUBSUB_DIGEST;
xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
xfield_var(expire) -> ?PUBSUB_EXPIRE;
xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
xfield_var('deliver') -> ?PUBSUB_DELIVER;
xfield_var('digest') -> ?PUBSUB_DIGEST;
xfield_var('digest_frequency') -> ?PUBSUB_DIGEST_FREQUENCY;
xfield_var('expire') -> ?PUBSUB_EXPIRE;
xfield_var('include_body') -> ?PUBSUB_INCLUDE_BODY;
xfield_var('show_values') -> ?PUBSUB_SHOW_VALUES;
xfield_var('subscription_type') -> ?PUBSUB_SUBSCRIPTION_TYPE;
xfield_var('subscription_depth') -> ?PUBSUB_SUBSCRIPTION_DEPTH.
%% Return the XForm variable type for a subscription option key.
xfield_type(deliver) -> "boolean";
xfield_type(digest) -> "boolean";
xfield_type(digest_frequency) -> "text-single";
xfield_type(expire) -> "text-single";
xfield_type(include_body) -> "boolean";
xfield_type(show_values) ->
xfield_type('deliver') -> "boolean";
xfield_type('digest') -> "boolean";
xfield_type('digest_frequency') -> "text-single";
xfield_type('expire') -> "text-single";
xfield_type('include_body') -> "boolean";
xfield_type('show_values') ->
{"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
{"chat", ?SHOW_VALUE_CHAT_LABEL},
{"dnd", ?SHOW_VALUE_DND_LABEL},
@ -309,27 +401,27 @@ xfield_type(subscription_depth) ->
{"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
%% Return the XForm variable label for a subscription option key.
xfield_label(deliver) -> ?DELIVER_LABEL;
xfield_label(digest) -> ?DIGEST_LABEL;
xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
xfield_label(expire) -> ?EXPIRE_LABEL;
xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
xfield_label('deliver') -> ?DELIVER_LABEL;
xfield_label('digest') -> ?DIGEST_LABEL;
xfield_label('digest_frequency') -> ?DIGEST_FREQUENCY_LABEL;
xfield_label('expire') -> ?EXPIRE_LABEL;
xfield_label('include_body') -> ?INCLUDE_BODY_LABEL;
xfield_label('show_values') -> ?SHOW_VALUES_LABEL;
xfield_label('subscription_type') -> ?SUBSCRIPTION_TYPE_LABEL;
xfield_label('subscription_depth') -> ?SUBSCRIPTION_DEPTH_LABEL.
%% Return the XForm value for a subscription option key.
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
xfield_val(digest, Val) -> [bool_to_xopt(Val)];
xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
xfield_val(show_values, Val) -> Val;
xfield_val(subscription_type, items) -> ["items"];
xfield_val(subscription_type, nodes) -> ["nodes"];
xfield_val(subscription_depth, all) -> ["all"];
xfield_val(subscription_depth, N) -> [integer_to_list(N)].
xfield_val('deliver', Val) -> [bool_to_xopt(Val)];
xfield_val('digest', Val) -> [bool_to_xopt(Val)];
xfield_val('digest_frequency', Val) -> [integer_to_list(Val)];
xfield_val('expire', Val) -> [jlib:now_to_utc_string(Val)];
xfield_val('include_body', Val) -> [bool_to_xopt(Val)];
xfield_val('show_values', Val) -> Val;
xfield_val('subscription_type', 'items') -> ["items"];
xfield_val('subscription_type', 'nodes') -> ["nodes"];
xfield_val('subscription_depth', 'all') -> ["all"];
xfield_val('subscription_depth', Depth) -> [integer_to_list(Depth)].
%% Convert erlang booleans to XForms.
bool_to_xopt(false) -> "false";
bool_to_xopt(true) -> "true".
bool_to_xopt('false') -> "false";
bool_to_xopt('true') -> "true".

View File

@ -37,7 +37,6 @@
-include_lib("stdlib/include/qlc.hrl").
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-define(PUBSUB_DELIVER, "pubsub#deliver").
-define(PUBSUB_DIGEST, "pubsub#digest").
@ -89,33 +88,33 @@
init() ->
ok = create_table().
subscribe_node(_JID, _NodeID, Options) ->
SubID = make_subid(),
?DB_MOD:add_subscription(#pubsub_subscription{subid = SubID, options = Options}),
{result, SubID}.
subscribe_node(_JID, _Nidx, Options) ->
SubId = make_subid(),
?DB_MOD:add_subscription(#pubsub_subscription{subid = SubId, options = Options}),
{result, SubId}.
unsubscribe_node(_JID, _NodeID, SubID) ->
case ?DB_MOD:read_subscription(SubID) of
unsubscribe_node(_JID, _Nidx, SubId) ->
case ?DB_MOD:read_subscription(SubId) of
{ok, Sub} ->
?DB_MOD:delete_subscription(SubID),
?DB_MOD:delete_subscription(SubId),
{result, Sub};
notfound ->
{error, notfound}
end.
get_subscription(_JID, _NodeID, SubID) ->
case ?DB_MOD:read_subscription(SubID) of
get_subscription(_JID, _Nidx, SubId) ->
case ?DB_MOD:read_subscription(SubId) of
{ok, Sub} -> {result, Sub};
notfound -> {error, notfound}
end.
set_subscription(_JID, _NodeID, SubID, Options) ->
case ?DB_MOD:read_subscription(SubID) of
set_subscription(_JID, _Nidx, SubId, Options) ->
case ?DB_MOD:read_subscription(SubId) of
{ok, _} ->
?DB_MOD:update_subscription(#pubsub_subscription{subid = SubID, options = Options}),
?DB_MOD:update_subscription(#pubsub_subscription{subid = SubId, options = Options}),
{result, ok};
notfound ->
?DB_MOD:add_subscription(#pubsub_subscription{subid = SubID, options = Options}),
?DB_MOD:add_subscription(#pubsub_subscription{subid = SubId, options = Options}),
{result, ok}
end.

View File

@ -1184,14 +1184,14 @@ get_jid_info(_, User, Server, JID)
try
F = fun() ->
LJID = jlib:short_prepd_jid(JID),
case catch gen_storage:read(LServer, {rosteritem, {LUser, LServer, LJID}}) of
LRJID = jlib:short_prepd_bare_jid(JID),
case catch gen_storage:read(LServer, {rosteritem, {LUser, LServer, LRJID}}) of
[#rosteritem{subscription = Subscription}] ->
Groups =
[Group || #rostergroup{grp = Group} <-
gen_storage:read(LServer, {rostergroup, {LUser, LServer, LJID}})],
gen_storage:read(LServer, {rostergroup, {LUser, LServer, LRJID}})],
{Subscription, Groups};
_ ->
LRJID = jlib:short_prepd_bare_jid(JID),
if
LRJID == LJID ->
{none, []};
@ -1202,7 +1202,7 @@ get_jid_info(_, User, Server, JID)
[#rosteritem{subscription = Subscription}] ->
Groups =
[Group || #rostergroup{grp = Group} <-
gen_storage:read(LServer, {rostergroup, {LUser, LServer, LJID}})],
gen_storage:read(LServer, {rostergroup, {LUser, LServer, LRJID}})],
{Subscription, Groups};
_ ->
{none, []}
@ -1518,7 +1518,7 @@ user_roster_item_parse_query(User, Server, Items, Query) ->
ns = ?NS_ROSTER,
name = 'query',
children = [Item]},
process_iq(
process_iq_set(
UJID, UJID,
#iq{type = set, ns = ?NS_JABBER_CLIENT, payload = Request}),
throw(submitted);

View File

@ -62,8 +62,8 @@ process_local_iq(_From, _To, #iq{type = 'set'} = IQ) ->
process_sm_iq(
#jid{prep_node = Node, prep_domain = Domain} = From,
#jid{prep_node = Node, prep_domain = Domain} = _To,
#jid{node = Node, domain = Domain} = From,
#jid{node = Node, domain = Domain} = _To,
#iq{type = 'get'} = IQ) ->
get_ip(From, IQ);

View File

@ -59,7 +59,13 @@ process_local_iq(_From, _To, #iq{type = get} = IQ_Rec) ->
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal, utc),
Seconds_diff = calendar:datetime_to_gregorian_seconds(Now_local)
- calendar:datetime_to_gregorian_seconds(Now_universal),
{Hd, Md, _} = calendar:seconds_to_time(Seconds_diff),
{Hd, Md, _} = case Seconds_diff >= 0 of
true ->
calendar:seconds_to_time(Seconds_diff);
false ->
{Hd0, Md0, Sd0} = calendar:seconds_to_time(-Seconds_diff),
{-Hd0, Md0, Sd0}
end,
{_, TZO_diff} = jlib:timestamp_to_iso({{0, 0, 0}, {0, 0, 0}}, {Hd, Md}),
Result = #xmlel{ns = ?NS_TIME, name = 'time', children = [
#xmlel{ns = ?NS_TIME, name = 'tzo', children = [

View File

@ -218,8 +218,8 @@ process_local_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
process_sm_iq(_From, To, #iq{type = get} = IQ_Rec) ->
LUser = exmpp_jid:prep_node_as_list(To),
LServer = exmpp_jid:prep_domain_as_list(To),
LUser = exmpp_jid:prep_node(To),
LServer = exmpp_jid:prep_domain(To),
case get_vcard(LUser, LServer) of
{vcard, VCard} ->
exmpp_iq:result(IQ_Rec, VCard);
@ -229,15 +229,16 @@ process_sm_iq(_From, To, #iq{type = get} = IQ_Rec) ->
process_sm_iq(From, _To, #iq{type = set, payload = Request} = IQ_Rec) ->
User = exmpp_jid:node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
LServerB = exmpp_jid:prep_domain(From),
case ?IS_MY_HOST(LServer) of
true ->
set_vcard(User, LServer, Request),
set_vcard(User, LServer, LServerB, Request),
exmpp_iq:result(IQ_Rec);
false ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end.
%% @spec (User::string(), Host::string()) -> {vcard, xmlel()} | novcard
%% @spec (User::binary(), Host::binary()) -> {vcard, xmlel()} | novcard
get_vcard(User, Host) ->
US = {User, Host},
case gen_storage:dirty_read(Host, {vcard, US}) of
@ -253,7 +254,7 @@ get_vcard(User, Host) ->
end.
set_vcard(User, LServer, VCARD) ->
set_vcard(User, LServer, LServerB, VCARD) ->
FN = exmpp_xml:get_path(VCARD,
[{element, 'FN'}, cdata_as_list]),
Family = exmpp_xml:get_path(VCARD,
@ -301,14 +302,14 @@ set_vcard(User, LServer, VCARD) ->
US = {LUser, LServer},
VcardToStore = case gen_storage:table_info(LServer, vcard, backend) of
VcardToStore = case gen_storage:table_info(LServerB, vcard, backend) of
mnesia -> VCARD;
odbc -> lists:flatten(exmpp_xml:document_to_list(VCARD))
end,
F = fun() ->
gen_storage:write(LServer, #vcard{user_host = US, vcard = VcardToStore}),
gen_storage:write(LServer,
gen_storage:write(LServerB, #vcard{user_host = US, vcard = VcardToStore}),
gen_storage:write(LServerB,
#vcard_search{user_host=US,
username = User, lusername = LUser,
fn = FN, lfn = LFN,
@ -324,8 +325,8 @@ set_vcard(User, LServer, VCARD) ->
orgunit = OrgUnit, lorgunit = LOrgUnit
})
end,
gen_storage:transaction(LServer, vcard, F),
LServerB = list_to_binary(LServer),
gen_storage:transaction(LServerB, vcard, F),
ejabberd_hooks:run(vcard_set, LServerB, [list_to_binary(LUser), LServerB, VCARD])
catch
_ ->
@ -402,9 +403,8 @@ do_route(ServerHost, From, To, Packet) ->
Result = #xmlel{
ns = ?NS_SEARCH,
name = 'query',
children = [
#xmlel{
ns = ?NS_DATA_FORMS,
children =
[#xmlel{ns = ?NS_DATA_FORMS,
name = 'x',
attrs = [?XMLATTR('type',
<<"result">>)],
@ -729,10 +729,10 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
LServer = binary_to_list(exmpp_stringprep:nameprep(Server)),
US = {LUser, LServer},
F = fun() ->
gen_storage:delete(LServer, {vcard, US}),
gen_storage:delete(LServer, {vcard_search, US})
gen_storage:delete(Server, {vcard, US}),
gen_storage:delete(Server, {vcard_search, US})
end,
gen_storage:transaction(LServer, vcard, F).
gen_storage:transaction(Server, vcard, F).
%%%
@ -892,6 +892,7 @@ get_user_photo(User, Host) ->
user_queue_parse_query(US, Query) ->
{User, Server} = US,
?INFO_MSG("Query vcard: ~p", [Query]), %+++
case lists:keysearch("removevcard", 1, Query) of
{value, _} ->
case remove_user(list_to_binary(User), list_to_binary(Server)) of

View File

@ -172,7 +172,7 @@ start_link(Host, Opts) ->
init([Host, Opts]) ->
HostB = list_to_binary(Host),
State = parse_options(Host, Opts),
IQDisc = gen_mod:get_opt(iqdisc, Opts, parallel),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_VCARD,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_VCARD,
@ -439,9 +439,8 @@ route(State, From, To, Packet) ->
Result = #xmlel{
ns = ?NS_SEARCH,
name = 'query',
children = [
#xmlel{
ns = ?NS_DATA_FORMS,
children =
[#xmlel{ns = ?NS_DATA_FORMS,
name = 'x',
attrs = [?XMLATTR('type',
<<"result">>)],

View File

@ -464,12 +464,6 @@ record_to_item(LServer, {Username, FN, Family, Given, Middle,
search(LServer, Data) ->
MatchSpec = make_matchspec(LServer, Data),
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE,
allow_return_all, false),
if
(MatchSpec == "") and (not AllowReturnAll) ->
[];
true ->
Limit = case gen_mod:get_module_opt(LServer, ?MODULE,
matches, ?JUD_MATCHES) of
infinity ->
@ -491,7 +485,6 @@ search(LServer, Data) ->
Error ->
?ERROR_MSG("~p", [Error]),
[]
end
end.
@ -500,12 +493,7 @@ make_matchspec(LServer, Data) ->
filter_fields(Data, ["host = '", Host, "'"], LServer).
filter_fields([], Match, _LServer) ->
case Match of
"" ->
"";
_ ->
[" where ", Match]
end;
[" where ", Match];
filter_fields([{SVar, [Val]} | Ds], Match, LServer)
when is_list(Val) and (Val /= "") ->
LVal = exmpp_stringprep:to_lower(Val),
@ -539,12 +527,7 @@ make_val(Match, Field, Val) ->
SVal = ejabberd_odbc:escape(Val),
[Field, " = '", SVal, "'"]
end,
case Match of
"" ->
Condition;
_ ->
[Match, " and ", Condition]
end.
[Match, " and ", Condition].

View File

@ -59,7 +59,7 @@
rid = none,
key,
socket,
output = "",
output = [],
input = queue:new(),
waiting_input = false,
shaper_state,
@ -1157,7 +1157,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
case SEls of
[{xmlstreamelement,
#xmlel{name = 'features',
declared_ns = [{undefined, 'stream'}],
declared_ns = [{undefined, "stream"}],
attrs = StreamAttribs,
children = StreamEls}} |
% {xmlelement,

View File

@ -73,9 +73,7 @@
-define(CT, {"Content-Type", "text/xml; charset=utf-8"}).
-define(BAD_REQUEST, [?CT, {"Set-Cookie", "ID=-3:0; expires=-1"}]).
-define(PARSER_OPTIONS, [
{names_as_atom, true}
]).
-define(PARSER_OPTIONS, [{names_as_atom, true}]).
%%%----------------------------------------------------------------------
%%% API

View File

@ -1001,8 +1001,8 @@ process_admin(Host,
{value, {_, String}} ->
case parse_access_rule(String) of
{ok, Rs} ->
ejabberd_config:add_global_option(
{access, Name, Host}, Rs),
ejabberd_config:add_global_option
({access, Name, Host}, Rs),
ok;
_ ->
error
@ -1732,10 +1732,17 @@ user_info(User, Server, Query, Lang) ->
FIP = case ejabberd_sm:get_user_info(
UserB, ServerB, R) of
offline ->
NodePidS = "",
"";
Info when is_list(Info) ->
Node = proplists:get_value(node, Info),
NodeS = atom_to_list(Node),
Conn = proplists:get_value(conn, Info),
Priority = proplists:get_value(priority, Info),
Creation = proplists:get_value(creation, Info),
Pid = proplists:get_value(pid, Info),
PidS = pid_to_list(Pid),
NodePidS = NodeS ++ "/pid/" ++ PidS,
{IP, Port} = proplists:get_value(ip, Info),
ConnS = case Conn of
c2s -> "plain";
@ -1746,14 +1753,16 @@ user_info(User, Server, Query, Lang) ->
http_poll -> "http-poll"
end,
" (" ++
" #" ++ integer_to_list(Priority) ++ " " ++
ConnS ++ "://" ++
inet_parse:ntoa(IP) ++
":" ++
integer_to_list(Port)
++ "#" ++ atom_to_list(Node)
++ ")"
integer_to_list(Port) ++
"#" ++ NodeS ++
" " ++ Creation ++
") "
end,
?LI([?C(binary_to_list(R) ++ FIP)])
?LI([?C(binary_to_list(R) ++ FIP), ?AC("/admin/node/" ++ NodePidS ++ "/", "View Process")])
end, lists:sort(Resources)))]
end,
Password = ejabberd_auth:get_password_s(User, Server),
@ -2149,6 +2158,7 @@ get_node(global, Node, [], Query, Lang) ->
[?XE('ul',
[?LI([?ACT(Base ++ "db/", "Database")]),
?LI([?ACT(Base ++ "backup/", "Backup")]),
?LI([?ACT(Base ++ "pid/", "Erlang Processes")]),
?LI([?ACT(Base ++ "ports/", "Listened Ports")]),
?LI([?ACT(Base ++ "stats/", "Statistics")]),
?LI([?ACT(Base ++ "update/", "Update")])
@ -2326,6 +2336,71 @@ get_node(global, Node, ["backup"], Query, Lang) ->
])
])])];
get_node(global, Node, ["pid"], _Query, Lang) ->
NodeS = atom_to_list(Node),
Processes = rpc:call(Node, erlang, processes, []),
ProcessesNumber = length(Processes),
ProcessesList = lists:map(
fun(P) ->
PS = pid_to_list(P),
NodePidS = NodeS ++ "/pid/" ++ PS,
?AC("/admin/node/" ++ NodePidS ++ "/", PS)
end,
Processes),
[?XC('h1', io_lib:format(?T("Erlang Processes at node ~p"), [Node])),
?XAE('table', [],
[?XE('tbody',
[?XE('tr', [?XCT('td', "Number of processes:"),
?XAC('td', [?XMLATTR('class', <<"alignright">>)],
pretty_string_int(ProcessesNumber))])
])
]),
?XAE('p', [],
[?CT("Processes: ")] ++ ProcessesList
)];
get_node(global, Node, ["pid", PidS], _Query, Lang) ->
NodeS = atom_to_list(Node),
ProcessInfo = rpc:call(Node, erlang, process_info, [list_to_pid(PidS)]),
ProcessInfoS = io_lib:format("~p", [ProcessInfo]),
ProcLinkList = case re:run(ProcessInfoS, "<[0-9]+\\.[0-9]+\\.[0-9]+>",
[global, {capture, all, list}]) of
{match, PidsRareList} ->
lists:map(
fun([PS]) ->
NodePidS = NodeS ++ "/pid/" ++ PS,
?AC("/admin/node/" ++ NodePidS ++ "/", PS)
end,
PidsRareList);
_ ->
[]
end,
PortLinkList =
case proplists:get_value(links, ProcessInfo) of
Links when is_list(Links) ->
lists:foldl(
fun(Link, LinkRes) when is_port(Link) ->
PortInfo = rpc:call(Node, erlang, port_info, [Link]),
PortInfoString = io_lib:format("~p", [PortInfo]),
LinkRes ++ [{erlang:port_to_list(Link), PortInfoString}];
(_Link, LinkRes) -> LinkRes
end,
[],
Links);
_ ->
[]
end,
[?XC('h1', io_lib:format(?T("Erlang Process ~s at node ~p"), [PidS, Node])),
?XC('h3', ?T("Process Information:")),
?XAE('pre', [], [?C(ProcessInfoS)]),
?XC('h3', ?T("Related Processes:")),
?XAE('p', [], ProcLinkList),
?XC('h3', ?T("Linked Ports:")),
?XE('ul',
[ ?XE('li', [ ?C(PortName), ?BR, ?C(PortDescr) ])
|| {PortName, PortDescr} <- PortLinkList])
];
get_node(global, Node, ["ports"], Query, Lang) ->
Ports = rpc:call(Node, ejabberd_config, get_local_option, [listen]),
Res = case catch node_ports_parse_query(Node, Ports, Query) of
@ -2964,6 +3039,7 @@ make_node_menu(global, Node, Lang) ->
NodeBase = get_base_path(global, Node),
NodeFixed = [{"db/", "Database"},
{"backup/", "Backup"},
{"pid/", "Erlang Processes"},
{"ports/", "Listened Ports"},
{"stats/", "Statistics"},
{"update/", "Update"}]

View File

@ -16,7 +16,7 @@ prepare_dirs ()
EJA_SRC_DIR=$EJA_DIR/src/
EJA_MSGS_DIR=$EJA_SRC_DIR/msgs/
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
EXTRACT_DIR=$EJA_DIR/tools/extract_translations/
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam