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:
commit
ce9ce8293b
@ -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
|
||||
|
412
doc/dev.html
412
doc/dev.html
@ -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 –
|
||||
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  Key Features</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc2">2  Additional Features</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc3">3  How it Works</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc4">3.1  Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc5">3.2  Local Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc6">3.3  Session Manager</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc7">3.4  S2S Manager</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc8">4  Authentication</A>
|
||||
<UL CLASS="toc">
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc9">4.0.1  External</A>
|
||||
</LI></UL>
|
||||
</UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc10">5  XML Representation</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc11">6  Module <TT>xml</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc12">7  Module <TT>xml_stream</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc13">8  Modules</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc14">8.1  Module gen_iq_handler</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2  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>  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 ‘on the fly’.</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>  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>  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…</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>  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>  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’s content.</P><!--TOC subsection Session Manager-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc6">3.3</A>  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>  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>  Authentication</H2><!--SEC END --><!--TOC subsubsection External-->
|
||||
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A NAME="htoc9">4.0.1</A>  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’s a valid user)
|
||||
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user’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('>h', input_length)
|
||||
return sys.stdin.read(size).split(':')
|
||||
|
||||
def to_ejabberd(bool):
|
||||
answer = 0
|
||||
if bool:
|
||||
answer = 1
|
||||
token = pack('>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>  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. g. this stanza:
|
||||
</P><PRE CLASS="verbatim"><message to='test@conference.example.org' type='groupchat'>
|
||||
<body>test</body>
|
||||
</message>
|
||||
</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>  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) -> 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) -> 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) -> 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) -> 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>  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) -> 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>  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>  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) ->
|
||||
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) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_CPUTIME).
|
||||
|
||||
process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
case Type of
|
||||
set ->
|
||||
{iq, ID, error, XMLNS,
|
||||
[SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
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>  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) ->
|
||||
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost])).
|
||||
|
||||
init(Host) ->
|
||||
ejabberd_router:register_local_route(Host),
|
||||
loop(Host).
|
||||
|
||||
loop(Host) ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
ejabberd_router:route(To, From, Packet),
|
||||
loop(Host);
|
||||
stop ->
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok;
|
||||
_ ->
|
||||
loop(Host)
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
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>
|
@ -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 –
|
||||
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. —
|
||||
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. —
|
||||
Peter Saint-André, 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 ‘on the fly’.</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 —
|
||||
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>
|
4174
doc/guide.html
4174
doc/guide.html
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{3.0.0-alpha-x}
|
@ -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
|
@ -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'
|
@ -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:
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
563
src/cache_tab.erl
Normal 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
53
src/cache_tab_sup.erl
Normal 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
5977
src/configure
vendored
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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 = [_|_]} ->
|
||||
|
@ -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
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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)"},
|
||||
|
@ -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
|
||||
|
@ -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}]
|
||||
).
|
||||
|
@ -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;
|
||||
|
@ -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, {}).
|
||||
|
@ -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},
|
||||
|
@ -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 ->
|
||||
|
@ -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}.
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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]}}.
|
||||
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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}) ->
|
||||
|
@ -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()}).
|
||||
|
||||
|
@ -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}).
|
||||
|
@ -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,
|
||||
|
@ -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 = [
|
||||
|
@ -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
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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
@ -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}.
|
||||
|
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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],
|
||||
|
@ -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.
|
||||
|
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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).
|
||||
|
@ -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) ->
|
||||
|
@ -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{}).
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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".
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 = [
|
||||
|
@ -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
|
||||
|
@ -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">>)],
|
||||
|
@ -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].
|
||||
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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"}]
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user