<commit_before>/*
 * Copyright (C) 2011 <NAME>
 *
 * Author: <NAME> <<EMAIL>>
 *
 * This file is part of Rygel.
 *
 * Rygel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Rygel 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

using GUPnP;
using Xml;

/**
 * Represents a device description document and offers methods for easy
 * manipulation of those.
 */
public class Rygel.DescriptionFile : Object {
    /// XML doc wrapper representing the description document
    private XMLDoc doc;

    // FIXME: Why does //serviceType not work? Seems a bug in libxml2.
    /**
     * XPath template for searching the service type node in
     * modify_service_type()
     */
    private const string SERVICE_TYPE_TEMPLATE = "//*[.='%s']";

    /**
     * Constructor to load a description file from disk
     *
     * @param template_file the path to the description file.
     * @throws GUPnP.XMLError.PARSE if there was an error reading or parsing
     * the file.
     */
    public DescriptionFile (string template_file) throws GLib.Error {
        this.doc = new XMLDoc.from_path (template_file);
    }

    /**
     * Constructor which wraps an existing GUPnP.XMLDoc as a description file.
     *
     * @param doc is the GUPnP.XMLDoc to wrap.
     */
    public DescriptionFile.from_xml_document (XMLDoc doc) {
        this.doc = doc;
    }

    /**
     * Change the type of a service.
     *
     * Usually used to modify the device version, e.g. default device type is
     * "MediaServer:2" and device_type = "MediaServer:1".
     *
     * @param device_type is the current content of serviceType.
     */
    public void set_device_type (string device_type) {
        this.set_device_element ("deviceType", device_type);
    }

    /**
     * Modify the model name.
     *
     * Usually the name of the software implementing this device.
     *
     * @param model_name is the new model name.
     */
    public void set_model_name (string model_name) {
        this.set_device_element ("modelName", model_name);
    }

    /**
     * Modify the model number.
     *
     * Usually the version of the software implementing this device.
     *
     * @param model_number is the new model number.
     */
    public void set_model_number (string model_number) {
        this.set_device_element ("modelNumber", model_number);
    }

    /**
     * Set the friendly name of the device.
     *
     * The friendly name is the one usually presented to the user in control
     * points or DMPs
     *
     * @param friendly_name is the new friendly name of the device.
     */
    public void set_friendly_name (string friendly_name) {
        this.set_device_element ("friendlyName", friendly_name);
    }

    /**
     * Get the current friendly name of the device.
     *
     * @return The currenly set friendly name.
     */
    public string get_friendly_name () {
        var element = Rygel.XMLUtils.get_element ((Xml.Node *) this.doc.doc,
                                                  "root",
                                                  "device",
                                                  "friendlyName");
        assert (element != null);

        return element->get_content ();
    }

    /**
     * Set the DLNA caps of this root device and while taking the
     * capabilities of the plugin into account.
     *
     * @param capabilities RygelPluginCapabilities flags
     */
    public void set_dlna_caps (PluginCapabilities capabilities) {
        var flags = new string[0];
        var content = "";

        if ((PluginCapabilities.UPLOAD & capabilities) != 0) {
            // This means "Supports upload to AnyContainer_DLNA.ORG", but we
            // also use it as "supports upload". AnyContainer upload is
            // handled by Rygel transparently.
            var allow_upload = true;
            var allow_delete = false;

            try {
                var config = MetaConfig.get_default ();
                allow_upload = config.get_allow_upload ();
                allow_delete = config.get_allow_deletion ();
            } catch (GLib.Error error) { }

            if (allow_upload) {
                if (PluginCapabilities.IMAGE_UPLOAD in capabilities) {
                    flags += "image-upload";
                }

                if (PluginCapabilities.VIDEO_UPLOAD in capabilities) {
                    flags += "av-upload";
                }

                if (PluginCapabilities.AUDIO_UPLOAD in capabilities) {
                    flags += "audio-upload";
                }
            }

            if (allow_delete) {
                flags += "create-item-with-OCM-destroy-item";
            }

        }

        if (PluginCapabilities.TRACK_CHANGES in capabilities) {
            flags += "content-synchronization";
            flags += "create-child-container";
        }

        // Might be that the plugin only supports create-child-container but
        // not change tracking, so we need to add this capability separately
        if (PluginCapabilities.CREATE_CONTAINERS in capabilities &&
            !(PluginCapabilities.TRACK_CHANGES in capabilities)) {
            flags += "create-child-container";
        }

        // Set the flags we found; otherwise remove whatever is in the
        // template.
        if (flags.length > 0) {
            content = string.joinv (",", flags);
        }

        this.set_device_element ("X_DLNACAP", content);
    }


    /**
     * Change the type of a service.
     *
     * Usually used to modify the service version, e.g. old_type =
     * "ContentDirectory:2" and new_type = "ContentDirectory:1".
     *
     * @param old_type is the current content of serviceType.
     * @param new_type is the content serviceType will be set to.
     */
    public void modify_service_type (string old_type,
                                     string new_type) {
        var context = new XPath.Context (this.doc.doc);

        var xpath = SERVICE_TYPE_TEMPLATE.printf (old_type);
        var xpath_object = context.eval_expression (xpath);
        assert (xpath_object != null);
        assert (xpath_object->type == XPath.ObjectType.NODESET);
        assert (!xpath_object->nodesetval->is_empty ());

        xpath_object->nodesetval->item (0)->set_content (new_type);
        delete xpath_object;
    }

    /**
     * Writes the current document to a file.
     *
     * It makes sure that the resulting file has the correct UTF-8 encoding
     * and does not have any kind of newlines. This is necessary as some
     * devices with broken XML parsers can't cope with UNIX newlines.
     * If a file with the same name exists it will be overwritten.
     *
     * @param path is a path to a file.
     * @throws GLib.Error if anything fails while creating the XML dump.
     */
    public void save (string path) throws GLib.Error {
        var file = FileStream.open (path, "w+");
        var message = _("Failed to write modified description to %s");

        if (unlikely (file == null)) {
            throw new IOError.FAILED (message, path);
        }

        string mem = null;
        int len = -1;
        doc.doc.dump_memory_enc (out mem, out len, "UTF-8");

        if (unlikely (len <= 0)) {
            throw new IOError.FAILED (message, path);
        }

        // Make sure we don't have any newlines
        file.puts (mem.replace ("\n", ""));
    }

    /**
     * Internal helper function to set an element to a new value.
     *
     * @param element below /root/device to be set.
     * @param new_vale is the new content of that element.
     */
    private void set_device_element (string element, string new_value) {
        var xml_element = Rygel.XMLUtils.get_element
                                        ((Xml.Node *) this.doc.doc,
                                         "root",
                                         "device",
                                         element);
        if (element != null) {
            xml_element->set_content (new_value);
        }
    }
}<commit_msg>DescriptionFile: create device elements if they do not exists<commit_after>/*
 * Copyright (C) 2011 <NAME>
 *
 * Author: <NAME> <<EMAIL>>
 *
 * This file is part of Rygel.
 *
 * Rygel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Rygel 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

using GUPnP;
using Xml;

/**
 * Represents a device description document and offers methods for easy
 * manipulation of those.
 */
public class Rygel.DescriptionFile : Object {
    /// XML doc wrapper representing the description document
    private XMLDoc doc;

    // FIXME: Why does //serviceType not work? Seems a bug in libxml2.
    /**
     * XPath template for searching the service type node in
     * modify_service_type()
     */
    private const string SERVICE_TYPE_TEMPLATE = "//*[.='%s']";

    /**
     * Constructor to load a description file from disk
     *
     * @param template_file the path to the description file.
     * @throws GUPnP.XMLError.PARSE if there was an error reading or parsing
     * the file.
     */
    public DescriptionFile (string template_file) throws GLib.Error {
        this.doc = new XMLDoc.from_path (template_file);
    }

    /**
     * Constructor which wraps an existing GUPnP.XMLDoc as a description file.
     *
     * @param doc is the GUPnP.XMLDoc to wrap.
     */
    public DescriptionFile.from_xml_document (XMLDoc doc) {
        this.doc = doc;
    }

    /**
     * Change the type of a service.
     *
     * Usually used to modify the device version, e.g. default device type is
     * "MediaServer:2" and device_type = "MediaServer:1".
     *
     * @param device_type is the current content of serviceType.
     */
    public void set_device_type (string device_type) {
        this.set_device_element ("deviceType", device_type);
    }

    /**
     * Modify the model name.
     *
     * Usually the name of the software implementing this device.
     *
     * @param model_name is the new model name.
     */
    public void set_model_name (string model_name) {
        this.set_device_element ("modelName", model_name);
    }

    /**
     * Modify the model number.
     *
     * Usually the version of the software implementing this device.
     *
     * @param model_number is the new model number.
     */
    public void set_model_number (string model_number) {
        this.set_device_element ("modelNumber", model_number);
    }

    /**
     * Set the friendly name of the device.
     *
     * The friendly name is the one usually presented to the user in control
     * points or DMPs
     *
     * @param friendly_name is the new friendly name of the device.
     */
    public void set_friendly_name (string friendly_name) {
        this.set_device_element ("friendlyName", friendly_name);
    }

    /**
     * Get the current friendly name of the device.
     *
     * @return The currenly set friendly name.
     */
    public string get_friendly_name () {
        var element = Rygel.XMLUtils.get_element ((Xml.Node *) this.doc.doc,
                                                  "root",
                                                  "device",
                                                  "friendlyName");
        assert (element != null);

        return element->get_content ();
    }

    /**
     * Set the DLNA caps of this root device and while taking the
     * capabilities of the plugin into account.
     *
     * @param capabilities RygelPluginCapabilities flags
     */
    public void set_dlna_caps (PluginCapabilities capabilities) {
        var flags = new string[0];

        if ((PluginCapabilities.UPLOAD & capabilities) != 0) {
            // This means "Supports upload to AnyContainer_DLNA.ORG", but we
            // also use it as "supports upload". AnyContainer upload is
            // handled by Rygel transparently.
            var allow_upload = true;
            var allow_delete = false;

            try {
                var config = MetaConfig.get_default ();
                allow_upload = config.get_allow_upload ();
                allow_delete = config.get_allow_deletion ();
            } catch (GLib.Error error) { }

            if (allow_upload) {
                if (PluginCapabilities.IMAGE_UPLOAD in capabilities) {
                    flags += "image-upload";
                }

                if (PluginCapabilities.VIDEO_UPLOAD in capabilities) {
                    flags += "av-upload";
                }

                if (PluginCapabilities.AUDIO_UPLOAD in capabilities) {
                    flags += "audio-upload";
                }
            }

            if (allow_delete) {
                flags += "create-item-with-OCM-destroy-item";
            }

        }

        if (PluginCapabilities.TRACK_CHANGES in capabilities) {
            flags += "content-synchronization";
            flags += "create-child-container";
        }

        // Might be that the plugin only supports create-child-container but
        // not change tracking, so we need to add this capability separately
        if (PluginCapabilities.CREATE_CONTAINERS in capabilities &&
            !(PluginCapabilities.TRACK_CHANGES in capabilities)) {
            flags += "create-child-container";
        }

        // Set the flags we found; otherwise remove whatever is in the
        // template.
        if (flags.length > 0) {
            var content = string.joinv (",", flags);
            this.set_device_element ("X_DLNACAP", content, "dlna");
        } else {
            this.remove_device_element ("X_DLNACAP");
        }
    }


    /**
     * Change the type of a service.
     *
     * Usually used to modify the service version, e.g. old_type =
     * "ContentDirectory:2" and new_type = "ContentDirectory:1".
     *
     * @param old_type is the current content of serviceType.
     * @param new_type is the content serviceType will be set to.
     */
    public void modify_service_type (string old_type,
                                     string new_type) {
        var context = new XPath.Context (this.doc.doc);

        var xpath = SERVICE_TYPE_TEMPLATE.printf (old_type);
        var xpath_object = context.eval_expression (xpath);
        assert (xpath_object != null);
        assert (xpath_object->type == XPath.ObjectType.NODESET);
        assert (!xpath_object->nodesetval->is_empty ());

        xpath_object->nodesetval->item (0)->set_content (new_type);
        delete xpath_object;
    }

    /**
     * Writes the current document to a file.
     *
     * It makes sure that the resulting file has the correct UTF-8 encoding
     * and does not have any kind of newlines. This is necessary as some
     * devices with broken XML parsers can't cope with UNIX newlines.
     * If a file with the same name exists it will be overwritten.
     *
     * @param path is a path to a file.
     * @throws GLib.Error if anything fails while creating the XML dump.
     */
    public void save (string path) throws GLib.Error {
        var file = FileStream.open (path, "w+");
        var message = _("Failed to write modified description to %s");

        if (unlikely (file == null)) {
            throw new IOError.FAILED (message, path);
        }

        string mem = null;
        int len = -1;
        doc.doc.dump_memory_enc (out mem, out len, "UTF-8");

        if (unlikely (len <= 0)) {
            throw new IOError.FAILED (message, path);
        }

        // Make sure we don't have any newlines
        file.puts (mem.replace ("\n", ""));
    }

    /**
     * Internal helper function to set an element to a new value,
     * creating it if needed.
     *
     * @param element below /root/device to be set.
     * @param new_vale is the new content of that element.
     */
    private void set_device_element (string element,
                                     string new_value,
                                     string? ns = null) {
        var xml_element = Rygel.XMLUtils.get_element
                                        ((Xml.Node *) this.doc.doc,
                                         "root",
                                         "device",
                                         element);
        if (xml_element == null) {
            var device_element = Rygel.XMLUtils.get_element
                                        ((Xml.Node *) this.doc.doc,
                                         "root",
                                         "device");
            if (device_element == null) {
                warning (_("XML node '%s' not found."), "/root/device");

                return;
            }

            Xml.Ns *xml_ns = null;
            if (ns != null) {
                xml_ns = this.doc.doc.search_ns(device_element, ns);
            }

            device_element->new_child (xml_ns, element, new_value);
        } else {
            xml_element->set_content (new_value);
        }
    }

    /**
     * Internal helper function to remove an element (if it exists).
     *
     * @param element below /root/device to be removed.
     */
    private void remove_device_element (string element) {
        var xml_element = Rygel.XMLUtils.get_element
                                        ((Xml.Node *) this.doc.doc,
                                         "root",
                                         "device",
                                         element);
        if (xml_element != null) {
            xml_element->unlink ();
            delete xml_element;
        }
    }
}
{'fraction_non_alphanumeric': 0.08676431424766977, 'fraction_numerical': 0.003089214380825566, 'mean_word_length': 1.823034130205984, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7050227', 'n_tokens_mistral': 4986, 'n_tokens_neox': 4854, 'n_words': 1722}