示例#1
0
        private void CreateNotifier(IPAddress localIp)
        {
            Task.Factory.StartNew(async(o) =>
            {
                try
                {
                    while (true)
                    {
                        _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));

                        var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;

                        await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException)
                {
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error in notifier", ex);
                }
            }, _tokenSource.Token, TaskCreationOptions.LongRunning);
        }
示例#2
0
        private async void ReloadComponents()
        {
            var options = _config.GetDlnaConfiguration();

            StartSsdpHandler();

            if (options.EnableServer)
            {
                await StartDevicePublisher().ConfigureAwait(false);
            }
            else
            {
                DisposeDevicePublisher();
            }

            if (options.EnablePlayTo)
            {
                StartPlayToManager();
            }
            else
            {
                DisposePlayToManager();
            }
        }
示例#3
0
        private async void OnMessageReceived(SsdpMessageEventArgs args)
        {
            if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
            {
                var mx = args.Headers["mx"];
                int delaySeconds;
                if (!string.IsNullOrWhiteSpace(mx) &&
                    int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds) &&
                    delaySeconds > 0)
                {
                    if (_config.GetDlnaConfiguration().EnableDebugLogging)
                    {
                        _logger.Debug("Delaying search response by {0} seconds", delaySeconds);
                    }

                    await Task.Delay(delaySeconds * 1000).ConfigureAwait(false);
                }

                RespondToSearch(args.EndPoint, args.Headers["st"]);
            }

            EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
        }
示例#4
0
        // Call this method from somewhere in your code to start the search.
        public void Start(ISsdpCommunicationsServer communicationsServer)
        {
            _deviceLocator = new SsdpDeviceLocator(communicationsServer, _timerFactory);

            // (Optional) Set the filter so we only see notifications for devices we care about
            // (can be any search target value i.e device type, uuid value etc - any value that appears in the
            // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
            //_DeviceLocator.NotificationFilter = "upnp:rootdevice";

            // Connect our event handler so we process devices as they are found
            _deviceLocator.DeviceAvailable   += deviceLocator_DeviceAvailable;
            _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;

            var dueTime  = TimeSpan.FromSeconds(5);
            var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);

            _deviceLocator.RestartBroadcastTimer(dueTime, interval);
        }
示例#5
0
        private async void OnMessageReceived(SsdpMessageEventArgs args)
        {
            if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
            {
                var headers = args.Headers;

                TimeSpan delay = GetSearchDelay(headers);

                if (_config.GetDlnaConfiguration().EnableDebugLogging)
                {
                    _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds);
                }

                await Task.Delay(delay).ConfigureAwait(false);

                string st;
                if (headers.TryGetValue("st", out st))
                {
                    RespondToSearch(args.EndPoint, st);
                }
            }

            EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
        }
示例#6
0
        private async void ReloadComponents()
        {
            var options = _config.GetDlnaConfiguration();

            if (!options.EnableServer && !options.EnablePlayTo && !_config.Configuration.EnableUPnP)
            {
                if (_ssdpHandlerStarted)
                {
                    // Sat/ip live tv depends on device discovery, as well as hd homerun detection
                    // In order to allow this to be disabled, we need a modular way of knowing if there are
                    // any parts of the system that are dependant on it
                    // DisposeSsdpHandler();
                }
                return;
            }

            if (!_ssdpHandlerStarted)
            {
                StartSsdpHandler();
            }

            var isServerStarted = _dlnaServerStarted;

            if (options.EnableServer && !isServerStarted)
            {
                await StartDlnaServer().ConfigureAwait(false);
            }
            else if (!options.EnableServer && isServerStarted)
            {
                DisposeDlnaServer();
            }

            var isPlayToStarted = _manager != null;

            if (options.EnablePlayTo && !isPlayToStarted)
            {
                StartPlayToManager();
            }
            else if (!options.EnablePlayTo && isPlayToStarted)
            {
                DisposePlayToManager();
            }
        }
示例#7
0
        private Task <HttpResponseInfo> PostSoapDataAsync(
            string url,
            string soapAction,
            string postData,
            string header,
            bool logRequest,
            CancellationToken cancellationToken)
        {
            if (soapAction[0] != '\"')
            {
                soapAction = '\"' + soapAction + '\"';
            }

            var options = new HttpRequestOptions
            {
                Url                  = url,
                UserAgent            = USERAGENT,
                LogRequest           = logRequest || _config.GetDlnaConfiguration().EnableDebugLog,
                LogErrorResponseBody = true,
                BufferContent        = false,

                // The periodic requests may keep some devices awake
                LogRequestAsDebug = true,

                CancellationToken = cancellationToken
            };

            options.RequestHeaders["SOAPAction"]            = soapAction;
            options.RequestHeaders["Pragma"]                = "no-cache";
            options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;

            if (!string.IsNullOrEmpty(header))
            {
                options.RequestHeaders["contentFeatures.dlna.org"] = header;
            }

            options.RequestContentType      = "text/xml";
            options.AppendCharsetToMimeType = true;
            options.RequestContent          = postData;

            return(_httpClient.Post(options));
        }
示例#8
0
        private User GetUser(DeviceProfile profile)
        {
            if (!string.IsNullOrEmpty(profile.UserId))
            {
                var user = _userManager.GetUserById(profile.UserId);

                if (user != null)
                {
                    return(user);
                }
            }

            var userId = _config.GetDlnaConfiguration().DefaultUserId;

            if (!string.IsNullOrEmpty(userId))
            {
                var user = _userManager.GetUserById(userId);

                if (user != null)
                {
                    return(user);
                }
            }

            foreach (var user in _userManager.Users)
            {
                if (user.Policy.IsAdministrator)
                {
                    return(user);
                }
            }

            foreach (var user in _userManager.Users)
            {
                return(user);
            }

            return(null);
        }
示例#9
0
        private void StartInternal()
        {
            lock (_syncLock)
            {
                if (_listenerCount > 0 && _deviceLocator == null)
                {
                    _deviceLocator = new SsdpDeviceLocator(_commsServer);

                    // (Optional) Set the filter so we only see notifications for devices we care about
                    // (can be any search target value i.e device type, uuid value etc - any value that appears in the
                    // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
                    //_DeviceLocator.NotificationFilter = "upnp:rootdevice";

                    // Connect our event handler so we process devices as they are found
                    _deviceLocator.DeviceAvailable   += OnDeviceLocatorDeviceAvailable;
                    _deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable;

                    var dueTime  = TimeSpan.FromSeconds(5);
                    var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);

                    _deviceLocator.RestartBroadcastTimer(dueTime, interval);
                }
            }
        }
示例#10
0
        private void StartAsyncSearch()
        {
            Task.Factory.StartNew(async(o) =>
            {
                while (!_tokenSource.IsCancellationRequested)
                {
                    try
                    {
                        // Enable listening for notifications (optional)
                        _deviceLocator.StartListeningForNotifications();

                        await _deviceLocator.SearchAsync().ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        _logger.ErrorException("Error searching for devices", ex);
                    }

                    var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;

                    await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
                }
            }, CancellationToken.None, TaskCreationOptions.LongRunning);
        }
示例#11
0
        public DlnaEntryPoint(
            IServerConfigurationManager config,
            ILoggerFactory loggerFactory,
            IServerApplicationHost appHost,
            ISessionManager sessionManager,
            IHttpClientFactory httpClientFactory,
            ILibraryManager libraryManager,
            IUserManager userManager,
            IDlnaManager dlnaManager,
            IImageProcessor imageProcessor,
            IUserDataManager userDataManager,
            ILocalizationManager localizationManager,
            IMediaSourceManager mediaSourceManager,
            IDeviceDiscovery deviceDiscovery,
            IMediaEncoder mediaEncoder,
            ISocketFactory socketFactory,
            INetworkManager networkManager,
            IUserViewManager userViewManager,
            ITVSeriesManager tvSeriesManager)
        {
            _config             = config;
            _appHost            = appHost;
            _sessionManager     = sessionManager;
            _httpClientFactory  = httpClientFactory;
            _libraryManager     = libraryManager;
            _userManager        = userManager;
            _dlnaManager        = dlnaManager;
            _imageProcessor     = imageProcessor;
            _userDataManager    = userDataManager;
            _localization       = localizationManager;
            _mediaSourceManager = mediaSourceManager;
            _deviceDiscovery    = deviceDiscovery;
            _mediaEncoder       = mediaEncoder;
            _socketFactory      = socketFactory;
            _networkManager     = networkManager;
            _logger             = loggerFactory.CreateLogger <DlnaEntryPoint>();

            ContentDirectory = new ContentDirectory.ContentDirectoryService(
                dlnaManager,
                userDataManager,
                imageProcessor,
                libraryManager,
                config,
                userManager,
                loggerFactory.CreateLogger <ContentDirectory.ContentDirectoryService>(),
                httpClientFactory,
                localizationManager,
                mediaSourceManager,
                userViewManager,
                mediaEncoder,
                tvSeriesManager);

            ConnectionManager = new ConnectionManager.ConnectionManagerService(
                dlnaManager,
                config,
                loggerFactory.CreateLogger <ConnectionManager.ConnectionManagerService>(),
                httpClientFactory);

            MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
                loggerFactory.CreateLogger <MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
                httpClientFactory,
                config);
            Current = this;

            var netConfig = config.GetConfiguration <NetworkConfiguration>(NetworkConfigurationStore.StoreKey);

            _disabled = appHost.ListenWithHttps && netConfig.RequireHttps;

            if (_disabled && _config.GetDlnaConfiguration().EnableServer)
            {
                _logger.LogError("The DLNA specification does not support HTTPS.");
            }
        }
示例#12
0
        private IEnumerable <KeyValuePair <string, string> > HandleBrowse(IDictionary <string, string> sparams, User user, string deviceId)
        {
            var id           = sparams["ObjectID"];
            var flag         = sparams["BrowseFlag"];
            var filter       = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
            var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));

            var provided = 0;

            // Default to null instead of 0
            // Upnp inspector sends 0 as requestedCount when it wants everything
            int?requestedCount = null;
            int?start          = 0;

            int requestedVal;

            if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requestedVal) && requestedVal > 0)
            {
                requestedCount = requestedVal;
            }

            int startVal;

            if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out startVal) && startVal > 0)
            {
                start = startVal;
            }

            var settings = new XmlWriterSettings
            {
                Encoding           = Encoding.UTF8,
                CloseOutput        = false,
                OmitXmlDeclaration = true,
                ConformanceLevel   = ConformanceLevel.Fragment
            };

            StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);

            int totalCount;

            using (XmlWriter writer = XmlWriter.Create(builder, settings))
            {
                //writer.WriteStartDocument();

                writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);

                writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
                writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
                writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
                //didl.SetAttribute("xmlns:sec", NS_SEC);

                DidlBuilder.WriteXmlRootAttributes(_profile, writer);

                var serverItem = GetItemFromObjectId(id, user);
                var item       = serverItem.Item;

                if (string.Equals(flag, "BrowseMetadata"))
                {
                    totalCount = 1;

                    if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
                    {
                        var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));

                        _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
                    }
                    else
                    {
                        _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, item, user, null, null, deviceId, filter);
                    }

                    provided++;
                }
                else
                {
                    var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
                    totalCount = childrenResult.TotalRecordCount;

                    provided = childrenResult.Items.Length;

                    foreach (var i in childrenResult.Items)
                    {
                        var childItem       = i.Item;
                        var displayStubType = i.StubType;

                        if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
                        {
                            var childCount = (GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0))
                                             .TotalRecordCount;

                            _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
                        }
                        else
                        {
                            _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, childItem, user, item, serverItem.StubType, deviceId, filter);
                        }
                    }
                }
                writer.WriteFullEndElement();
                //writer.WriteEndDocument();
            }

            var resXML = builder.ToString();

            return(new List <KeyValuePair <string, string> >
            {
                new KeyValuePair <string, string>("Result", resXML),
                new KeyValuePair <string, string>("NumberReturned", provided.ToString(_usCulture)),
                new KeyValuePair <string, string>("TotalMatches", totalCount.ToString(_usCulture)),
                new KeyValuePair <string, string>("UpdateID", _systemUpdateId.ToString(_usCulture))
            });
        }
示例#13
0
        private async Task <QueryResult <ServerItem> > GetUserItems(BaseItem item, StubType?stubType, User user, SortCriteria sort, int?startIndex, int?limit)
        {
            if (stubType.HasValue)
            {
                if (stubType.Value == StubType.People)
                {
                    var items = item.People.Select(i =>
                    {
                        try
                        {
                            return(_libraryManager.GetPerson(i.Name));
                        }
                        catch
                        {
                            return(null);
                        }
                    }).Where(i => i != null).ToArray();

                    var result = new QueryResult <ServerItem>
                    {
                        Items = items.Select(i => new ServerItem {
                            Item = i, StubType = StubType.Folder
                        }).ToArray(),
                        TotalRecordCount = items.Length
                    };

                    return(ApplyPaging(result, startIndex, limit));
                }
                if (stubType.Value == StubType.Folder)
                {
                    var movie = item as Movie;
                    if (movie != null)
                    {
                        return(ApplyPaging(await GetMovieItems(movie).ConfigureAwait(false), startIndex, limit));
                    }
                }

                var person = item as Person;
                if (person != null)
                {
                    return(await GetItemsFromPerson(person, user, startIndex, limit).ConfigureAwait(false));
                }

                return(ApplyPaging(new QueryResult <ServerItem>(), startIndex, limit));
            }

            var folder = (Folder)item;

            var sortOrders = new List <string>();

            if (!folder.IsPreSorted)
            {
                sortOrders.Add(ItemSortBy.SortName);
            }

            var queryResult = await folder.GetItems(new InternalItemsQuery
            {
                Limit      = limit,
                StartIndex = startIndex,
                SortBy     = sortOrders.ToArray(),
                SortOrder  = sort.SortOrder,
                User       = user,
                Filter     = FilterUnsupportedContent
            }).ConfigureAwait(false);

            var options = _config.GetDlnaConfiguration();

            var serverItems = queryResult
                              .Items
                              .Select(i => new ServerItem
            {
                Item     = i,
                StubType = GetDisplayStubType(i, item, options)
            })
                              .ToArray();

            return(new QueryResult <ServerItem>
            {
                TotalRecordCount = queryResult.TotalRecordCount,
                Items = serverItems
            });
        }
示例#14
0
        private async Task <IEnumerable <KeyValuePair <string, string> > > HandleBrowse(Headers sparams, User user, string deviceId)
        {
            var id           = sparams["ObjectID"];
            var flag         = sparams["BrowseFlag"];
            var filter       = new Filter(sparams.GetValueOrDefault("Filter", "*"));
            var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", ""));

            var provided = 0;

            // Default to null instead of 0
            // Upnp inspector sends 0 as requestedCount when it wants everything
            int?requestedCount = null;
            int?start          = 0;

            int requestedVal;

            if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requestedVal) && requestedVal > 0)
            {
                requestedCount = requestedVal;
            }

            int startVal;

            if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out startVal) && startVal > 0)
            {
                start = startVal;
            }

            //var root = GetItem(id) as IMediaFolder;
            var result = new XmlDocument();

            var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);

            didl.SetAttribute("xmlns:dc", NS_DC);
            didl.SetAttribute("xmlns:dlna", NS_DLNA);
            didl.SetAttribute("xmlns:upnp", NS_UPNP);
            //didl.SetAttribute("xmlns:sec", NS_SEC);
            result.AppendChild(didl);

            var serverItem = GetItemFromObjectId(id, user);
            var item       = serverItem.Item;

            int totalCount;

            if (string.Equals(flag, "BrowseMetadata"))
            {
                totalCount = 1;

                if (item.IsFolder || serverItem.StubType.HasValue)
                {
                    var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));

                    result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id));
                }
                else
                {
                    result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, item, null, null, deviceId, filter));
                }

                provided++;
            }
            else
            {
                var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
                totalCount = childrenResult.TotalRecordCount;

                provided = childrenResult.Items.Length;

                foreach (var i in childrenResult.Items)
                {
                    var childItem       = i.Item;
                    var displayStubType = i.StubType;

                    if (childItem.IsFolder || displayStubType.HasValue)
                    {
                        var childCount = (await GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false))
                                         .TotalRecordCount;

                        result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, childItem, displayStubType, item, childCount, filter));
                    }
                    else
                    {
                        result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, childItem, item, serverItem.StubType, deviceId, filter));
                    }
                }
            }

            var resXML = result.OuterXml;

            return(new List <KeyValuePair <string, string> >
            {
                new KeyValuePair <string, string>("Result", resXML),
                new KeyValuePair <string, string>("NumberReturned", provided.ToString(_usCulture)),
                new KeyValuePair <string, string>("TotalMatches", totalCount.ToString(_usCulture)),
                new KeyValuePair <string, string>("UpdateID", _systemUpdateId.ToString(_usCulture))
            });
        }
示例#15
0
        private void HandleBrowse(XmlWriter xmlWriter, IDictionary <string, string> sparams, string deviceId)
        {
            var id           = sparams["ObjectID"];
            var flag         = sparams["BrowseFlag"];
            var filter       = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
            var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));

            var provided = 0;

            // Default to null instead of 0
            // Upnp inspector sends 0 as requestedCount when it wants everything
            int?requestedCount = null;
            int?start          = 0;

            if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out var requestedVal) && requestedVal > 0)
            {
                requestedCount = requestedVal;
            }

            if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out var startVal) && startVal > 0)
            {
                start = startVal;
            }

            int totalCount;

            using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
            {
                var settings = new XmlWriterSettings()
                {
                    Encoding           = Encoding.UTF8,
                    CloseOutput        = false,
                    OmitXmlDeclaration = true,
                    ConformanceLevel   = ConformanceLevel.Fragment
                };

                using (var writer = XmlWriter.Create(builder, settings))
                {
                    writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);

                    writer.WriteAttributeString("xmlns", "dc", null, NsDc);
                    writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
                    writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);

                    DidlBuilder.WriteXmlRootAttributes(_profile, writer);

                    var serverItem = GetItemFromObjectId(id);
                    var item       = serverItem.Item;

                    if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
                    {
                        totalCount = 1;

                        if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
                        {
                            var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);

                            _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
                        }
                        else
                        {
                            var dlnaOptions = _config.GetDlnaConfiguration();
                            _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
                        }

                        provided++;
                    }
                    else
                    {
                        var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
                        totalCount = childrenResult.TotalRecordCount;

                        provided = childrenResult.Items.Count;

                        var dlnaOptions = _config.GetDlnaConfiguration();
                        foreach (var i in childrenResult.Items)
                        {
                            var childItem       = i.Item;
                            var displayStubType = i.StubType;

                            if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
                            {
                                var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0)
                                                 .TotalRecordCount;

                                _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
                            }
                            else
                            {
                                _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
                            }
                        }
                    }

                    writer.WriteFullEndElement();
                }

                xmlWriter.WriteElementString("Result", builder.ToString());
            }

            xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
            xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
            xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
        }