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