<commit_before># Implements UPnP Device Architecture version 1.0
# http://upnp.org/sdcps-and-certification/standards/device-architecture-documents/

makeUuid = require 'node-uuid'
xml2js = require 'xml2js'

httpu    = require '../httpu'
protocol = require '../protocol'
xml      = require '../xml'

class Service

    constructor: (@device) ->
        @xmlParser = new xml2js.Parser()
        @subscriptions = {}

    action: (action, data, callback) ->
        @xmlParser.parseString data, (err, data) =>
            options = data['s:Body']["u:#{action}"]
            @[action] options, (err, data) ->
                callback null, data

    subscribe: (cbUrls, timeout, callback) ->
        uuid = "uuid:#{makeUuid()}"
        @subscriptions[uuid] = new Subscription(
            cbUrls
            timeout
            uuid
            @
        )
        callback null, { sid: uuid, timeout: 'Second-1800' }

    renew: (uuid, timeout, callback) ->
        unless @subscriptions[uuid]?
            console.info "Got subscription renewal request but could not find
 device #{sid}."
            return callback new HttpError 412
        console.info "Renewed subscription #{uuid}"
        @subscriptions[uuid].selfDestruct(timeout)

    unsubscribe: (uuid) ->
        delete @subscriptions[uuid]

    optionalAction: (options, callback) ->
        @buildSoapError(
            { code: 602, description: "Optional Action Not Implemented" }
            (err, resp) ->
                callback null, resp
        )

    buildSoapResponse: xml.buildSoapResponse
    buildSoapError: xml.buildSoapError
    buildEvent: xml.buildEvent


class Subscription
    constructor: (urls, timeout, @uuid, @service) ->
        @eventKey = 0
        @callbackUrls = urls.split(',')
        console.info "Added new subscription #{@uuid} and callback url", @callbackUrls
        @selfDestruct timeout
        # Send out event on current state variables.
        @notify @service.stateVariables

    selfDestruct: (timeout) ->
        # Self destruct in `ms` milliseconds.
        ms = parseInt(/Second-(\d+)/.exec(timeout)[1]) * 1000
        setTimeout(
            =>
                console.info "Subscription #{@uuid} has timed out, deleting."
                @service.unsubscribe @uuid
            ms)

    notify: (vars) ->
        @service.buildEvent vars, (err, resp) =>
            httpu.postEvent.call(
                @service.device
                @callbackUrls
                @uuid
                @eventKey
                resp
            )
        @eventKey++<commit_msg>Return actual time until subscription expiration in response<commit_after># Implements UPnP Device Architecture version 1.0
# http://upnp.org/sdcps-and-certification/standards/device-architecture-documents/

makeUuid = require 'node-uuid'
xml2js = require 'xml2js'

httpu    = require '../httpu'
protocol = require '../protocol'
xml      = require '../xml'
{HttpError} = httpu

unless /upnp-device/.test process.env.NODE_DEBUG
    (console[c] = ->) for c in ['log', 'info']

class Service

    constructor: (@device) ->
        @xmlParser = new xml2js.Parser()
        @subscriptions = {}


    # Control action. Most actions build a SOAP response and calls back.
    action: (action, data, callback) ->
        @xmlParser.parseString data, (err, data) =>
            options = data['s:Body']["u:#{action}"]
            @[action] options, (err, data) ->
                callback err, data


    # Event subscriptions.
    subscribe: (cbUrls, reqTimeout, callback) ->
        uuid = "uuid:#{makeUuid()}"
        @subscriptions[uuid] = new Subscription(
            uuid
            cbUrls
            @
        )
        realTimeout = @subscriptions[uuid].selfDestruct reqTimeout
        callback null, sid: uuid, timeout: "Second-#{realTimeout}"

    renew: (uuid, reqTimeout, callback) ->
        unless @subscriptions[uuid]?
            console.info "Got subscription renewal request but could not find device #{sid}."
            return callback new HttpError 412
        console.info "Renewing subscription #{uuid}."
        realTimeout = @subscriptions[uuid].selfDestruct reqTimeout
        callback null, sid: uuid, timeout: "Second-#{realTimeout}"

    unsubscribe: (uuid) ->
        console.info "Deleting subscription #{uuid}."
        delete @subscriptions[uuid]

    optionalAction: (options, callback) ->
        @buildSoapError(
            code: 602, description: "Optional Action Not Implemented"
            (err, resp) ->
                callback null, resp
        )

    buildSoapResponse: xml.buildSoapResponse
    buildSoapError: xml.buildSoapError
    buildEvent: xml.buildEvent


class Subscription
    constructor: (@uuid, urls, @service) ->
        @eventKey = 0
        @minTimeout = 1800
        @callbackUrls = urls.split(',')
        console.info "Added new subscription #{@uuid} with callbacks", @callbackUrls
        @notify @service.stateVariables

    selfDestruct: (timeout) ->
        # `timeout` is like `Second-(seconds|infinite)`.
        time = /Second-(infinite|\d+)/.exec(timeout)[1]
        if time is 'infinite' or parseInt(time) > @minTimeout
            time = @minTimeout
        else
            time = parseInt(time)
        console.log "Subscription expiring in #{time}s."
        setTimeout(
            => @service.unsubscribe(@uuid)
            time * 1000)
        # Return actual time until unsubscription.
        time

    notify: (vars) ->
        console.info "Sending out event for current state variables to", @callbackUrls
        @service.buildEvent vars, (err, resp) =>
            httpu.postEvent.call(
                @service.device
                @callbackUrls
                @uuid
                @eventKey
                resp
            )
        @eventKey++
{'fraction_non_alphanumeric': 0.11409858669424336, 'fraction_numerical': 0.007238883143743537, 'mean_word_length': 2.1215707369553525, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 20}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2822592', 'n_tokens_mistral': 1645, 'n_tokens_neox': 1570, 'n_words': 427}