This file is indexed.

/usr/share/pyshared/remuco/config.py is in remuco-base 0.9.5-1.1build1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# =============================================================================
#
#    Remuco - A remote control system for media players.
#    Copyright (C) 2006-2010 by the Remuco team, see AUTHORS.
#
#    This file is part of Remuco.
#
#    Remuco 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.
#
#    Remuco 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 Remuco.  If not, see <http://www.gnu.org/licenses/>.
#
# =============================================================================

from __future__ import with_statement

import ConfigParser
from datetime import datetime
from glob import glob
import os
from os.path import join, isdir, exists, pathsep, basename
import re
import shutil
import sys
import textwrap

from remuco import log
from remuco import defs
from remuco.remos import user_config_dir
from remuco.remos import user_cache_dir

# =============================================================================
# Ordered dictionary, makes CP write sorted config files
# =============================================================================

class _odict(dict):
    
    def keys(self):
        kl = list(super(_odict, self).keys())
        kl.sort()
        return kl
    
    def items(self):
        il = list(super(_odict, self).items())
        il.sort(cmp=lambda a,b: cmp(a[0], b[0]))
        return il

# =============================================================================
# Constants and definitions
# =============================================================================

DEVICE_FILE = join(user_cache_dir, "remuco", "devices")

_DOC_HEADER = """# Player Adapter Configuration
# ============================
#
# Options defined in section DEFAULT affect *all* player adapters. Individual
# option values can be defined in each player's section.
#
# Options starting with `x-` are player specific options, i.e. they don't
# appear in section DEFAULT because they only make sense for specific players.  
#
# Options
# =======
#"""

# should be updated on major changes in config backend (simple removal or
# additions of options do not require a version update)
_CONFIG_VERSION = "3"

# standard options with default values, converter functions and documentation
_OPTIONS = {
    "config-version": ("0", None,
        "Used internally, don't edit."),
    "bluetooth-enabled": ("1", int,
        "Enable or disable Bluetooth."),
    "bluetooth-channel": ("0", int,
        "Bluetooth channel to use. 0 mean the next free channel."),
    "wifi-enabled": ("1", int,
        "Enable or disable WiFi (Inet)."),
    "wifi-port": ("34271", int,
        "WiFi port to use. Should be changed if Remuco is used for multiple "
        "players simultaneously to prevent port conflicts among adapters."),
    "player-encoding": ("UTF8", None,
        "Encoding of text coming from the player (i.e. artist, title, ...)."),
    "log-level": ("INFO", lambda v: getattr(log, v),
        "Log verbosity. Possible values: ERROR, WARNING, INFO, DEBUG."),
    "fb-show-extensions": ("0", int,
        "If to show file name extensions in a client's file browser."),
    "fb-root-dirs": ("auto", lambda v: v.split(pathsep),
        "List of directories (separated by `%s`) to show in a client's file "
        "browser. `auto` expands to all directories which typically contain "
        "files of the mime types a player supports (e.g. `~/Music` for audio "
        "players)." % pathsep),
    "master-volume-enabled": ("0", int,
        "Enable or disable master volume. By default a player's volume level "
        "is controlled by and displayed on clients. By setting this to `1` "
        "the system's master volume is used instead - in that case the "
        "following options *may* need to get adusted."),
    "master-volume-get-cmd": (r'amixer get Master | grep -E "\[[0-9]+%\]" | '
        'sed -re "s/^.*\[([0-9]+)%\].*$/\\1/"', None,
        "Command to get the master volume level in percent."),
    "master-volume-up-cmd": ("amixer set Master 5%+", None,
        "Command to increase the master volume."),
    "master-volume-down-cmd": ("amixer set Master 5%-", None,
        "Command to decrease the master volume."),
    "master-volume-mute-cmd": ("amixer set Master 0%", None,
        "Command to mute the master volume."),
    "system-shutdown-enabled": ("0", int,
        "Enable or disable system shutdown by clients. If enabled, the "
        "following option *may* need to get adjusted."),
    "system-shutdown-cmd": ("dbus-send --session --type=method_call "
        "--dest=org.freedesktop.PowerManagement "
        "/org/freedesktop/PowerManagement "
        "org.freedesktop.PowerManagement.Shutdown", None,
        "Command to shut down the system."),
}

# defaults-only version of _OPTIONS to pass to config parser
_DEFAULTS = _odict()
for k, v in _OPTIONS.items():
    _DEFAULTS[k] = v[0]

# timestamp (used for backups of old config data)
_TS = datetime.now().strftime("%Y%m%d-%H%M%S")

# =============================================================================
# Config class
# =============================================================================

class Config(object):
    """Class for getting and setting player adapter specific configurations.
    
    An instance of Config mirrors the configuration of a specific player
    adapter (usually ~/.config/remuco/PLAYER/conf).
    
    Player adapters are not supposed to create instances of Config. Instead
    use the 'config' attribute of a PlayerAdapter instance to access the
    currently used Config instance.
    
    """
    def __init__(self, player_name):
        """Create a new instance for the given player (adapter)."""

        super(Config, self).__init__()
        
        # convert descriptive name to a plain canonical one
        self.player = re.sub(r'[^\w-]', '', player_name).lower()
        
        # paths
        self.dir = join(user_config_dir, "remuco")
        self.cache = join(user_cache_dir, "remuco")
        self.file = join(self.dir, "remuco.cfg")

        # remove old stuff
        self.__cleanup()

        # create directories
        for dname in (self.dir, self.cache):
            try:
                if not isdir(dname):
                    os.makedirs(dname)
            except OSError, e:
                log.error("failed to make dir: %s", e)
        if not "REMUCO_LOG_STDOUT" in os.environ and isdir(self.cache):
            log.set_file(join(self.cache, "%s.log" % self.player))

        # load
        cp = ConfigParser.RawConfigParser(_DEFAULTS, _odict)
        if not cp.has_section(self.player):
            cp.add_section(self.player)
        if exists(self.file):
            try:
                cp.read(self.file)
            except ConfigParser.Error, e:
                log.warning("failed to read config %s (%s)" % (self.file, e))

        # reset on version change
        if cp.get(ConfigParser.DEFAULTSECT, "config-version") != _CONFIG_VERSION:
            sections = cp.sections() # keep already existing player sections
            cp = ConfigParser.RawConfigParser(_DEFAULTS, _odict)
            for sec in sections:
                cp.add_section(sec)
            if exists(self.file):
                bak = "%s.%s.backup" % (self.file, _TS)
                log.info("reset config (major changes, backup: %s)" % bak)
                shutil.copy(self.file, bak)
            
        # remove unknown options in all sections
        for sec in cp.sections() + [ConfigParser.DEFAULTSECT]:
            for key, value in cp.items(sec):
                if key not in _DEFAULTS and not key.startswith("x-"):
                    cp.remove_option(sec, key)
                    
        # add not yet existing options to default section
        for key, value in _DEFAULTS.items():
            if not cp.has_option(ConfigParser.DEFAULTSECT, key):
                cp.set(ConfigParser.DEFAULTSECT, key, value)
        
        # update version
        cp.set(ConfigParser.DEFAULTSECT, "config-version", _CONFIG_VERSION)

        self.__cp = cp
        
        # save to always have a clean file
        self.__save()

        log.set_level(self.log_level)
        
        log.info("remuco version: %s" % defs.REMUCO_VERSION)
        
    def __getattribute__(self, attr):
        """Attribute-style access to standard options."""
        
        try:
            return super(Config, self).__getattribute__(attr)
        except AttributeError, e:
            _attr = attr.replace("_", "-")
            if _attr in _OPTIONS:
                attr = _attr
            elif attr not in _OPTIONS:
                raise e
            value = self.__cp.get(self.player, attr)
            converter = _OPTIONS[attr][1] or (lambda v: v)
            try:
                return converter(value)
            except Exception, e:
                log.error("malformed option '%s: %s' (%s)" % (attr, e))
                return converter(_DEFAULTS[attr])
    
    def getx(self, key, default, converter=None, save=True):
        """Get the value of a non-standard, player specific option.
        
        @param key:
            config option name
        @param default:
            default value (as string!)
        @keyword converter:
            value converter function, e.g. `int`
        @keyword save:
            save default value in config file if not yet set
        @return:
            option value, optionally converted
        
        """
        key = "x-%s" % key
        if not self.__cp.has_option(self.player, key) and save:
            self.__cp.set(self.player, key, default)
            self.__save()
        try:
            value = self.__cp.get(self.player, key)
        except ConfigParser.NoOptionError:
            value = default
        converter = converter or (lambda v: v)
        try:
            return converter(value)
        except Exception, e:
            log.error("malformed option '%s: %s' (%s)" % (key, value, e))
            return converter(default) # if this fails then, it's a bug

    def __save(self):
        """Save config to it's file."""
        
        doc = [_DOC_HEADER]
        for key in _DEFAULTS.keys():
            idoc = "# %s:" % key 
            idoc = [idoc] + textwrap.wrap(_OPTIONS[key][2], 73)
            idoc = "\n#     ".join(idoc)
            doc.append(idoc)
        doc = "\n".join(doc)
        
        try:
            with open(self.file, 'w') as fp:
                fp.write(doc)
                fp.write("\n\n")
                self.__cp.write(fp)
        except IOError, e:
            log.warning("failed to save config to %s (%s)" % (self.file, e))

    def __cleanup(self):
        """Trash obsolete config and cache data from older versions."""
        
        def obsolete(fn):
            """Check if a config or cache item may be trashed."""
            obs  = isdir(fn)
            obs |= basename(fn) in ("shutdown-system", "volume")
            obs &= not basename(fn).startswith("old-")
            return obs
        
        for dname, dtype in ((self.dir, "config"), (self.cache, "cache")):
            trash = join(dname, "old-%s.backup" % _TS)
            fnames = [f for f in glob(join(dname, "*")) if obsolete(f)]
            if fnames:
                log.info("moving old %s data to %s" % (dtype, trash))
                if not exists(trash):
                    os.makedirs(trash)
                for fn in fnames:
                    shutil.move(fn, trash)