Switch to unified view

a/libupnpp/discovery.cxx b/libupnpp/discovery.cxx
...
...
20
#include <errno.h>
20
#include <errno.h>
21
#include <unistd.h>
21
#include <unistd.h>
22
22
23
#include <iostream>
23
#include <iostream>
24
#include <map>
24
#include <map>
25
#include <functional>
25
using namespace std;
26
using namespace std;
27
using namespace std::placeholders;
26
28
27
#include "upnpp_p.hxx"
29
#include "upnpp_p.hxx"
28
30
29
#include <upnp/upnp.h>
31
#include <upnp/upnp.h>
30
#include <upnp/upnptools.h>
32
#include <upnp/upnptools.h>
31
33
32
#include "workqueue.hxx"
34
#include "workqueue.hxx"
33
#include "expatmm.hxx"
35
#include "expatmm.hxx"
34
#include "upnpplib.hxx"
36
#include "upnpplib.hxx"
35
#include "description.hxx"
37
#include "description.hxx"
36
#include "cdirectory.hxx"
37
#include "discovery.hxx"
38
#include "discovery.hxx"
38
#include "log.hxx"
39
#include "log.hxx"
39
40
41
namespace UPnPClient {
42
40
#undef LOCAL_LOGINC
43
//#undef LOCAL_LOGINC
41
#define LOCAL_LOGINC 0
44
//#define LOCAL_LOGINC 3
42
43
// The service type string for Content Directories:
44
static const string
45
ContentDirectorySType("urn:schemas-upnp-org:service:ContentDirectory:1");
46
47
// We don't include a version in comparisons, as we are satisfied with
48
// version 1
49
static bool isCDService(const string& st)
50
{
51
    const string::size_type sz(ContentDirectorySType.size()-2);
52
    return !ContentDirectorySType.compare(0, sz, st, 0, sz);
53
}
54
55
// The device type string for Media Servers
56
static const string
57
MediaServerDType("urn:schemas-upnp-org:device:MediaServer:1") ;
58
59
#if 0
60
static bool isMSDevice(const string& st)
61
{
62
    const string::size_type sz(MediaServerDType.size()-2);
63
    return !MediaServerDType.compare(0, sz, st, 0, sz);
64
}
65
#endif
66
45
67
static string cluDiscoveryToStr(const struct Upnp_Discovery *disco)
46
static string cluDiscoveryToStr(const struct Upnp_Discovery *disco)
68
{
47
{
69
    stringstream ss;
48
    stringstream ss;
70
    ss << "ErrCode: " << disco->ErrCode << endl;
49
    ss << "ErrCode: " << disco->ErrCode << endl;
...
...
98
    string deviceId;
77
    string deviceId;
99
    int expires; // Seconds valid
78
    int expires; // Seconds valid
100
};
79
};
101
static WorkQueue<DiscoveredTask*> discoveredQueue("DiscoveredQueue");
80
static WorkQueue<DiscoveredTask*> discoveredQueue("DiscoveredQueue");
102
81
103
// Descriptor for one device having a Content Directory service found
82
// This gets called in a libupnp thread context for all asynchronous
104
// on the network.
83
// events which we asked for.
84
// Example: ContentDirectories appearing and disappearing from the network
85
// We queue a task for our worker thread(s)
86
// It seems that this can get called by several threads. We have a
87
// mutex just for clarifying the message printing, the workqueue is
88
// mt-safe of course.
89
static PTMutexInit cblock;
90
static int cluCallBack(Upnp_EventType et, void* evp, void*)
91
{
92
    PTMutexLocker lock(cblock);
93
    LOGDEB1("discovery:cluCallBack: " << LibUPnP::evTypeAsString(et) << endl);
94
95
    switch (et) {
96
    case UPNP_DISCOVERY_SEARCH_RESULT:
97
    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
98
    {
99
        struct Upnp_Discovery *disco = (struct Upnp_Discovery *)evp;
100
        // Devices send multiple messages for themselves, their subdevices and 
101
        // services. AFAIK they all point to the same description.xml document,
102
        // which has all the interesting data. So let's try to only process
103
        // one message per device: the one which probably correspond to the 
104
        // upnp "root device" message and has empty service and device types:
105
        if (!disco->DeviceType[0] && !disco->ServiceType[0]) {
106
            LOGDEB1("discovery:cllb:ALIVE: " << cluDiscoveryToStr(disco) 
107
                    << endl);
108
            DiscoveredTask *tp = new DiscoveredTask(1, disco);
109
            if (discoveredQueue.put(tp)) {
110
                return UPNP_E_FINISH;
111
            }
112
        }
113
        break;
114
    }
115
    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
116
    {
117
        struct Upnp_Discovery *disco = (struct Upnp_Discovery *)evp;
118
        //LOGDEB("discovery:cllB:BYEBYE: " << cluDiscoveryToStr(disco) << endl);
119
        DiscoveredTask *tp = new DiscoveredTask(0, disco);
120
        if (discoveredQueue.put(tp)) {
121
            return UPNP_E_FINISH;
122
        }
123
        break;
124
    }
125
    default:
126
        // Ignore other events for now
127
        LOGDEB("discovery:cluCallBack: unprocessed evt type: [" << 
128
               LibUPnP::evTypeAsString(et) << "]"  << endl);
129
        break;
130
    }
131
132
    return UPNP_E_SUCCESS;
133
}
134
135
// Descriptor for one device found on the network.
105
class DeviceDescriptor {
136
class DeviceDescriptor {
106
public:
137
public:
107
    DeviceDescriptor(const string& url, const string& description,
138
    DeviceDescriptor(const string& url, const string& description,
108
                     time_t last, int exp)
139
                     time_t last, int exp)
109
        : device(url, description), last_seen(last), expires(exp+20)
140
        : device(url, description), last_seen(last), expires(exp+20)
...
...
137
        size_t qsz;
168
        size_t qsz;
138
        if (!discoveredQueue.take(&tsk, &qsz)) {
169
        if (!discoveredQueue.take(&tsk, &qsz)) {
139
            discoveredQueue.workerExit();
170
            discoveredQueue.workerExit();
140
            return (void*)1;
171
            return (void*)1;
141
        }
172
        }
142
        PLOGDEB("discoExplorer: alive %d deviceId [%s] URL [%s]\n",
173
        LOGDEB1("discoExplorer: got task: alive " << tsk->alive << " deviceId ["
143
                tsk->alive, tsk->deviceId.c_str(), tsk->url.c_str());
174
                << tsk->deviceId << " URL [" << tsk->url << "]" << endl);
175
144
        if (!tsk->alive) {
176
        if (!tsk->alive) {
145
            // Device signals it is going off.
177
            // Device signals it is going off.
146
            PTMutexLocker lock(o_pool.m_mutex);
178
            PTMutexLocker lock(o_pool.m_mutex);
147
            DevPoolIt it = o_pool.m_devices.find(tsk->deviceId);
179
            DevPoolIt it = o_pool.m_devices.find(tsk->deviceId);
148
            if (it != o_pool.m_devices.end()) {
180
            if (it != o_pool.m_devices.end()) {
...
...
163
                continue;
195
                continue;
164
            }
196
            }
165
            string sdesc(buf);
197
            string sdesc(buf);
166
            free(buf);
198
            free(buf);
167
                        
199
                        
168
            //LOGDEB("discoExplorer: downloaded description document of " <<
200
            LOGDEB1("discoExplorer: downloaded description document of " <<
169
            //   sdesc.size() << " bytes" << endl);
201
                    sdesc.size() << " bytes" << endl);
170
202
171
            // Update or insert the device
203
            // Update or insert the device
172
            DeviceDescriptor d(tsk->url, sdesc, time(0), tsk->expires);
204
            DeviceDescriptor d(tsk->url, sdesc, time(0), tsk->expires);
173
            if (!d.device.ok) {
205
            if (!d.device.ok) {
174
                LOGERR("discoExplorer: description parse failed for " << 
206
                LOGERR("discoExplorer: description parse failed for " << 
175
                       tsk->deviceId << endl);
207
                       tsk->deviceId << endl);
176
                delete tsk;
208
                delete tsk;
177
                continue;
209
                continue;
178
            }
210
            }
179
            PTMutexLocker lock(o_pool.m_mutex);
211
            PTMutexLocker lock(o_pool.m_mutex);
180
            //LOGDEB("discoExplorer: inserting device id "<< tsk->deviceId << 
212
            //LOGDEB1("discoExplorer: inserting device id "<< tsk->deviceId << 
181
            //       " description: " << endl << d.device.dump() << endl);
213
            //        " description: " << endl << d.device.dump() << endl);
182
            o_pool.m_devices[tsk->deviceId] = d;
214
            o_pool.m_devices[tsk->deviceId] = d;
183
        }
215
        }
184
        delete tsk;
216
        delete tsk;
185
    }
217
    }
186
}
187
188
// This gets called in a libupnp thread context for all asynchronous
189
// events which we asked for.
190
// Example: ContentDirectories appearing and disappearing from the network
191
// We queue a task for our worker thread(s)
192
// It seems that this can get called by several threads. We have a
193
// mutex just for clarifying the message printing, the workqueue is
194
// mt-safe of course.
195
static PTMutexInit cblock;
196
static int cluCallBack(Upnp_EventType et, void* evp, void*)
197
{
198
    PTMutexLocker lock(cblock);
199
    //LOGDEB("discovery:cluCallBack: " << LibUPnP::evTypeAsString(et) << endl);
200
201
    switch (et) {
202
    case UPNP_DISCOVERY_SEARCH_RESULT:
203
    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
204
    {
205
        struct Upnp_Discovery *disco = (struct Upnp_Discovery *)evp;
206
        //LOGDEB("discovery:cllb:ALIVE: " << cluDiscoveryToStr(disco) << endl);
207
        DiscoveredTask *tp = new DiscoveredTask(1, disco);
208
        if (discoveredQueue.put(tp)) {
209
            return UPNP_E_FINISH;
210
        }
211
        break;
212
    }
213
    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
214
    {
215
        struct Upnp_Discovery *disco = (struct Upnp_Discovery *)evp;
216
        //LOGDEB("discovery:cllB:BYEBYE: " << cluDiscoveryToStr(disco) << endl);
217
        DiscoveredTask *tp = new DiscoveredTask(0, disco);
218
        if (discoveredQueue.put(tp)) {
219
            return UPNP_E_FINISH;
220
        }
221
        break;
222
    }
223
    default:
224
        // Ignore other events for now
225
        LOGDEB("discovery:cluCallBack: unprocessed evt type: [" << 
226
               LibUPnP::evTypeAsString(et) << "]"  << endl);
227
        break;
228
    }
229
230
    return UPNP_E_SUCCESS;
231
}
218
}
232
219
233
// Look at the devices and get rid of those which have not been seen
220
// Look at the devices and get rid of those which have not been seen
234
// for too long. We do this when listing the top directory
221
// for too long. We do this when listing the top directory
235
void UPnPDeviceDirectory::expireDevices()
222
void UPnPDeviceDirectory::expireDevices()
...
...
239
    time_t now = time(0);
226
    time_t now = time(0);
240
    bool didsomething = false;
227
    bool didsomething = false;
241
228
242
    for (DevPoolIt it = o_pool.m_devices.begin();
229
    for (DevPoolIt it = o_pool.m_devices.begin();
243
         it != o_pool.m_devices.end();) {
230
         it != o_pool.m_devices.end();) {
231
        LOGDEB1("Dev in pool: type: " << it->second.device.deviceType <<
232
                " friendlyName " << it->second.device.friendlyName << endl);
244
        if (now - it->second.last_seen > it->second.expires) {
233
        if (now - it->second.last_seen > it->second.expires) {
245
            //LOGDEB("expireDevices: deleting " <<  it->first.c_str() << " " << 
234
            //LOGDEB("expireDevices: deleting " <<  it->first.c_str() << " " << 
246
            //   it->second.device.friendlyName.c_str() << endl);
235
            //   it->second.device.friendlyName.c_str() << endl);
247
            o_pool.m_devices.erase(it++);
236
            o_pool.m_devices.erase(it++);
248
            didsomething = true;
237
            didsomething = true;
...
...
264
{
253
{
265
    if (!discoveredQueue.start(1, discoExplorer, 0)) {
254
    if (!discoveredQueue.start(1, discoExplorer, 0)) {
266
        m_reason = "Discover work queue start failed";
255
        m_reason = "Discover work queue start failed";
267
        return;
256
        return;
268
    }
257
    }
258
    pthread_yield();
269
    LibUPnP *lib = LibUPnP::getLibUPnP();
259
    LibUPnP *lib = LibUPnP::getLibUPnP();
270
    if (lib == 0) {
260
    if (lib == 0) {
271
        m_reason = "Can't get lib";
261
        m_reason = "Can't get lib";
272
        return;
262
        return;
273
    }
263
    }
...
...
280
    m_ok = search();
270
    m_ok = search();
281
}
271
}
282
272
283
bool UPnPDeviceDirectory::search()
273
bool UPnPDeviceDirectory::search()
284
{
274
{
285
    PLOGDEB("UPnPDeviceDirectory::search\n");
275
    LOGDEB1("UPnPDeviceDirectory::search" << endl);
286
    if (time(0) - m_lastSearch < 10)
276
    if (time(0) - m_lastSearch < 10)
287
        return true;
277
        return true;
288
278
289
    LibUPnP *lib = LibUPnP::getLibUPnP();
279
    LibUPnP *lib = LibUPnP::getLibUPnP();
290
    if (lib == 0) {
280
    if (lib == 0) {
291
        m_reason = "Can't get lib";
281
        m_reason = "Can't get lib";
292
        return false;
282
        return false;
293
    }
283
    }
294
284
295
    // We search both for device and service just in case.
285
    LOGDEB1("UPnPDeviceDirectory::search: calling upnpsearchasync"<<endl);
286
    //const char *cp = "ssdp:all";
287
    const char *cp = "upnp:rootdevice";
296
    int code1 = UpnpSearchAsync(lib->getclh(), m_searchTimeout,
288
    int code1 = UpnpSearchAsync(lib->getclh(), m_searchTimeout, cp, lib);
297
                                ContentDirectorySType.c_str(), lib);
298
    if (code1 != UPNP_E_SUCCESS) {
289
    if (code1 != UPNP_E_SUCCESS) {
299
        m_reason = LibUPnP::errAsString("UpnpSearchAsync", code1);
290
        m_reason = LibUPnP::errAsString("UpnpSearchAsync", code1);
291
        LOGERR("UPnPDeviceDirectory::search: UpnpSearchAsync failed: " <<
292
               m_reason << endl);
300
    }
293
    }
301
    int code2 = UpnpSearchAsync(lib->getclh(), m_searchTimeout,
302
                                MediaServerDType.c_str(), lib);
303
    if (code2 != UPNP_E_SUCCESS) {
304
        m_reason = LibUPnP::errAsString("UpnpSearchAsync", code2);
305
    }
306
    if (code1 != UPNP_E_SUCCESS && code2 != UPNP_E_SUCCESS)
307
        return false;
308
    m_lastSearch = time(0);
294
    m_lastSearch = time(0);
309
    return true;
295
    return true;
310
}
296
}
311
297
312
static UPnPDeviceDirectory *theDevDir;
298
static UPnPDeviceDirectory *theDevDir;
...
...
325
}
311
}
326
312
327
time_t UPnPDeviceDirectory::getRemainingDelay()
313
time_t UPnPDeviceDirectory::getRemainingDelay()
328
{
314
{
329
    time_t now = time(0);
315
    time_t now = time(0);
330
    if (now - m_lastSearch >= m_searchTimeout)
316
    if (now - m_lastSearch >= m_searchTimeout+1)
331
        return 0;
317
        return 0;
332
    return  m_searchTimeout - (now - m_lastSearch);
318
    return  m_searchTimeout - (now - m_lastSearch);
333
}
319
}
334
320
335
bool UPnPDeviceDirectory::getDirServices(vector<ContentDirectoryService>& out)
321
bool UPnPDeviceDirectory::traverse(UPnPDeviceDirectory::Visitor visit)
336
{
322
{
337
    //LOGDEB("UPnPDeviceDirectory::getDirServices" << endl);
323
    //LOGDEB("UPnPDeviceDirectory::traverse" << endl);
338
    if (m_ok == false)
324
    if (m_ok == false)
339
        return false;
325
        return false;
340
326
341
    if (getRemainingDelay() > 0)
327
    if (getRemainingDelay() > 0)
342
        sleep(getRemainingDelay());
328
        sleep(getRemainingDelay());
...
...
344
    // Has locking, do it before our own lock
330
    // Has locking, do it before our own lock
345
    expireDevices();
331
    expireDevices();
346
332
347
    PTMutexLocker lock(o_pool.m_mutex);
333
    PTMutexLocker lock(o_pool.m_mutex);
348
334
349
    for (DevPoolIt dit = o_pool.m_devices.begin();
335
    for (auto& dde : o_pool.m_devices) {
350
         dit != o_pool.m_devices.end(); dit++) {
336
        for (auto& service : dde.second.device.services) {
351
        for (DevServIt sit = dit->second.device.services.begin();
337
            if (!visit(dde.second.device, service))
352
             sit != dit->second.device.services.end(); sit++) {
338
                return false;
353
            if (isCDService(sit->serviceType)) {
354
                out.push_back(ContentDirectoryService(dit->second.device,
355
                                                      *sit));
356
            }
339
        }
357
        }
340
    }
358
    }
359
360
    return true;
341
    return true;
361
}
342
}
362
343
363
// Get server by friendly name. It's a bit wasteful to copy all
344
} // namespace UPnPClient
364
// servers for this, we could directly walk the list. Otoh there isn't
365
// going to be millions...
366
bool UPnPDeviceDirectory::getServer(const string& friendlyName,
367
                                    ContentDirectoryService& server)
368
{
369
    vector<ContentDirectoryService> ds;
370
    if (!getDirServices(ds)) {
371
        PLOGDEB("UPnPDeviceDirectory::getServer: no servers?\n");
372
        return false;
373
    }
374
    for (vector<ContentDirectoryService>::const_iterator it = ds.begin();
375
         it != ds.end(); it++) {
376
        if (!friendlyName.compare(it->getFriendlyName())) {
377
            server = *it;
378
            return true;
379
        }
380
    }
381
    return false;
382
}
383
345