1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
/* NitroShare - A simple network file sharing tool.
Copyright (C) 2012 Nathan Osman
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 3 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, see <http://www.gnu.org/licenses/>. */
#include <QDateTime>
#include <QDebug>
#include <QMutableMapIterator>
#include <QNetworkAddressEntry>
#include <qjson/parser.h>
#include <qjson/serializer.h>
#include <discovery/CBroadcastServer.h>
#include <util/network.h>
#include <util/settings.h>
CBroadcastServer::CBroadcastServer()
: m_start_time(QDateTime::currentMSecsSinceEpoch())
{
connect(this, SIGNAL(readyRead()), SLOT(OnMessage()));
connect(&m_timer, SIGNAL(timeout()), SLOT(OnTimer()));
}
void CBroadcastServer::Init()
{
/* Stop the timer and stop listening for incoming connections. */
close();
m_timer.stop();
/* Attempt to find the broadcast address on the default address. */
if(!Network::FindIPv4Address(Network::GetCurrentInterface(), Network::BroadcastAddress, &m_broadcast_address))
{
emit Error(tr("Unable to find an IPv4 broadcast address for the current interface."));
return;
}
if(!bind(Settings::Get("Network/BroadcastPort").toUInt(), QUdpSocket::ShareAddress))
{
emit Error(tr("Unable to listen for broadcast notifications."));
return;
}
/* Start the timer and send out an initial ping. */
m_timer.setInterval(Settings::Get("Network/BroadcastInterval").toInt());
m_timer.start();
OnTimer();
}
void CBroadcastServer::OnMessage()
{
/* This message indicates that we've received a datagram from one
of the other machines. We begin by reading the datagram. */
QByteArray message;
message.resize(pendingDatagramSize());
QHostAddress src;
readDatagram(message.data(), message.size(), &src);
/* The message is in JSON format, so we begin by decoding it. */
QVariantMap info = QJson::Parser().parse(message).toMap();
/* Ensure that the values we require were included in the message. */
if(info.contains("id") && info.contains("name") &&
info.contains("transmission_port") && info.contains("start_time"))
{
/* Make sure we don't accidentally detect ourselves. */
if(info["id"] != Settings::GetID())
UpdateMachineEntry(src, info["id"].toString(), info["name"].toByteArray(),
info["transmission_port"].toUInt(), info["start_time"].toLongLong());
}
else
qDebug() << "Invalid datagram received from" << src.toString();
}
void CBroadcastServer::OnTimer()
{
/* Construct the JSON map containing some information about our instance. */
QVariantMap info;
info["id"] = Settings::GetID();
info["name"] = Settings::Get("General/MachineName");
info["transmission_port"] = Settings::Get("Network/TransmissionPort");
info["start_time"] = m_start_time;
writeDatagram(QJson::Serializer().serialize(QVariant(info)),
m_broadcast_address, Settings::Get("Network/BroadcastPort").toUInt());
/* Check for any expired machine entries. */
CheckForExpiredMachines();
}
void CBroadcastServer::UpdateMachineEntry(QHostAddress host, QString id, QByteArray name,
quint16 transmission_port, qint64 start_time)
{
/* Check to see if this machine is already in our map. If not
then add it, checking to see if it has recently joined. */
if(!m_machines.contains(id))
{
/* The CMachine entry will get filled in later. */
m_machines.insert(id, CMachine());
/* The new machine tells us what time it joined the network, so we
can display a message if the user has enabled it. */
if(start_time > m_start_time && Settings::Notify("NewPC"))
emit Information(tr("%1 has joined the network.").arg(QString(name)));
}
/* Update our entry for the machine. */
m_machines[id].name = name;
m_machines[id].address = host;
m_machines[id].port = transmission_port;
m_machines[id].last_ping = QDateTime::currentMSecsSinceEpoch();
}
void CBroadcastServer::CheckForExpiredMachines()
{
qint64 current_time = QDateTime::currentMSecsSinceEpoch();
qint64 timeout = Settings::Get("Network/TimeoutInterval").toInt() * 1000;
/* Move through the map of machines, removing any that have expired. */
QMutableMapIterator<QString, CMachine> i(m_machines);
while(i.hasNext())
{
i.next();
/* If the last ping from this machine + the user-specified timeout
is greater than the current time, the machine is expired. */
if(i.value().last_ping + timeout < current_time)
{
if(Settings::Notify("PCQuit"))
emit Information(tr("%1 has left the network.").arg(QString(i.value().name)));
m_machines.remove(i.key());
}
}
}
|