Esempio n. 1
0
 def test_default_config(self):
     conf_text = make_default_config()
     
     # Don't try to open a real device
     DEFAULT_DEVICE = "OsmoSDRDevice('')"
     self.assertIn(DEFAULT_DEVICE, conf_text)
     conf_text = conf_text.replace(DEFAULT_DEVICE, "OsmoSDRDevice('file=/dev/null,rate=100000')")
     
     with open(self.__config_name, 'w') as f:
         f.write(conf_text)
     config_obj = Config(the_reactor)
     execute_config(config_obj, self.__config_name)
     return config_obj._wait_and_validate()
Esempio n. 2
0
 def test_default_config(self):
     conf_text = make_default_config()
     
     # Don't try to open a real device
     DEFAULT_DEVICE = "OsmoSDRDevice('')"
     self.assertIn(DEFAULT_DEVICE, conf_text)
     conf_text = conf_text.replace(DEFAULT_DEVICE, "OsmoSDRDevice('file=/dev/null,rate=100000')")
     
     with open(self.__config_name, 'w') as f:
         f.write(conf_text)
     config_obj = Config(the_reactor)
     execute_config(config_obj, self.__config_name)
     return config_obj._wait_and_validate()
Esempio n. 3
0
def ConfigFactory(log=Logger()):
    return Config(the_reactor, log)
Esempio n. 4
0
def _main_async(reactor, argv=None, _abort_for_test=False):
    if argv is None:
        argv = sys.argv

    if not _abort_for_test:
        # Configure logging. Some log messages would be discarded if we did not set up things early
        # TODO: Consult best practices for Python and Twisted logging.
        # TODO: Logs which are observably relevant should be sent to the client (e.g. the warning of refusing to have more receivers active)
        logging.basicConfig(level=logging.INFO)
        log.startLoggingWithObserver(
            log.PythonLoggingObserver(loggerName='shinysdr').emit, False)

    # Option parsing is done before importing the main modules so as to avoid the cost of initializing gnuradio if we are aborting early. TODO: Make that happen for createConfig too.
    argParser = argparse.ArgumentParser(prog=argv[0])
    argParser.add_argument('config_path',
                           metavar='CONFIG',
                           help='path of configuration directory or file')
    argParser.add_argument(
        '--create',
        dest='createConfig',
        action='store_true',
        help='write template configuration file to CONFIG and exit')
    argParser.add_argument('-g, --go',
                           dest='openBrowser',
                           action='store_true',
                           help='open the UI in a web browser')
    argParser.add_argument(
        '--force-run',
        dest='force_run',
        action='store_true',
        help='Run DSP even if no client is connected (for debugging).')
    args = argParser.parse_args(args=argv[1:])

    # Verify we can actually run.
    # Note that this must be done before we actually load core modules, because we might get an import error then.
    version_report = yield _check_versions()
    if version_report:
        print >> sys.stderr, version_report
        sys.exit(1)

    # We don't actually use shinysdr.devices directly, but we want it to be guaranteed available in the context of the config file.
    # pylint: disable=unused-variable
    import shinysdr.devices as lazy_devices
    import shinysdr.source as lazy_source  # legacy shim

    # Load config file
    if args.createConfig:
        write_default_config(args.config_path)
        log.msg('Created default configuration at: ' + args.config_path)
        sys.exit(0)  # TODO: Consider using a return value or something instead
    else:
        configObj = Config(reactor)
        execute_config(configObj, args.config_path)
        yield configObj._wait_and_validate()

        stateFile = configObj._state_filename

    def restore(root, get_defaults):
        if stateFile is not None:
            if os.path.isfile(stateFile):
                root.state_from_json(json.load(open(stateFile, 'r')))
                # make a backup in case this code version misreads the state and loses things on save (but only if the load succeeded, in case the file but not its backup is bad)
                shutil.copyfile(stateFile, stateFile + '~')
            else:
                root.state_from_json(get_defaults(root))

    log.msg('Constructing...')
    app = configObj._create_app()

    singleton_reactor.addSystemEventTrigger('during', 'shutdown',
                                            app.close_all_devices)

    log.msg('Restoring state...')
    restore(app, _app_defaults)

    # Set up persistence
    if stateFile is not None:

        def eventually_write():
            log.msg('Scheduling state write.')

            def actually_write():
                log.msg('Performing state write...')
                current_state = pcd.get()
                with open(stateFile, 'w') as f:
                    json.dump(current_state, f)
                log.msg('...done')

            reactor.callLater(_PERSISTENCE_DELAY, actually_write)

        pcd = PersistenceChangeDetector(app, eventually_write,
                                        the_subscription_context)
        # Start implicit write-to-disk loop, but don't actually write.
        # This is because it is useful in some failure modes to not immediately overwrite a good state file with a bad one on startup.
        pcd.get()

    log.msg('Starting web server...')
    services = MultiService()
    for maker in configObj._service_makers:
        IService(maker(app)).setServiceParent(services)
    services.startService()

    log.msg('ShinySDR is ready.')

    for service in services:
        # TODO: should have an interface (currently no proper module to put it in)
        service.announce(args.openBrowser)

    if args.force_run:
        log.msg('force_run')
        from gnuradio.gr import msg_queue
        # TODO kludge, make this less digging into guts
        app.get_receive_flowgraph().monitor.get_fft_distributor().subscribe(
            msg_queue(limit=2))

    if _abort_for_test:
        services.stopService()
        defer.returnValue(app)
    else:
        yield defer.Deferred()  # never fires
Esempio n. 5
0
 def setUp(self):
     self.config = Config(the_reactor)
Esempio n. 6
0
class TestConfigObject(unittest.TestCase):
    def setUp(self):
        self.config = Config(the_reactor)

    # TODO: In type error tests, also check message once we've cleaned them up.

    # --- General functionality ---

    def test_reactor(self):
        self.assertEqual(self.config.reactor, the_reactor)

    # TODO def test_wait_for(self):

    @defer.inlineCallbacks
    def test_validate_succeed(self):
        self.config.devices.add(u'foo', StubDevice())
        d = self.config._wait_and_validate()
        self.assertIsInstance(d, defer.Deferred)  # don't succeed trivially
        yield d

    # TODO: Test "No network service defined"; is a warning not an error

    # --- Persistence ---

    @defer.inlineCallbacks
    def test_persist_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException,
                          lambda: self.config.persist_to_file('foo'))
        self.assertEqual({}, self.config.devices._values)

    def test_persist_none(self):
        self.assertEqual(None, self.config._state_filename)

    def test_persist_ok(self):
        self.config.persist_to_file('foo')
        self.assertEqual('foo', self.config._state_filename)

    def test_persist_duplication(self):
        self.config.persist_to_file('foo')
        self.assertRaises(ConfigException,
                          lambda: self.config.persist_to_file('bar'))
        self.assertEqual('foo', self.config._state_filename)

    # --- Devices ---

    @defer.inlineCallbacks
    def test_device_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(
            ConfigTooLateException,
            lambda: self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({}, self.config.devices._values)

    def test_device_key_ok(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))

    def test_device_key_string_ok(self):
        dev = StubDevice()
        self.config.devices.add('foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))

    def test_device_key_type(self):
        self.assertRaises(
            ConfigException,
            lambda: self.config.devices.add(StubDevice(), StubDevice()))
        self.assertEqual({}, self.config.devices._values)

    def test_device_key_duplication(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertRaises(
            ConfigException,
            lambda: self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({u'foo': dev}, self.config.devices._values)

    def test_device_empty(self):
        self.assertRaises(ConfigException,
                          lambda: self.config.devices.add(u'foo'))
        self.assertEqual({}, self.config.devices._values)

    # --- serve_web ---

    @defer.inlineCallbacks
    def test_web_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(
            ConfigTooLateException,
            lambda: self.config.serve_web(http_endpoint='tcp:8100',
                                          ws_endpoint='tcp:8101'))
        self.assertEqual({}, self.config.devices._values)

    def test_web_ok(self):
        self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101')
        self.assertEqual(1, len(self.config._service_makers))

    def test_web_root_cap_empty(self):
        self.assertRaises(
            ConfigException, lambda: self.config.serve_web(
                http_endpoint='tcp:8100', ws_endpoint='tcp:8101', root_cap=''))
        self.assertEqual([], self.config._service_makers)

    def test_web_root_cap_none(self):
        self.config.serve_web(http_endpoint='tcp:0', ws_endpoint='tcp:0')
        self.assertEqual(1, len(self.config._service_makers))
        # Actually instantiating the service. We need to do this to check if the root_cap value was processed correctly.
        service = self.config._service_makers[0](DummyAppRoot())
        self.assertEqual('/', service.get_host_relative_url())

    # --- serve_ghpsdr ---

    @defer.inlineCallbacks
    def test_ghpsdr_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException,
                          lambda: self.config.serve_ghpsdr())
        self.assertEqual({}, self.config.devices._values)

    def test_ghpsdr_ok(self):
        self.config.serve_ghpsdr()
        self.assertEqual(1, len(self.config._service_makers))

    # --- Misc options ---

    @defer.inlineCallbacks
    def test_server_audio_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException,
                          lambda: self.config.set_server_audio_allowed(True))
        self.assertEqual({}, self.config.devices._values)

    # TODO test rest of config.set_server_audio_allowed

    @defer.inlineCallbacks
    def test_stereo_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException,
                          lambda: self.config.set_stereo(True))
        self.assertEqual({}, self.config.devices._values)

    # TODO test rest of config.set_stereo

    # --- Features ---

    def test_features_unknown(self):
        self.assertRaises(ConfigException,
                          lambda: self.config.features.enable('bogus'))
        self.assertFalse('bogus' in self.config.features._state)

    @defer.inlineCallbacks
    def test_features_enable_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(
            ConfigTooLateException,
            lambda: self.config.features.enable('_test_disabled_feature'))
        self.assertFalse(self.config.features._get('_test_disabled_feature'))

    @defer.inlineCallbacks
    def test_features_disable_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(
            ConfigTooLateException,
            lambda: self.config.features.enable('_test_enabled_feature'))
        self.assertTrue(self.config.features._get('_test_enabled_feature'))
Esempio n. 7
0
class TestConfigObject(unittest.TestCase):
    def setUp(self):
        self.config = Config(the_reactor)

    # TODO: In type error tests, also check message once we've cleaned them up.

    # --- General functionality ---

    def test_reactor(self):
        self.assertEqual(self.config.reactor, the_reactor)

    # TODO def test_wait_for(self):

    @defer.inlineCallbacks
    def test_validate_succeed(self):
        self.config.devices.add(u'foo', StubDevice())
        d = self.config._wait_and_validate()
        self.assertIsInstance(d, defer.Deferred)  # don't succeed trivially
        yield d

    # TODO: Test "No network service defined"; is a warning not an error

    # --- Persistence ---

    @defer.inlineCallbacks
    def test_persist_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception,
                          lambda: self.config.persist_to_file('foo'))
        self.assertEqual({}, self.config.devices._values)

    def test_persist_none(self):
        self.assertEqual(None, self.config._state_filename)

    def test_persist_ok(self):
        self.config.persist_to_file('foo')
        self.assertEqual('foo', self.config._state_filename)

    def test_persist_duplication(self):
        self.config.persist_to_file('foo')
        self.assertRaises(ValueError,
                          lambda: self.config.persist_to_file('bar'))
        self.assertEqual('foo', self.config._state_filename)

    # --- Devices ---

    @defer.inlineCallbacks
    def test_device_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(
            Exception, lambda: self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({}, self.config.devices._values)

    def test_device_key_ok(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))

    def test_device_key_string_ok(self):
        dev = StubDevice()
        self.config.devices.add('foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))

    def test_device_key_type(self):
        self.assertRaises(
            TypeError,
            lambda: self.config.devices.add(StubDevice(), StubDevice()))
        self.assertEqual({}, self.config.devices._values)

    def test_device_key_duplication(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertRaises(
            KeyError, lambda: self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({u'foo': dev}, self.config.devices._values)

    def test_device_empty(self):
        self.assertRaises(ValueError, lambda: self.config.devices.add(u'foo'))
        self.assertEqual({}, self.config.devices._values)

    # --- serve_web ---

    @defer.inlineCallbacks
    def test_web_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(
            Exception, lambda: self.config.serve_web(http_endpoint='tcp:8100',
                                                     ws_endpoint='tcp:8101'))
        self.assertEqual({}, self.config.devices._values)

    def test_web_ok(self):
        self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101')
        self.assertEqual(1, len(self.config._service_makers))

    def test_web_root_cap_empty(self):
        self.assertRaises(
            ValueError, lambda: self.config.serve_web(
                http_endpoint='tcp:8100', ws_endpoint='tcp:8101', root_cap=''))
        self.assertEqual([], self.config._service_makers)

    # --- serve_ghpsdr ---

    @defer.inlineCallbacks
    def test_ghpsdr_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda: self.config.serve_ghpsdr())
        self.assertEqual({}, self.config.devices._values)

    def test_ghpsdr_ok(self):
        self.config.serve_ghpsdr()
        self.assertEqual(1, len(self.config._service_makers))

    # --- Misc options ---

    @defer.inlineCallbacks
    def test_server_audio_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception,
                          lambda: self.config.set_server_audio_allowed(True))
        self.assertEqual({}, self.config.devices._values)

    # TODO test rest of config.set_server_audio_allowed

    @defer.inlineCallbacks
    def test_stereo_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda: self.config.set_stereo(True))
        self.assertEqual({}, self.config.devices._values)
Esempio n. 8
0
def _main_async(reactor, argv=None, _abort_for_test=False):
    if argv is None:
        argv = sys.argv
    
    if not _abort_for_test:
        # Configure logging. Some log messages would be discarded if we did not set up things early
        # TODO: Consult best practices for Python and Twisted logging.
        # TODO: Logs which are observably relevant should be sent to the client (e.g. the warning of refusing to have more receivers active)
        logging.basicConfig(level=logging.INFO)
        log.startLoggingWithObserver(log.PythonLoggingObserver(loggerName='shinysdr').emit, False)
    
    # Option parsing is done before importing the main modules so as to avoid the cost of initializing gnuradio if we are aborting early. TODO: Make that happen for createConfig too.
    argParser = argparse.ArgumentParser(prog=argv[0])
    argParser.add_argument('configFile', metavar='CONFIG',
        help='path of configuration file')
    argParser.add_argument('--create', dest='createConfig', action='store_true',
        help='write template configuration file to CONFIG and exit')
    argParser.add_argument('-g, --go', dest='openBrowser', action='store_true',
        help='open the UI in a web browser')
    argParser.add_argument('--force-run', dest='force_run', action='store_true',
        help='Run DSP even if no client is connected (for debugging).')
    args = argParser.parse_args(args=argv[1:])

    # Verify we can actually run.
    # Note that this must be done before we actually load core modules, because we might get an import error then.
    yield check_versions()

    # We don't actually use shinysdr.devices directly, but we want it to be guaranteed available in the context of the config file.
    import shinysdr.devices as lazy_devices
    import shinysdr.source as lazy_source  # legacy shim

    # Load config file
    if args.createConfig:
        with open(args.configFile, 'w') as f:
            f.write(make_default_config())
            log.msg('Created default configuration file at: ' + args.configFile)
            sys.exit(0)  # TODO: Consider using a return value or something instead
    else:
        configObj = Config(reactor)
        execute_config(configObj, args.configFile)
        yield configObj._wait_and_validate()
        
        stateFile = configObj._state_filename
    
    def noteDirty():
        if stateFile is not None:
            # just immediately write (revisit this when more performance is needed)
            with open(stateFile, 'w') as f:
                json.dump(top.state_to_json(), f)
    
    def restore(root, get_defaults):
        if stateFile is not None:
            if os.path.isfile(stateFile):
                root.state_from_json(json.load(open(stateFile, 'r')))
                # make a backup in case this code version misreads the state and loses things on save (but only if the load succeeded, in case the file but not its backup is bad)
                shutil.copyfile(stateFile, stateFile + '~')
            else:
                root.state_from_json(get_defaults(root))
    
    log.msg('Constructing flow graph...')
    top = configObj._create_top_block()
    
    singleton_reactor.addSystemEventTrigger('during', 'shutdown', top.close_all_devices)
    
    log.msg('Restoring state...')
    restore(top, top_defaults)
    
    log.msg('Starting web server...')
    services = MultiService()
    for maker in configObj._service_makers:
        IService(maker(top, noteDirty)).setServiceParent(services)
    services.startService()
    
    log.msg('ShinySDR is ready.')
    
    for service in services:
        # TODO: should have an interface (currently no proper module to put it in)
        service.announce(args.openBrowser)
    
    if args.force_run:
        log.msg('force_run')
        from gnuradio.gr import msg_queue
        top.monitor.get_fft_distributor().subscribe(msg_queue(limit=2))
    
    if _abort_for_test:
        services.stopService()
        defer.returnValue((top, noteDirty))
    else:
        yield defer.Deferred()  # never fires
Esempio n. 9
0
class TestConfigFiles(unittest.TestCase):
    def setUp(self):
        self.__temp_dir = tempfile.mkdtemp(prefix='shinysdr_test_config_tmp')
        self.__config_name = os.path.join(self.__temp_dir, 'config')
        self.__config = Config(the_reactor)

    def tearDown(self):
        shutil.rmtree(self.__temp_dir)

    def __dirpath(self, *paths):
        return os.path.join(self.__config_name, *paths)

    def test_config_file(self):
        with open(self.__config_name, 'w') as f:
            f.write('config.features.enable("_test_disabled_feature")')
        # DB CSV file we expect NOT to be loaded
        os.mkdir(os.path.join(self.__temp_dir, 'dbs'))
        with open(os.path.join(self.__temp_dir, 'dbs', 'foo.csv'), 'w') as f:
            f.write('Frequency,Name')

        execute_config(self.__config, self.__config_name)

        # Config python was executed
        self.assertTrue(self.__config.features._get('_test_disabled_feature'))

        # Config-directory-related defaults were not set
        self.assertEquals(None, self.__config._state_filename)
        self.assertEquals(
            get_default_dbs().viewkeys(),
            self.__config.databases._get_read_only_databases().viewkeys())

    def test_config_directory(self):
        os.mkdir(self.__config_name)
        with open(self.__dirpath('config.py'), 'w') as f:
            f.write('config.features.enable("_test_disabled_feature")')
        os.mkdir(self.__dirpath('dbs-read-only'))
        with open(self.__dirpath('dbs-read-only', 'foo.csv'), 'w') as f:
            f.write('Frequency,Name')
        execute_config(self.__config, self.__config_name)

        # Config python was executed
        self.assertTrue(self.__config.features._get('_test_disabled_feature'))

        # Config-directory-related defaults were set
        self.assertEquals(self.__dirpath('state.json'),
                          self.__config._state_filename)
        self.assertIn('foo.csv',
                      self.__config.databases._get_read_only_databases())

    def test_default_config(self):
        write_default_config(self.__config_name)
        self.assertTrue(os.path.isdir(self.__config_name))

        # Don't try to open a real device
        with open(self.__dirpath('config.py'), 'r') as f:
            conf_text = f.read()
        DEFAULT_DEVICE = "OsmoSDRDevice('')"
        self.assertIn(DEFAULT_DEVICE, conf_text)
        conf_text = conf_text.replace(
            DEFAULT_DEVICE, "OsmoSDRDevice('file=/dev/null,rate=100000')")
        with open(self.__dirpath('config.py'), 'w') as f:
            f.write(conf_text)

        execute_config(self.__config, self.__config_name)

        self.assertTrue(os.path.isdir(self.__dirpath('dbs-read-only')))
        return self.__config._wait_and_validate()
Esempio n. 10
0
def _main_async(reactor, argv=None, _abort_for_test=False):
    if argv is None:
        argv = sys.argv
    
    if not _abort_for_test:
        # Some log messages would be discarded if we did not set up things early.
        configure_logging()
    
    # Option parsing is done before importing the main modules so as to avoid the cost of initializing gnuradio if we are aborting early. TODO: Make that happen for createConfig too.
    argParser = argparse.ArgumentParser(prog=argv[0])
    argParser.add_argument('config_path', metavar='CONFIG',
        help='path of configuration directory or file')
    argParser.add_argument('--create', dest='createConfig', action='store_true',
        help='write template configuration file to CONFIG and exit')
    argParser.add_argument('-g, --go', dest='openBrowser', action='store_true',
        help='open the UI in a web browser')
    argParser.add_argument('--force-run', dest='force_run', action='store_true',
        help='Run DSP even if no client is connected (for debugging).')
    args = argParser.parse_args(args=argv[1:])

    # Verify we can actually run.
    # Note that this must be done before we actually load core modules, because we might get an import error then.
    version_report = yield _check_versions()
    if version_report:
        print >>sys.stderr, version_report
        sys.exit(1)

    # Write config file and exit if asked ...
    if args.createConfig:
        write_default_config(args.config_path)
        log.msg('Created default configuration at: ' + args.config_path)
        sys.exit(0)  # TODO: Consider using a return value or something instead
    
    # ... else read config file
    config_obj = Config(reactor)
    execute_config(config_obj, args.config_path)
    yield config_obj._wait_and_validate()
    
    log.msg('Constructing...')
    app = config_obj._create_app()
    
    reactor.addSystemEventTrigger('during', 'shutdown', app.close_all_devices)
    
    log.msg('Restoring state...')
    pfg = PersistenceFileGlue(
        reactor=reactor,
        root_object=app,
        filename=config_obj._state_filename,
        get_defaults=_app_defaults)
    
    log.msg('Starting web server...')
    services = MultiService()
    for maker in config_obj._service_makers:
        IService(maker(app)).setServiceParent(services)
    services.startService()
    
    log.msg('ShinySDR is ready.')
    
    for service in services:
        # TODO: should have an interface (currently no proper module to put it in)
        service.announce(args.openBrowser)
    
    if args.force_run:
        log.msg('force_run')
        from gnuradio.gr import msg_queue
        # TODO kludge, make this less digging into guts
        app.get_receive_flowgraph().monitor.get_fft_distributor().subscribe(msg_queue(limit=2))
    
    if _abort_for_test:
        services.stopService()
        yield pfg.sync()
        defer.returnValue(app)
    else:
        yield defer.Deferred()  # never fires
Esempio n. 11
0
def _main_async(reactor, argv=None, _abort_for_test=False):
    if argv is None:
        argv = sys.argv
    
    if not _abort_for_test:
        # Configure logging. Some log messages would be discarded if we did not set up things early
        # TODO: Consult best practices for Python and Twisted logging.
        # TODO: Logs which are observably relevant should be sent to the client (e.g. the warning of refusing to have more receivers active)
        logging.basicConfig(level=logging.INFO)
        log.startLoggingWithObserver(log.PythonLoggingObserver(loggerName='shinysdr').emit, False)
    
    # Option parsing is done before importing the main modules so as to avoid the cost of initializing gnuradio if we are aborting early. TODO: Make that happen for createConfig too.
    argParser = argparse.ArgumentParser(prog=argv[0])
    argParser.add_argument('configFile', metavar='CONFIG',
        help='path of configuration file')
    argParser.add_argument('--create', dest='createConfig', action='store_true',
        help='write template configuration file to CONFIG and exit')
    argParser.add_argument('-g, --go', dest='openBrowser', action='store_true',
        help='open the UI in a web browser')
    argParser.add_argument('--force-run', dest='force_run', action='store_true',
        help='Run DSP even if no client is connected (for debugging).')
    args = argParser.parse_args(args=argv[1:])

    # We don't actually use shinysdr.devices directly, but we want it to be guaranteed available in the context of the config file.
    import shinysdr.devices as lazy_devices
    import shinysdr.source as lazy_source  # legacy shim

    # Load config file
    if args.createConfig:
        with open(args.configFile, 'w') as f:
            f.write(make_default_config())
            log.msg('Created default configuration file at: ' + args.configFile)
            sys.exit(0)  # TODO: Consider using a return value or something instead
    else:
        configObj = Config(reactor)
        execute_config(configObj, args.configFile)
        yield configObj._wait_and_validate()
        
        stateFile = configObj._state_filename
    
    def noteDirty():
        if stateFile is not None:
            # just immediately write (revisit this when more performance is needed)
            with open(stateFile, 'w') as f:
                json.dump(top.state_to_json(), f)
    
    def restore(root, get_defaults):
        if stateFile is not None:
            if os.path.isfile(stateFile):
                root.state_from_json(json.load(open(stateFile, 'r')))
                # make a backup in case this code version misreads the state and loses things on save (but only if the load succeeded, in case the file but not its backup is bad)
                shutil.copyfile(stateFile, stateFile + '~')
            else:
                root.state_from_json(get_defaults(root))
    
    log.msg('Constructing flow graph...')
    top = configObj._create_top_block()
    
    log.msg('Restoring state...')
    restore(top, top_defaults)
    
    log.msg('Starting web server...')
    services = MultiService()
    for maker in configObj._service_makers:
        IService(maker(top, noteDirty)).setServiceParent(services)
    services.startService()
    
    log.msg('ShinySDR is ready.')
    
    for service in services:
        # TODO: should have an interface (currently no proper module to put it in)
        service.announce(args.openBrowser)
    
    if args.force_run:
        log.msg('force_run')
        from gnuradio.gr import msg_queue
        top.add_audio_queue(msg_queue(limit=2), 44100)
        top.set_unpaused(True)
    
    if _abort_for_test:
        services.stopService()
        defer.returnValue((top, noteDirty))
    else:
        yield defer.Deferred()  # never fires
Esempio n. 12
0
class TestConfigObject(unittest.TestCase):
    def setUp(self):
        self.config = Config(the_reactor)
    
    # TODO: In type error tests, also check message once we've cleaned them up.
    
    # --- General functionality ---
    
    def test_reactor(self):
        self.assertEqual(self.config.reactor, the_reactor)
    
    # TODO def test_wait_for(self):
    
    @defer.inlineCallbacks
    def test_validate_succeed(self):
        self.config.devices.add(u'foo', StubDevice())
        d = self.config._wait_and_validate()
        self.assertIsInstance(d, defer.Deferred)  # don't succeed trivially
        yield d
    
    # TODO: Test "No network service defined"; is a warning not an error

    # --- Persistence ---
    
    @defer.inlineCallbacks
    def test_persist_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.persist_to_file('foo'))
        self.assertEqual({}, self.config.devices._values)
    
    def test_persist_none(self):
        self.assertEqual(None, self.config._state_filename)

    def test_persist_ok(self):
        self.config.persist_to_file('foo')
        self.assertEqual('foo', self.config._state_filename)

    def test_persist_duplication(self):
        self.config.persist_to_file('foo')
        self.assertRaises(ConfigException, lambda: self.config.persist_to_file('bar'))
        self.assertEqual('foo', self.config._state_filename)

    # --- Devices ---
    
    @defer.inlineCallbacks
    def test_device_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({}, self.config.devices._values)
    
    def test_device_key_ok(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))
    
    def test_device_key_string_ok(self):
        dev = StubDevice()
        self.config.devices.add('foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))
    
    def test_device_key_type(self):
        self.assertRaises(ConfigException, lambda:
            self.config.devices.add(StubDevice(), StubDevice()))
        self.assertEqual({}, self.config.devices._values)
    
    def test_device_key_duplication(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertRaises(ConfigException, lambda:
            self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({u'foo': dev}, self.config.devices._values)
    
    def test_device_empty(self):
        self.assertRaises(ConfigException, lambda:
            self.config.devices.add(u'foo'))
        self.assertEqual({}, self.config.devices._values)
    
    # --- serve_web ---
    
    @defer.inlineCallbacks
    def test_web_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101'))
        self.assertEqual({}, self.config.devices._values)
    
    def test_web_ok(self):
        self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101')
        self.assertEqual(1, len(self.config._service_makers))
    
    def test_web_root_cap_empty(self):
        self.assertRaises(ConfigException, lambda:
            self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101', root_cap=''))
        self.assertEqual([], self.config._service_makers)
    
    def test_web_root_cap_none(self):
        self.config.serve_web(http_endpoint='tcp:0', ws_endpoint='tcp:0')
        self.assertEqual(1, len(self.config._service_makers))
        # Actually instantiating the service. We need to do this to check if the root_cap value was processed correctly.
        service = self.config._service_makers[0](DummyAppRoot())
        self.assertEqual('/public/', service.get_host_relative_url())
    
    # --- serve_ghpsdr ---
    
    @defer.inlineCallbacks
    def test_ghpsdr_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.serve_ghpsdr())
        self.assertEqual({}, self.config.devices._values)
    
    def test_ghpsdr_ok(self):
        self.config.serve_ghpsdr()
        self.assertEqual(1, len(self.config._service_makers))
    
    # --- Misc options ---
    
    @defer.inlineCallbacks
    def test_server_audio_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.set_server_audio_allowed(True))
        self.assertEqual({}, self.config.devices._values)
    
    # TODO test rest of config.set_server_audio_allowed

    @defer.inlineCallbacks
    def test_stereo_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.set_stereo(True))
        self.assertEqual({}, self.config.devices._values)
    
    # TODO test rest of config.set_stereo
    
    # --- Features ---
    
    def test_features_unknown(self):
        self.assertRaises(ConfigException, lambda:
            self.config.features.enable('bogus'))
        self.assertFalse('bogus' in self.config.features._state)
    
    @defer.inlineCallbacks
    def test_features_enable_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.features.enable('_test_disabled_feature'))
        self.assertFalse(self.config.features._get('_test_disabled_feature'))
    
    @defer.inlineCallbacks
    def test_features_disable_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(ConfigTooLateException, lambda:
            self.config.features.enable('_test_enabled_feature'))
        self.assertTrue(self.config.features._get('_test_enabled_feature'))
Esempio n. 13
0
 def setUp(self):
     self.__temp_dir = tempfile.mkdtemp(prefix='shinysdr_test_config_tmp')
     self.__config_name = os.path.join(self.__temp_dir, 'config')
     self.__config = Config(the_reactor)
Esempio n. 14
0
class TestConfigFiles(unittest.TestCase):
    def setUp(self):
        self.__temp_dir = tempfile.mkdtemp(prefix='shinysdr_test_config_tmp')
        self.__config_name = os.path.join(self.__temp_dir, 'config')
        self.__config = Config(the_reactor)
    
    def tearDown(self):
        shutil.rmtree(self.__temp_dir)
    
    def __dirpath(self, *paths):
        return os.path.join(self.__config_name, *paths)
    
    def test_config_file(self):
        with open(self.__config_name, 'w') as f:
            f.write('config.features.enable("_test_disabled_feature")')
        # DB CSV file we expect NOT to be loaded
        os.mkdir(os.path.join(self.__temp_dir, 'dbs'))
        with open(os.path.join(self.__temp_dir, 'dbs', 'foo.csv'), 'w') as f:
            f.write('Frequency,Name')

        execute_config(self.__config, self.__config_name)
        
        # Config python was executed
        self.assertTrue(self.__config.features._get('_test_disabled_feature'))
        
        # Config-directory-related defaults were not set
        self.assertEquals(None, self.__config._state_filename)
        self.assertEquals(get_default_dbs().viewkeys(), self.__config.databases._get_read_only_databases().viewkeys())
    
    def test_config_directory(self):
        os.mkdir(self.__config_name)
        with open(self.__dirpath('config.py'), 'w') as f:
            f.write('config.features.enable("_test_disabled_feature")')
        os.mkdir(self.__dirpath('dbs-read-only'))
        with open(self.__dirpath('dbs-read-only', 'foo.csv'), 'w') as f:
            f.write('Frequency,Name')
        execute_config(self.__config, self.__config_name)
        
        # Config python was executed
        self.assertTrue(self.__config.features._get('_test_disabled_feature'))
        
        # Config-directory-related defaults were set
        self.assertEquals(self.__dirpath('state.json'), self.__config._state_filename)
        self.assertIn('foo.csv', self.__config.databases._get_read_only_databases())
    
    def test_default_config(self):
        write_default_config(self.__config_name)
        self.assertTrue(os.path.isdir(self.__config_name))
        
        # Don't try to open a real device
        with open(self.__dirpath('config.py'), 'r') as f:
            conf_text = f.read()
        DEFAULT_DEVICE = "OsmoSDRDevice('')"
        self.assertIn(DEFAULT_DEVICE, conf_text)
        conf_text = conf_text.replace(DEFAULT_DEVICE, "OsmoSDRDevice('file=/dev/null,rate=100000')")
        with open(self.__dirpath('config.py'), 'w') as f:
            f.write(conf_text)
        
        execute_config(self.__config, self.__config_name)
        
        self.assertTrue(os.path.isdir(self.__dirpath('dbs-read-only')))
        return self.__config._wait_and_validate()
Esempio n. 15
0
class TestConfigObject(unittest.TestCase):
    def setUp(self):
        self.config = Config(the_reactor)
    
    # TODO: In type error tests, also check message once we've cleaned them up.
    
    # --- General functionality ---
    
    def test_reactor(self):
        self.assertEqual(self.config.reactor, the_reactor)
    
    # TODO def test_wait_for(self):
    
    @defer.inlineCallbacks
    def test_validate_succeed(self):
        self.config.devices.add(u'foo', StubDevice())
        d = self.config._wait_and_validate()
        self.assertIsInstance(d, defer.Deferred)  # don't succeed trivially
        yield d
    
    # TODO: Test "No network service defined"; is a warning not an error

    # --- Persistence ---
    
    @defer.inlineCallbacks
    def test_persist_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda:
            self.config.persist_to_file('foo'))
        self.assertEqual({}, self.config.devices._values)
    
    def test_persist_none(self):
        self.assertEqual(None, self.config._state_filename)

    def test_persist_ok(self):
        self.config.persist_to_file('foo')
        self.assertEqual('foo', self.config._state_filename)

    def test_persist_duplication(self):
        self.config.persist_to_file('foo')
        self.assertRaises(ValueError, lambda: self.config.persist_to_file('bar'))
        self.assertEqual('foo', self.config._state_filename)

    # --- Devices ---
    
    @defer.inlineCallbacks
    def test_device_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda:
            self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({}, self.config.devices._values)
    
    def test_device_key_ok(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))
    
    def test_device_key_string_ok(self):
        dev = StubDevice()
        self.config.devices.add('foo', dev)
        self.assertEqual({u'foo': dev}, self.config.devices._values)
        self.assertEqual(unicode, type(self.config.devices._values.keys()[0]))
    
    def test_device_key_type(self):
        self.assertRaises(TypeError, lambda:
            self.config.devices.add(StubDevice(), StubDevice()))
        self.assertEqual({}, self.config.devices._values)
    
    def test_device_key_duplication(self):
        dev = StubDevice()
        self.config.devices.add(u'foo', dev)
        self.assertRaises(KeyError, lambda:
            self.config.devices.add(u'foo', StubDevice()))
        self.assertEqual({u'foo': dev}, self.config.devices._values)
    
    def test_device_empty(self):
        self.assertRaises(ValueError, lambda:
            self.config.devices.add(u'foo'))
        self.assertEqual({}, self.config.devices._values)
    
    # --- serve_web ---
    
    @defer.inlineCallbacks
    def test_web_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda:
            self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101'))
        self.assertEqual({}, self.config.devices._values)
    
    def test_web_ok(self):
        self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101')
        self.assertEqual(1, len(self.config._service_makers))
    
    def test_web_root_cap_empty(self):
        self.assertRaises(ValueError, lambda:
            self.config.serve_web(http_endpoint='tcp:8100', ws_endpoint='tcp:8101', root_cap=''))
        self.assertEqual([], self.config._service_makers)
    
    # --- serve_ghpsdr ---
    
    @defer.inlineCallbacks
    def test_ghpsdr_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda:
            self.config.serve_ghpsdr())
        self.assertEqual({}, self.config.devices._values)
    
    def test_ghpsdr_ok(self):
        self.config.serve_ghpsdr()
        self.assertEqual(1, len(self.config._service_makers))
    
    # --- Misc options ---
    
    @defer.inlineCallbacks
    def test_server_audio_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda:
            self.config.set_server_audio_allowed(True))
        self.assertEqual({}, self.config.devices._values)
    
    # TODO test rest of config.set_server_audio_allowed

    @defer.inlineCallbacks
    def test_stereo_too_late(self):
        yield self.config._wait_and_validate()
        self.assertRaises(Exception, lambda:
            self.config.set_stereo(True))
        self.assertEqual({}, self.config.devices._values)
Esempio n. 16
0
 def setUp(self):
     self.config = Config(the_reactor)
Esempio n. 17
0
 def setUp(self):
     self.__temp_dir = tempfile.mkdtemp(prefix='shinysdr_test_config_tmp')
     self.__config_name = os.path.join(self.__temp_dir, 'config')
     self.__config = Config(the_reactor)
Esempio n. 18
0
def _main_async(reactor, argv=None, _abort_for_test=False):
    if argv is None:
        argv = sys.argv
    
    if not _abort_for_test:
        # Some log messages would be discarded if we did not set up things early.
        configure_logging()
    
    # Option parsing is done before importing the main modules so as to avoid the cost of initializing gnuradio if we are aborting early. TODO: Make that happen for createConfig too.
    argParser = argparse.ArgumentParser(prog=argv[0])
    argParser.add_argument('config_path', metavar='CONFIG',
        help='path of configuration directory or file')
    argParser.add_argument('--create', dest='createConfig', action='store_true',
        help='write template configuration file to CONFIG and exit')
    argParser.add_argument('-g, --go', dest='openBrowser', action='store_true',
        help='open the UI in a web browser')
    argParser.add_argument('--force-run', dest='force_run', action='store_true',
        help='Run DSP even if no client is connected (for debugging).')
    args = argParser.parse_args(args=argv[1:])

    # Verify we can actually run.
    # Note that this must be done before we actually load core modules, because we might get an import error then.
    version_report = yield _check_versions()
    if version_report:
        print(version_report, file=sys.stderr)
        sys.exit(1)

    # Write config file and exit if asked ...
    if args.createConfig:
        write_default_config(args.config_path)
        _log.info('Created default configuration at: {config_path}', config_path=args.config_path)
        sys.exit(0)  # TODO: Consider using a return value or something instead
    
    # ... else read config file
    config_obj = Config(reactor=reactor, log=_log)
    execute_config(config_obj, args.config_path)
    yield config_obj._wait_and_validate()
    
    _log.info('Constructing...')
    app = config_obj._create_app()
    
    reactor.addSystemEventTrigger('during', 'shutdown', app.close_all_devices)
    
    _log.info('Restoring state...')
    pfg = PersistenceFileGlue(
        reactor=reactor,
        root_object=app,
        filename=config_obj._state_filename,
        get_defaults=_app_defaults)
    
    _log.info('Starting web server...')
    services = MultiService()
    for maker in config_obj._service_makers:
        IService(maker(app)).setServiceParent(services)
    services.startService()
    
    _log.info('ShinySDR is ready.')
    
    for service in services:
        # TODO: should have an interface (currently no proper module to put it in)
        service.announce(args.openBrowser)
    
    if args.force_run:
        _log.debug('force_run')
        # TODO kludge, make this less digging into guts
        app.get_receive_flowgraph().get_monitor().state()['fft'].subscribe2(lambda v: None, the_subscription_context)
    
    if _abort_for_test:
        services.stopService()
        yield pfg.sync()
        defer.returnValue(app)
    else:
        yield defer.Deferred()  # never fires
Esempio n. 19
0
def get_default_dbs():
    config_obj = Config(the_reactor)
    return config_obj.databases._get_read_only_databases()