]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_ircv3_batch.cpp
Add support for the IRCv3 batch specification.
[user/henk/code/inspircd.git] / src / modules / m_ircv3_batch.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21 #include "modules/cap.h"
22 #include "modules/ircv3_batch.h"
23
24 class BatchMessage : public ClientProtocol::Message
25 {
26  public:
27         BatchMessage(const IRCv3::Batch::Batch& batch, bool start)
28                 : ClientProtocol::Message("BATCH", ServerInstance->Config->ServerName)
29         {
30                 char c = (start ? '+' : '-');
31                 PushParam(std::string(1, c) + batch.GetRefTagStr());
32                 if ((start) && (!batch.GetType().empty()))
33                         PushParamRef(batch.GetType());
34         }
35 };
36
37 /** Extra structure allocated only for running batches, containing objects only relevant for
38  * that specific run of the batch.
39  */
40 struct IRCv3::Batch::BatchInfo
41 {
42         /** List of users that have received the batch start message
43          */
44         std::vector<LocalUser*> users;
45         BatchMessage startmsg;
46         ClientProtocol::Event startevent;
47
48         BatchInfo(ClientProtocol::EventProvider& protoevprov, IRCv3::Batch::Batch& b)
49                 : startmsg(b, true)
50                 , startevent(protoevprov, startmsg)
51         {
52         }
53 };
54
55 class IRCv3::Batch::ManagerImpl : public Manager
56 {
57         typedef std::vector<Batch*> BatchList;
58
59         Cap::Capability cap;
60         ClientProtocol::EventProvider protoevprov;
61         LocalIntExt batchbits;
62         BatchList active_batches;
63         bool unloading;
64
65         bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
66         {
67                 if (!cap.get(user))
68                         return false;
69
70                 Batch& batch = *static_cast<Batch*>(tagdata.provdata);
71                 // Check if this is the first message the user is getting that is part of the batch
72                 const intptr_t bits = batchbits.get(user);
73                 if (!(bits & batch.GetBit()))
74                 {
75                         // Send the start batch command ("BATCH +reftag TYPE"), remember the user so we can send them a
76                         // "BATCH -reftag" message later when the batch ends and set the flag we just checked so this is
77                         // only done once per user per batch.
78                         batchbits.set(user, (bits | batch.GetBit()));
79                         batch.batchinfo->users.push_back(user);
80                         user->Send(batch.batchinfo->startevent);
81                 }
82
83                 return true;
84         }
85
86         unsigned int NextFreeId() const
87         {
88                 if (active_batches.empty())
89                         return 0;
90                 return active_batches.back()->GetId()+1;
91         }
92
93  public:
94         ManagerImpl(Module* mod)
95                 : Manager(mod)
96                 , cap(mod, "batch")
97                 , protoevprov(mod, "BATCH")
98                 , batchbits("batchbits", ExtensionItem::EXT_USER, mod)
99                 , unloading(false)
100         {
101         }
102
103         void Init()
104         {
105                 // Set batchbits to 0 for all users in case we were reloaded and the previous, now meaningless,
106                 // batchbits are set on users
107                 const UserManager::LocalList& users = ServerInstance->Users.GetLocalUsers();
108                 for (UserManager::LocalList::const_iterator i = users.begin(); i != users.end(); ++i)
109                 {
110                         LocalUser* const user = *i;
111                         batchbits.set(user, 0);
112                 }
113         }
114
115         void Shutdown()
116         {
117                 unloading = true;
118                 while (!active_batches.empty())
119                         ManagerImpl::End(*active_batches.back());
120         }
121
122         void RemoveFromAll(LocalUser* user)
123         {
124                 const intptr_t bits = batchbits.get(user);
125
126                 // User is quitting, remove them from all lists
127                 for (BatchList::iterator i = active_batches.begin(); i != active_batches.end(); ++i)
128                 {
129                         Batch& batch = **i;
130                         // Check the bit first to avoid list scan in case they're not on the list
131                         if ((bits & batch.GetBit()) != 0)
132                                 stdalgo::vector::swaperase(batch.batchinfo->users, user);
133                 }
134         }
135
136         void Start(Batch& batch) CXX11_OVERRIDE
137         {
138                 if (unloading)
139                         return;
140
141                 if (batch.IsRunning())
142                         return; // Already started, don't start again
143
144                 const size_t id = NextFreeId();
145                 if (id >= MAX_BATCHES)
146                         return;
147
148                 batch.Setup(id);
149                 // Set the manager field which Batch::IsRunning() checks and is also used by AddToBatch()
150                 // to set the message tag
151                 batch.manager = this;
152                 batch.batchinfo = new IRCv3::Batch::BatchInfo(protoevprov, batch);
153                 batch.batchstartmsg = &batch.batchinfo->startmsg;
154                 active_batches.push_back(&batch);
155         }
156
157         void End(Batch& batch) CXX11_OVERRIDE
158         {
159                 if (!batch.IsRunning())
160                         return;
161
162                 // Mark batch as stopped
163                 batch.manager = NULL;
164
165                 BatchInfo& batchinfo = *batch.batchinfo;
166                 // Send end batch message to all users who got the batch start message and unset bit so it can be reused
167                 BatchMessage endbatchmsg(batch, false);
168                 ClientProtocol::Event endbatchevent(protoevprov, endbatchmsg);
169                 for (std::vector<LocalUser*>::const_iterator i = batchinfo.users.begin(); i != batchinfo.users.end(); ++i)
170                 {
171                         LocalUser* const user = *i;
172                         user->Send(endbatchevent);
173                         batchbits.set(user, batchbits.get(user) & ~batch.GetBit());
174                 }
175
176                 // erase() not swaperase because the reftag generation logic depends on the order of the elements
177                 stdalgo::erase(active_batches, &batch);
178                 delete batch.batchinfo;
179                 batch.batchinfo = NULL;
180         }
181 };
182
183 class ModuleIRCv3Batch : public Module
184 {
185         IRCv3::Batch::ManagerImpl manager;
186
187  public:
188         ModuleIRCv3Batch()
189                 : manager(this)
190         {
191         }
192
193         void init() CXX11_OVERRIDE
194         {
195                 manager.Init();
196         }
197
198         void OnUnloadModule(Module* mod) CXX11_OVERRIDE
199         {
200                 if (mod == this)
201                         manager.Shutdown();
202         }
203
204         void OnUserDisconnect(LocalUser* user) CXX11_OVERRIDE
205         {
206                 // Remove the user from all internal lists
207                 manager.RemoveFromAll(user);
208         }
209
210         Version GetVersion() CXX11_OVERRIDE
211         {
212                 return Version("Provides the batch IRCv3 extension", VF_VENDOR);
213         }
214 };
215
216 MODULE_INIT(ModuleIRCv3Batch)