From ce313a5b22ca0cd3f8a1dbb3678661671b148642 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 13:29:10 -0500
Subject: [PATCH 01/33] docstring formatting

---
 .../media-monitor2/media/monitor/manager.py   | 98 +++++++------------
 1 file changed, 34 insertions(+), 64 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/manager.py b/python_apps/media-monitor2/media/monitor/manager.py
index 548590f38..2c5512ec6 100644
--- a/python_apps/media-monitor2/media/monitor/manager.py
+++ b/python_apps/media-monitor2/media/monitor/manager.py
@@ -13,11 +13,10 @@ import media.monitor.pure as mmp
 
 
 class ManagerTimeout(threading.Thread,Loggable):
-    """
-    The purpose of this class is to flush the organize directory every 3
-    secnods. This used to be just a work around for cc-4235 but recently
-    became a permanent solution because it's "cheap" and reliable
-    """
+    """ The purpose of this class is to flush the organize directory
+    every 3 secnods. This used to be just a work around for cc-4235
+    but recently became a permanent solution because it's "cheap" and
+    reliable """
     def __init__(self, manager, interval=1.5):
         # TODO : interval should be read from config and passed here instead
         # of just using the hard coded value
@@ -30,11 +29,9 @@ class ManagerTimeout(threading.Thread,Loggable):
             self.manager.flush_organize()
 
 class Manager(Loggable):
-    """
-    An abstraction over media monitors core pyinotify functions. These
-    include adding watched,store, organize directories, etc. Basically
-    composes over WatchManager from pyinotify
-    """
+    """ An abstraction over media monitors core pyinotify functions.
+    These include adding watched,store, organize directories, etc.
+    Basically composes over WatchManager from pyinotify """
     def __init__(self):
         self.wm = pyinotify.WatchManager()
         # These two instance variables are assumed to be constant
@@ -76,23 +73,19 @@ class Manager(Loggable):
     # through dedicated handler objects. Because we must have access to a
     # manager instance. Hence we must slightly break encapsulation.
     def watch_move(self, watch_dir, sender=None):
-        """
-        handle 'watch move' events directly sent from listener
-        """
+        """ handle 'watch move' events directly sent from listener """
         self.logger.info("Watch dir '%s' has been renamed (hence removed)" %
                 watch_dir)
         self.remove_watch_directory(normpath(watch_dir))
 
     def watch_signal(self):
-        """
-        Return the signal string our watch_listener is reading events from
-        """
+        """ Return the signal string our watch_listener is reading
+        events from """
         return self.watch_listener.signal
 
     def __remove_watch(self,path):
-        """
-        Remove path from being watched (first will check if 'path' is watched)
-        """
+        """ Remove path from being watched (first will check if 'path'
+        is watched) """
         # only delete if dir is actually being watched
         if path in self.__wd_path:
             wd = self.__wd_path[path]
@@ -100,10 +93,8 @@ class Manager(Loggable):
             del(self.__wd_path[path])
 
     def __add_watch(self,path,listener):
-        """
-        Start watching 'path' using 'listener'. First will check if directory
-        is being watched before adding another watch
-        """
+        """ Start watching 'path' using 'listener'. First will check if
+        directory is being watched before adding another watch """
 
         self.logger.info("Attempting to add listener to path '%s'" % path)
         self.logger.info( 'Listener: %s' % str(listener) )
@@ -114,9 +105,8 @@ class Manager(Loggable):
             if wd: self.__wd_path[path] = wd.values()[0]
 
     def __create_organizer(self, target_path, recorded_path):
-        """
-        creates an organizer at new destination path or modifies the old one
-        """
+        """ creates an organizer at new destination path or modifies the
+        old one """
         # TODO : find a proper fix for the following hack
         # We avoid creating new instances of organize because of the way
         # it interacts with pydispatch. We must be careful to never have
@@ -134,23 +124,17 @@ class Manager(Loggable):
                     recorded_path=recorded_path)
 
     def get_problem_files_path(self):
-        """
-        returns the path where problem files should go
-        """
+        """ returns the path where problem files should go """
         return self.organize['problem_files_path']
 
     def set_problem_files_path(self, new_path):
-        """
-        Set the path where problem files should go
-        """
+        """ Set the path where problem files should go """
         self.organize['problem_files_path'] = new_path
         self.organize['problem_handler'] = \
             ProblemFileHandler( PathChannel(signal='badfile',path=new_path) )
 
     def get_recorded_path(self):
-        """
-        returns the path of the recorded directory
-        """
+        """ returns the path of the recorded directory """
         return self.organize['recorded_path']
 
     def set_recorded_path(self, new_path):
@@ -160,17 +144,14 @@ class Manager(Loggable):
         self.__add_watch(new_path, self.watch_listener)
 
     def get_organize_path(self):
-        """
-        returns the current path that is being watched for organization
-        """
+        """ returns the current path that is being watched for
+        organization """
         return self.organize['organize_path']
 
     def set_organize_path(self, new_path):
-        """
-        sets the organize path to be new_path. Under the current scheme there
-        is only one organize path but there is no reason why more cannot be
-        supported
-        """
+        """ sets the organize path to be new_path. Under the current
+        scheme there is only one organize path but there is no reason
+        why more cannot be supported """
         # if we are already organizing a particular directory we remove the
         # watch from it first before organizing another directory
         self.__remove_watch(self.organize['organize_path'])
@@ -188,19 +169,15 @@ class Manager(Loggable):
         return self.organize['imported_path']
 
     def set_imported_path(self,new_path):
-        """
-        set the directory where organized files go to.
-        """
+        """ set the directory where organized files go to. """
         self.__remove_watch(self.organize['imported_path'])
         self.organize['imported_path'] = new_path
         self.__create_organizer( new_path, self.organize['recorded_path'])
         self.__add_watch(new_path, self.watch_listener)
 
     def change_storage_root(self, store):
-        """
-        hooks up all the directories for you. Problem, recorded, imported,
-        organize.
-        """
+        """ hooks up all the directories for you. Problem, recorded,
+        imported, organize. """
         store_paths = mmp.expand_storage(store)
         # First attempt to make sure that all paths exist before adding any
         # watches
@@ -217,18 +194,14 @@ class Manager(Loggable):
             mmp.create_dir(p)
 
     def has_watch(self, path):
-        """
-        returns true if the path is being watched or not. Any kind of watch:
-        organize, store, watched.
-        """
+        """ returns true if the path is being watched or not. Any kind
+        of watch: organize, store, watched. """
         return path in self.__wd_path
 
     def add_watch_directory(self, new_dir):
-        """
-        adds a directory to be "watched". "watched" directories are
+        """ adds a directory to be "watched". "watched" directories are
         those that are being monitored by media monitor for airtime in
-        this context and not directories pyinotify calls watched
-        """
+        this context and not directories pyinotify calls watched """
         if self.has_watch(new_dir):
             self.logger.info("Cannot add '%s' to watched directories. It's \
                     already being watched" % new_dir)
@@ -237,9 +210,8 @@ class Manager(Loggable):
             self.__add_watch(new_dir, self.watch_listener)
 
     def remove_watch_directory(self, watch_dir):
-        """
-        removes a directory from being "watched". Undoes add_watch_directory
-        """
+        """ removes a directory from being "watched". Undoes
+        add_watch_directory """
         if self.has_watch(watch_dir):
             self.logger.info("Removing watched directory: '%s'", watch_dir)
             self.__remove_watch(watch_dir)
@@ -250,9 +222,7 @@ class Manager(Loggable):
             self.logger.info( self.__wd_path )
 
     def loop(self):
-        """
-        block until we receive pyinotify events
-        """
+        """ block until we receive pyinotify events """
         notifier = pyinotify.Notifier(self.wm)
         notifier.coalesce_events()
         notifier.loop()

From c287e11bee8dc50a3129d5bef3d17b12755386e0 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 14:02:55 -0500
Subject: [PATCH 02/33] moved setting up unicde into it's own routine

---
 python_apps/api_clients/api_client.py |  1 +
 python_apps/media-monitor2/mm2.py     | 34 +++++++++++++++------------
 2 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py
index ac93aa4c6..633993903 100644
--- a/python_apps/api_clients/api_client.py
+++ b/python_apps/api_clients/api_client.py
@@ -389,3 +389,4 @@ class AirtimeApiClient(object):
         """
         self.logger.info( self.notify_webstream_data.req(
             _post_data={'data':data}, media_id=str(media_id)).retry(5))
+
diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 2abace71c..dc9cd160a 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -24,6 +24,24 @@ import media.monitor.pure          as mmp
 from api_clients import api_client as apc
 
 
+def setup_global(log):
+    """ setup unicode and other stuff """
+    log.info("Attempting to set the locale...")
+    try:
+        mmp.configure_locale(mmp.get_system_locale())
+    except FailedToSetLocale as e:
+        log.info("Failed to set the locale...")
+        sys.exit(1)
+    except FailedToObtainLocale as e:
+        log.info("Failed to obtain the locale form the default path: \
+                '/etc/default/locale'")
+        sys.exit(1)
+    except Exception as e:
+        log.info("Failed to set the locale for unknown reason. \
+                Logging exception.")
+        log.info(str(e))
+
+
 
 def main(global_config, api_client_config, log_config,
         index_create_attempt=False):
@@ -72,21 +90,7 @@ def main(global_config, api_client_config, log_config,
     if not os.path.exists(config['index_path']):
         log.info("Index file does not exist. Terminating")
 
-    log.info("Attempting to set the locale...")
-
-    try:
-        mmp.configure_locale(mmp.get_system_locale())
-    except FailedToSetLocale as e:
-        log.info("Failed to set the locale...")
-        sys.exit(1)
-    except FailedToObtainLocale as e:
-        log.info("Failed to obtain the locale form the default path: \
-                '/etc/default/locale'")
-        sys.exit(1)
-    except Exception as e:
-        log.info("Failed to set the locale for unknown reason. \
-                Logging exception.")
-        log.info(str(e))
+    setup_global(log)
 
     watch_syncer = WatchSyncer(signal='watch',
                                chunking_number=config['chunking_number'],

From 3509cfc2128b9547915f9487c978504ac6924ffb Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 14:03:46 -0500
Subject: [PATCH 03/33] formatting

---
 python_apps/media-monitor2/mm2.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index dc9cd160a..7e786722d 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -27,8 +27,7 @@ from api_clients import api_client as apc
 def setup_global(log):
     """ setup unicode and other stuff """
     log.info("Attempting to set the locale...")
-    try:
-        mmp.configure_locale(mmp.get_system_locale())
+    try: mmp.configure_locale(mmp.get_system_locale())
     except FailedToSetLocale as e:
         log.info("Failed to set the locale...")
         sys.exit(1)

From 83d4705bcadd079c88d2b1acccc47b72d33e83a2 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 14:19:37 -0500
Subject: [PATCH 04/33] removed useless comments

---
 python_apps/media-monitor2/media/monitor/manager.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/manager.py b/python_apps/media-monitor2/media/monitor/manager.py
index 2c5512ec6..e64d1c985 100644
--- a/python_apps/media-monitor2/media/monitor/manager.py
+++ b/python_apps/media-monitor2/media/monitor/manager.py
@@ -38,8 +38,6 @@ class Manager(Loggable):
         self.watch_channel    = 'watch'
         self.organize_channel = 'organize'
         self.watch_listener   = StoreWatchListener(signal = self.watch_channel)
-        # TODO : change this to  a weak ref
-        # TODO : get rid of this hack once cc-4235 is fixed
         self.__timeout_thread = ManagerTimeout(self)
         self.__timeout_thread.daemon = True
         self.__timeout_thread.start()

From a851d8dd7426b88712b2fca81d0ea81ecae0843b Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 16:04:39 -0500
Subject: [PATCH 05/33] docstring format

---
 .../media-monitor2/media/monitor/config.py    | 20 ++++++++-----------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/config.py b/python_apps/media-monitor2/media/monitor/config.py
index 3a7be8eb5..b06e00a84 100644
--- a/python_apps/media-monitor2/media/monitor/config.py
+++ b/python_apps/media-monitor2/media/monitor/config.py
@@ -12,25 +12,21 @@ class MMConfig(object):
         self.cfg = ConfigObj(path)
 
     def __getitem__(self, key):
-        """
-        We always return a copy of the config item to prevent callers from
-        doing any modifications through the returned objects methods
-        """
+        """ We always return a copy of the config item to prevent
+        callers from doing any modifications through the returned
+        objects methods """
         return copy.deepcopy(self.cfg[key])
 
     def __setitem__(self, key, value):
-        """
-        We use this method not to allow anybody to mess around with config file
-        any settings made should be done through MMConfig's instance methods
-        """
+        """ We use this method not to allow anybody to mess around with
+        config file any settings made should be done through MMConfig's
+        instance methods """
         raise ConfigAccessViolation(key)
 
     def save(self): self.cfg.write()
 
     def last_ran(self):
-        """
-        Returns the last time media monitor was ran by looking at the time when
-        the file at 'index_path' was modified
-        """
+        """ Returns the last time media monitor was ran by looking at
+        the time when the file at 'index_path' was modified """
         return mmp.last_modified(self.cfg['index_path'])
 

From 6392c69e27abe91679898e78fe1dccdf14beb875 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 16:10:59 -0500
Subject: [PATCH 06/33] Removed unused code

---
 python_apps/media-monitor2/mm2.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 7e786722d..9471f698b 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -151,9 +151,6 @@ Options:
     --log=<path>       log config at <path>
 """
 
-def main_loop():
-    while True: pass
-
 if __name__ == '__main__':
     from docopt import docopt
     args = docopt(__doc__,version="mm1.99")

From 66138c4bafc5f831216ac935ee879d69833cb79f Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Mon, 5 Nov 2012 16:11:35 -0500
Subject: [PATCH 07/33] removed references to gevent

---
 python_apps/media-monitor2/mm2.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 9471f698b..99d16f9f7 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -160,5 +160,4 @@ if __name__ == '__main__':
             sys.exit(0)
     print("Running mm1.99")
     main(args['--config'],args['--apiclient'],args['--log'])
-    #gevent.joinall([ gevent.spawn(main_loop) ])
 

From 110008e9091968837d0137e1b61a29851c248154 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 11:29:39 -0500
Subject: [PATCH 08/33] added saas shit

---
 .../media-monitor2/media/saas/__init__.py     |  0
 .../media/saas/airtimeinstance.py             | 32 +++++++++++++++++++
 .../media-monitor2/media/saas/thread.py       | 19 +++++++++++
 3 files changed, 51 insertions(+)
 create mode 100644 python_apps/media-monitor2/media/saas/__init__.py
 create mode 100644 python_apps/media-monitor2/media/saas/airtimeinstance.py
 create mode 100644 python_apps/media-monitor2/media/saas/thread.py

diff --git a/python_apps/media-monitor2/media/saas/__init__.py b/python_apps/media-monitor2/media/saas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/python_apps/media-monitor2/media/saas/airtimeinstance.py b/python_apps/media-monitor2/media/saas/airtimeinstance.py
new file mode 100644
index 000000000..ed37caa41
--- /dev/null
+++ b/python_apps/media-monitor2/media/saas/airtimeinstance.py
@@ -0,0 +1,32 @@
+import os
+
+from media.monitor.exceptions import NoConfigFile
+from media.monitor.pure import LazyProperty
+from media.monitor.config import MMConfig
+from api_cients import AirtimeApiClient
+
+class AirtimeInstance(object):
+    """ AirtimeInstance is a class that abstracts away every airtime
+    instance by providing all the necessary objects required to interact
+    with the instance. ApiClient, configs, root_directory """
+
+    def __init__(self,name, root_path, config_paths):
+        """ name is an internal name only """
+        for cfg in ['api_client','media_monitor', 'logging']:
+            if cfg not in config_paths: raise NoConfigFile(config_paths)
+            elif not os.path.exists(config_paths[cfg]):
+                raise NoConfigFile(config_paths[cfg])
+        self.name         = name
+        self.config_paths = config_paths
+        self.root_path    = root_path
+
+    def __str__(self):
+        return "%s,%s(%s)" % (self.name, self.root_path, self.config_paths)
+
+    @LazyProperty
+    def api_client(self):
+        return AirtimeApiClient(config_path=self.config_paths['api_client'])
+
+    @LazyProperty
+    def mm_config(self):
+        return MMConfig(self.config_paths['media_monitor'])
diff --git a/python_apps/media-monitor2/media/saas/thread.py b/python_apps/media-monitor2/media/saas/thread.py
new file mode 100644
index 000000000..b71c4a8fb
--- /dev/null
+++ b/python_apps/media-monitor2/media/saas/thread.py
@@ -0,0 +1,19 @@
+import threading
+
+tc = threading.local()
+
+class InstanceThread(threading.Thread):
+    def __init__(self,user, *args, **kwargs):
+        super(InstanceThread, self).__init__(*args, **kwargs)
+        self._user = user
+
+    def run(self):
+        tc._user = self._user
+        
+    def user(self):
+        return tc._user
+
+class InstanceInheritingThread(threading.Thread):
+    def __init__(self, *args, **kwargs):
+        super(InstanceInheritingThread, self).__init__(*args, **kwargs)
+        self.user = tc._user

From 882a515caa4101ab009d16de1ef65f7cbcbc9027 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 17:44:55 -0500
Subject: [PATCH 09/33] refactored threads which know the user they belong to

---
 python_apps/media-monitor2/media/saas/thread.py | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/python_apps/media-monitor2/media/saas/thread.py b/python_apps/media-monitor2/media/saas/thread.py
index b71c4a8fb..489ad35a8 100644
--- a/python_apps/media-monitor2/media/saas/thread.py
+++ b/python_apps/media-monitor2/media/saas/thread.py
@@ -2,18 +2,16 @@ import threading
 
 tc = threading.local()
 
-class InstanceThread(threading.Thread):
+class HasUser(object):
+    def user(self): 
+        return self._user
+
+class InstanceThread(threading.Thread, HasUser):
     def __init__(self,user, *args, **kwargs):
         super(InstanceThread, self).__init__(*args, **kwargs)
         self._user = user
-
-    def run(self):
-        tc._user = self._user
         
-    def user(self):
-        return tc._user
-
-class InstanceInheritingThread(threading.Thread):
+class InstanceInheritingThread(threading.Thread, HasUser):
     def __init__(self, *args, **kwargs):
+        self._user = threading.current_thread().user()
         super(InstanceInheritingThread, self).__init__(*args, **kwargs)
-        self.user = tc._user

From 9e7b8a6b28409b5a27db32c2f1420e6bab481917 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 17:45:08 -0500
Subject: [PATCH 10/33] added tests

---
 .../media-monitor2/tests/test_thread.py       | 64 +++++++++++++++++++
 1 file changed, 64 insertions(+)
 create mode 100644 python_apps/media-monitor2/tests/test_thread.py

diff --git a/python_apps/media-monitor2/tests/test_thread.py b/python_apps/media-monitor2/tests/test_thread.py
new file mode 100644
index 000000000..1638a60e3
--- /dev/null
+++ b/python_apps/media-monitor2/tests/test_thread.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+import unittest
+import time
+from media.saas.thread import InstanceThread, InstanceInheritingThread
+
+# ugly but necessary for 2.7
+signal = False
+signal2 = False
+
+class TestInstanceThread(unittest.TestCase):
+    def test_user_inject(self):
+        global signal
+        signal = False
+        u = "rudi"
+        class T(InstanceThread):
+            def run(me):
+                global signal
+                super(T, me).run()
+                signal = True
+                self.assertEquals(u, me.user())
+        t = T(u, name="test_user_inject")
+        t.daemon = True 
+        t.start()
+        time.sleep(0.2)
+        self.assertTrue(signal)
+
+    def test_inheriting_thread(utest):
+        global signal2
+        u = "testing..."
+
+        class TT(InstanceInheritingThread):
+            def run(self):
+                global signal2
+                utest.assertEquals(self.user(), u)
+                signal2 = True
+
+        class T(InstanceThread):
+            def run(self):
+                super(T, self).run()
+                child_thread = TT(name="child thread")
+                child_thread.daemon = True
+                child_thread.start()
+
+        parent_thread = T(u, name="Parent instance thread")
+        parent_thread.daemon = True
+        parent_thread.start()
+
+        time.sleep(0.2)
+        utest.assertTrue(signal2)
+
+    def test_different_user(utest):
+        u1, u2 = "ru", "di"
+        class T(InstanceThread):
+            def run(self):
+                super(T, self).run()
+
+        for u in [u1, u2]:
+            t = T(u)
+            t.daemon = True
+            t.start()
+            utest.assertEquals(t.user(), u)
+
+
+if __name__ == '__main__': unittest.main()

From 13f59be21de8fa41a2b20f0b7626e4ec1e6dc0dc Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 18:16:33 -0500
Subject: [PATCH 11/33] typo

---
 python_apps/media-monitor2/media/saas/airtimeinstance.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python_apps/media-monitor2/media/saas/airtimeinstance.py b/python_apps/media-monitor2/media/saas/airtimeinstance.py
index ed37caa41..293fc84c2 100644
--- a/python_apps/media-monitor2/media/saas/airtimeinstance.py
+++ b/python_apps/media-monitor2/media/saas/airtimeinstance.py
@@ -3,7 +3,7 @@ import os
 from media.monitor.exceptions import NoConfigFile
 from media.monitor.pure import LazyProperty
 from media.monitor.config import MMConfig
-from api_cients import AirtimeApiClient
+from api_clients import AirtimeApiClient
 
 class AirtimeInstance(object):
     """ AirtimeInstance is a class that abstracts away every airtime

From 7e5ec6505b142a466296795c2b909705a23474c4 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 18:16:48 -0500
Subject: [PATCH 12/33] added tests for instance

---
 .../media-monitor2/tests/test_instance.py     | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 python_apps/media-monitor2/tests/test_instance.py

diff --git a/python_apps/media-monitor2/tests/test_instance.py b/python_apps/media-monitor2/tests/test_instance.py
new file mode 100644
index 000000000..badaadf07
--- /dev/null
+++ b/python_apps/media-monitor2/tests/test_instance.py
@@ -0,0 +1,21 @@
+import unittest
+from copy import deepcopy
+from media.saas.airtimeinstance import AirtimeInstance, NoConfigFile
+
+class TestAirtimeInstance(unittest.TestCase):
+    def setUp(self):
+        self.cfg = {
+            'api_client'    : 'tests/test_instance.py',
+            'media_monitor' : 'tests/test_instance.py',
+            'logging'       : 'tests/test_instance.py',
+        }
+
+    def test_init_good(self):
+        AirtimeInstance("/root", self.cfg)
+        self.assertTrue(True)
+
+    def test_init_bad(self):
+        cfg = deepcopy(self.cfg)
+        cfg['api_client'] = 'bs'
+        with self.assertRaises(NoConfigFile):
+            AirtimeInstance("/root", cfg)

From ed00089a1a6df7ba6439ead4b600ecdb7e9e2acb Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 22:53:39 -0500
Subject: [PATCH 13/33] added user level method to get current user of thread

---
 python_apps/media-monitor2/media/saas/thread.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/python_apps/media-monitor2/media/saas/thread.py b/python_apps/media-monitor2/media/saas/thread.py
index 489ad35a8..f2e5ae28d 100644
--- a/python_apps/media-monitor2/media/saas/thread.py
+++ b/python_apps/media-monitor2/media/saas/thread.py
@@ -1,10 +1,12 @@
 import threading
 
-tc = threading.local()
+class UserlessThread(Exception):
+    def __str__():
+        return "Current thread: %s is not an instance of InstanceThread \
+                of InstanceInheritingThread" % str(threading.current_thread())
 
 class HasUser(object):
-    def user(self): 
-        return self._user
+    def user(self): return self._user
 
 class InstanceThread(threading.Thread, HasUser):
     def __init__(self,user, *args, **kwargs):
@@ -15,3 +17,7 @@ class InstanceInheritingThread(threading.Thread, HasUser):
     def __init__(self, *args, **kwargs):
         self._user = threading.current_thread().user()
         super(InstanceInheritingThread, self).__init__(*args, **kwargs)
+
+def user():
+    try: return threading.current_thread().user()
+    except AttributeError: raise UserlessThread()

From 55f0462946a7e4050050d1572c619780a8e81c51 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 23:21:08 -0500
Subject: [PATCH 14/33] removed useless line of code

---
 python_apps/media-monitor2/media/saas/thread.py | 2 ++
 python_apps/media-monitor2/mm2.py               | 2 --
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/python_apps/media-monitor2/media/saas/thread.py b/python_apps/media-monitor2/media/saas/thread.py
index f2e5ae28d..0b4ae079e 100644
--- a/python_apps/media-monitor2/media/saas/thread.py
+++ b/python_apps/media-monitor2/media/saas/thread.py
@@ -21,3 +21,5 @@ class InstanceInheritingThread(threading.Thread, HasUser):
 def user():
     try: return threading.current_thread().user()
     except AttributeError: raise UserlessThread()
+
+def apc(): return user().api_client
diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 99d16f9f7..b246a9491 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -124,8 +124,6 @@ def main(global_config, api_client_config, log_config,
             airtime_receiver.new_watch({ 'directory':watch_dir }, restart=True)
         else: log.info("Failed to add watch on %s" % str(watch_dir))
 
-    bs = Bootstrapper( db=sdb, watch_signal='watch' )
-
     ed = EventDrainer(airtime_notifier.connection,
             interval=float(config['rmq_event_wait']))
 

From c222ac10f74e5793889d3c0d35ceb2f821a11e33 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 23:21:48 -0500
Subject: [PATCH 15/33] typo

---
 python_apps/media-monitor2/mm2.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index b246a9491..8c1c35abb 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -48,7 +48,7 @@ def main(global_config, api_client_config, log_config,
         if not os.path.exists(cfg): raise NoConfigFile(cfg)
     # MMConfig is a proxy around ConfigObj instances. it does not allow
     # itself users of MMConfig instances to modify any config options
-    # directly through the dictionary. Users of this object muse use the
+    # directly through the dictionary. Users of this object must use the
     # correct methods designated for modification
     try: config = MMConfig(global_config)
     except NoConfigFile as e:

From eaf0baa6b6501c88774ed9aa0e24d99be0531ca0 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 7 Nov 2012 23:22:25 -0500
Subject: [PATCH 16/33] removed useless code

---
 python_apps/media-monitor2/mm2.py | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 8c1c35abb..9d12ed32d 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -5,7 +5,6 @@ import logging
 import logging.config
 
 from media.monitor.manager          import Manager
-from media.monitor.bootstrap        import Bootstrapper
 from media.monitor.log              import get_logger, setup_logging
 from media.monitor.config           import MMConfig
 from media.monitor.toucher          import ToucherThread
@@ -23,7 +22,6 @@ from std_err_override               import LogWriter
 import media.monitor.pure          as mmp
 from api_clients import api_client as apc
 
-
 def setup_global(log):
     """ setup unicode and other stuff """
     log.info("Attempting to set the locale...")
@@ -41,7 +39,6 @@ def setup_global(log):
         log.info(str(e))
 
 
-
 def main(global_config, api_client_config, log_config,
         index_create_attempt=False):
     for cfg in [global_config, api_client_config]:
@@ -100,8 +97,6 @@ def main(global_config, api_client_config, log_config,
 
     ReplayGainUpdater.start_reply_gain(apiclient)
 
-    sdb = AirtimeDB(apiclient)
-
     manager = Manager()
 
     airtime_receiver = AirtimeMessageReceiver(config,manager)

From 350d5dd6204ec7cec4d0e98bf6fdc8b9738b4e59 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Sat, 10 Nov 2012 09:24:26 -0500
Subject: [PATCH 17/33] refactored logging setup when mm is setup

---
 python_apps/media-monitor2/mm2.py | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 9d12ed32d..f78768201 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -22,6 +22,17 @@ from std_err_override               import LogWriter
 import media.monitor.pure          as mmp
 from api_clients import api_client as apc
 
+def setup_logger(log_config, logpath):
+    logging.config.fileConfig(log_config)
+    #need to wait for Python 2.7 for this..
+    #logging.captureWarnings(True)
+    logger = logging.getLogger()
+    LogWriter.override_std_err(logger)
+    logfile = unicode(logpath)
+    setup_logging(logfile)
+    log = get_logger()
+    return log
+
 def setup_global(log):
     """ setup unicode and other stuff """
     log.info("Attempting to set the locale...")
@@ -55,18 +66,8 @@ def main(global_config, api_client_config, log_config,
     except Exception as e:
         print("Unknown error reading configuration file: '%s'" % global_config)
         print(str(e))
-
-
-    logging.config.fileConfig(log_config)
-
-    #need to wait for Python 2.7 for this..
-    #logging.captureWarnings(True)
-
-    logger = logging.getLogger()
-    LogWriter.override_std_err(logger)
-    logfile = unicode( config['logpath'] )
-    setup_logging(logfile)
-    log = get_logger()
+    
+    log = setup_logger( log_config, config['logpath'] )
 
     if not index_create_attempt:
         if not os.path.exists(config['index_path']):

From a727f2bb1ecb632fa797ea6bb58ab5e834ba2b09 Mon Sep 17 00:00:00 2001
From: denise <denise@denise-DX4860sourcefabric.org>
Date: Mon, 12 Nov 2012 14:28:50 -0500
Subject: [PATCH 18/33] CC-4638: Stream Settings -> Apache error when setting
 master source port to same port Airtime is running on

-fixed
---
 .../application/forms/LiveStreamingPreferences.php    | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/airtime_mvc/application/forms/LiveStreamingPreferences.php b/airtime_mvc/application/forms/LiveStreamingPreferences.php
index c5b9d9aca..5a5c6fa49 100644
--- a/airtime_mvc/application/forms/LiveStreamingPreferences.php
+++ b/airtime_mvc/application/forms/LiveStreamingPreferences.php
@@ -150,13 +150,15 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
             if ($master_harbor_input_port == $dj_harbor_input_port && $master_harbor_input_port != "") {
                 $element = $this->getElement("dj_harbor_input_port");
                 $element->addError("You cannot use same port as Master DJ port.");
+                $isValid = false;
             }
             if ($master_harbor_input_port != "") {
                 if (is_numeric($master_harbor_input_port)) {
                     if ($master_harbor_input_port != Application_Model_StreamSetting::getMasterLiveStreamPort()) {
                         $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
-                        $res = socket_bind($sock, 0, $master_harbor_input_port);
-                        if (!$res) {
+                        try {
+                            socket_bind($sock, 0, $master_harbor_input_port);
+                        } catch (Exception $e) {
                             $element = $this->getElement("master_harbor_input_port");
                             $element->addError("Port '$master_harbor_input_port' is not available.");
                             $isValid = false;
@@ -171,8 +173,9 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
                 if (is_numeric($dj_harbor_input_port)) {
                     if ($dj_harbor_input_port != Application_Model_StreamSetting::getDjLiveStreamPort()) {
                         $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
-                        $res = socket_bind($sock, 0, $dj_harbor_input_port);
-                        if (!$res) {
+                        try {
+                            socket_bind($sock, 0, $dj_harbor_input_port);
+                        } catch (Exception $e) {
                             $element = $this->getElement("dj_harbor_input_port");
                             $element->addError("Port '$dj_harbor_input_port' is not available.");
                             $isValid = false;

From 93ff2ce9f6c4c324945487ec0f413dfe5abca392 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Tue, 13 Nov 2012 17:32:42 -0500
Subject: [PATCH 19/33] removed extra import

---
 python_apps/media-monitor2/mm2.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index f78768201..0a3acfabd 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -8,7 +8,6 @@ from media.monitor.manager          import Manager
 from media.monitor.log              import get_logger, setup_logging
 from media.monitor.config           import MMConfig
 from media.monitor.toucher          import ToucherThread
-from media.monitor.syncdb           import AirtimeDB
 from media.monitor.exceptions       import FailedToObtainLocale, \
                                            FailedToSetLocale, \
                                            NoConfigFile

From f1effc37a98a2606ce8d67ebdc875bf0a4f986c1 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 14 Nov 2012 11:51:00 -0500
Subject: [PATCH 20/33] removed dependency of mm from replaygain

---
 .../media-monitor2/media/update/replaygainupdater.py        | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python_apps/media-monitor2/media/update/replaygainupdater.py b/python_apps/media-monitor2/media/update/replaygainupdater.py
index df48f09de..2f52c0a23 100644
--- a/python_apps/media-monitor2/media/update/replaygainupdater.py
+++ b/python_apps/media-monitor2/media/update/replaygainupdater.py
@@ -3,12 +3,11 @@ from threading import Thread
 import traceback
 import os
 import time
+import logging
 
 from media.update import replaygain
-from media.monitor.log import Loggable
 
-
-class ReplayGainUpdater(Thread, Loggable):
+class ReplayGainUpdater(Thread):
     """
     The purpose of the class is to query the server for a list of files which
     do not have a ReplayGain value calculated. This class will iterate over the
@@ -30,6 +29,7 @@ class ReplayGainUpdater(Thread, Loggable):
     def __init__(self,apc):
         Thread.__init__(self)
         self.api_client = apc
+        self.logger = logging.getLogger()
 
     def main(self):
         raw_response = self.api_client.list_all_watched_dirs()

From d5cacf4011995f68ad4e7e933b40294515c89f77 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 14 Nov 2012 14:43:10 -0500
Subject: [PATCH 21/33] moved replay gain to pypo for better saas performance

---
 .../media-monitor2/media/update/__init__.py   |   0
 .../media-monitor2/media/update/replaygain.py | 152 ------------------
 .../media/update/replaygainupdater.py         |  82 ----------
 python_apps/media-monitor2/mm2.py             |   3 +-
 python_apps/pypo/pypocli.py                   |   5 +
 5 files changed, 6 insertions(+), 236 deletions(-)
 delete mode 100644 python_apps/media-monitor2/media/update/__init__.py
 delete mode 100644 python_apps/media-monitor2/media/update/replaygain.py
 delete mode 100644 python_apps/media-monitor2/media/update/replaygainupdater.py

diff --git a/python_apps/media-monitor2/media/update/__init__.py b/python_apps/media-monitor2/media/update/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/python_apps/media-monitor2/media/update/replaygain.py b/python_apps/media-monitor2/media/update/replaygain.py
deleted file mode 100644
index 5af7cd4a1..000000000
--- a/python_apps/media-monitor2/media/update/replaygain.py
+++ /dev/null
@@ -1,152 +0,0 @@
-from subprocess import Popen, PIPE
-import re
-import os
-import sys
-import shutil
-import tempfile
-import logging
-
-
-logger = logging.getLogger()
-
-def get_process_output(command):
-    """
-    Run subprocess and return stdout
-    """
-    logger.debug(command)
-    p = Popen(command, shell=True, stdout=PIPE)
-    return p.communicate()[0].strip()
-
-def run_process(command):
-    """
-    Run subprocess and return "return code"
-    """
-    p = Popen(command, shell=True)
-    return os.waitpid(p.pid, 0)[1]
-
-def get_mime_type(file_path):
-    """
-    Attempts to get the mime type but will return prematurely if the process
-    takes longer than 5 seconds. Note that this function should only be called
-    for files which do not have a mp3/ogg/flac extension.
-    """
-
-    return get_process_output("timeout 5 file -b --mime-type %s" % file_path)
-
-def duplicate_file(file_path):
-    """
-    Makes a duplicate of the file and returns the path of this duplicate file.
-    """
-    fsrc = open(file_path, 'r')
-    fdst = tempfile.NamedTemporaryFile(delete=False)
-
-    logger.info("Copying %s to %s" % (file_path, fdst.name))
-
-    shutil.copyfileobj(fsrc, fdst)
-
-    fsrc.close()
-    fdst.close()
-
-    return fdst.name
-
-def get_file_type(file_path):
-    file_type = None
-    if re.search(r'mp3$', file_path, re.IGNORECASE):
-        file_type = 'mp3'
-    elif re.search(r'og(g|a)$', file_path, re.IGNORECASE):
-        file_type = 'vorbis'
-    elif re.search(r'flac$', file_path, re.IGNORECASE):
-        file_type = 'flac'
-    else:
-        mime_type = get_mime_type(file_path)
-        if 'mpeg' in mime_type:
-            file_type = 'mp3'
-        elif 'ogg' in mime_type:
-            file_type = 'vorbis'
-        elif 'flac' in mime_type:
-            file_type = 'flac'
-
-    return file_type
-
-
-def calculate_replay_gain(file_path):
-    """
-    This function accepts files of type mp3/ogg/flac and returns a calculated
-    ReplayGain value in dB.
-    If the value cannot be calculated for some reason, then we default to 0
-    (Unity Gain).
-
-    http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification
-    """
-
-    try:
-        """
-        Making a duplicate is required because the ReplayGain extraction utilities we use
-        make unwanted modifications to the file.
-        """
-
-        search = None
-        temp_file_path = duplicate_file(file_path)
-
-        file_type = get_file_type(file_path)
-        nice_level = '15'
-
-        if file_type:
-            if file_type == 'mp3':
-                if run_process("which mp3gain > /dev/null") == 0:
-                    command = 'nice -n %s mp3gain -q "%s" 2> /dev/null' \
-                            % (nice_level, temp_file_path)
-                    out = get_process_output(command)
-                    search = re.search(r'Recommended "Track" dB change: (.*)', \
-                                       out)
-                else:
-                    logger.warn("mp3gain not found")
-            elif file_type == 'vorbis':
-                command = "which vorbisgain > /dev/null  && which ogginfo > \
-                        /dev/null"
-                if run_process(command) == 0:
-                    command = 'nice -n %s vorbisgain -q -f "%s" 2>/dev/null \
-                                >/dev/null' % (nice_level,temp_file_path)
-                    run_process(command)
-
-                    out = get_process_output('ogginfo "%s"' % temp_file_path)
-                    search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
-                else:
-                    logger.warn("vorbisgain/ogginfo not found")
-            elif file_type == 'flac':
-                if run_process("which metaflac > /dev/null") == 0:
-
-                    command = 'nice -n %s metaflac --add-replay-gain "%s"' \
-                            % (nice_level, temp_file_path)
-                    run_process(command)
-
-                    command = 'nice -n %s metaflac \
-                            --show-tag=REPLAYGAIN_TRACK_GAIN "%s"' \
-                            % (nice_level, temp_file_path)
-
-                    out = get_process_output(command)
-                    search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
-                else: logger.warn("metaflac not found")
-
-    except Exception, e:
-        logger.error(str(e))
-    finally:
-        #no longer need the temp, file simply remove it.
-        try: os.remove(temp_file_path)
-        except: pass
-
-    replay_gain = 0
-    if search:
-        matches = search.groups()
-        if len(matches) == 1:
-            replay_gain = matches[0]
-        else:
-            logger.warn("Received more than 1 match in: '%s'" % str(matches))
-
-    return replay_gain
-
-
-# Example of running from command line:
-# python replay_gain.py /path/to/filename.mp3
-if __name__ == "__main__":
-    print calculate_replay_gain(sys.argv[1])
diff --git a/python_apps/media-monitor2/media/update/replaygainupdater.py b/python_apps/media-monitor2/media/update/replaygainupdater.py
deleted file mode 100644
index 2f52c0a23..000000000
--- a/python_apps/media-monitor2/media/update/replaygainupdater.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from threading import Thread
-
-import traceback
-import os
-import time
-import logging
-
-from media.update import replaygain
-
-class ReplayGainUpdater(Thread):
-    """
-    The purpose of the class is to query the server for a list of files which
-    do not have a ReplayGain value calculated. This class will iterate over the
-    list calculate the values, update the server and repeat the process until
-    the server reports there are no files left.
-
-    This class will see heavy activity right after a 2.1->2.2 upgrade since 2.2
-    introduces ReplayGain normalization. A fresh install of Airtime 2.2 will
-    see this class not used at all since a file imported in 2.2 will
-    automatically have its ReplayGain value calculated.
-    """
-
-    @staticmethod
-    def start_reply_gain(apc):
-        me = ReplayGainUpdater(apc)
-        me.daemon = True
-        me.start()
-
-    def __init__(self,apc):
-        Thread.__init__(self)
-        self.api_client = apc
-        self.logger = logging.getLogger()
-
-    def main(self):
-        raw_response = self.api_client.list_all_watched_dirs()
-        if 'dirs' not in raw_response:
-            self.logger.error("Could not get a list of watched directories \
-                               with a dirs attribute. Printing full request:")
-            self.logger.error( raw_response )
-            return
-
-        directories = raw_response['dirs']
-
-        for dir_id, dir_path in directories.iteritems():
-            try:
-                # keep getting few rows at a time for current music_dir (stor
-                # or watched folder).
-                total = 0
-                while True:
-                    # return a list of pairs where the first value is the
-                    # file's database row id and the second value is the
-                    # filepath
-                    files = self.api_client.get_files_without_replay_gain_value(dir_id)
-                    processed_data = []
-                    for f in files:
-                        full_path = os.path.join(dir_path, f['fp'])
-                        processed_data.append((f['id'], replaygain.calculate_replay_gain(full_path)))
-
-                    try:
-                        self.api_client.update_replay_gain_values(processed_data)
-                    except Exception as e: self.unexpected_exception(e)
-
-                    if len(files) == 0: break
-                self.logger.info("Processed: %d songs" % total)
-
-            except Exception, e:
-                self.logger.error(e)
-                self.logger.debug(traceback.format_exc())
-    def run(self):
-        try:
-            while True:
-                self.logger.info("Runnning replaygain updater")
-                self.main()
-                # Sleep for 5 minutes in case new files have been added
-                time.sleep(60 * 5)
-        except Exception, e:
-            self.logger.error('ReplayGainUpdater Exception: %s', traceback.format_exc())
-            self.logger.error(e)
-
-if __name__ == "__main__":
-    rgu = ReplayGainUpdater()
-    rgu.main()
diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 0a3acfabd..2705ef0ee 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -15,7 +15,6 @@ from media.monitor.airtime          import AirtimeNotifier, \
                                            AirtimeMessageReceiver
 from media.monitor.watchersyncer    import WatchSyncer
 from media.monitor.eventdrainer     import EventDrainer
-from media.update.replaygainupdater import ReplayGainUpdater
 from std_err_override               import LogWriter
 
 import media.monitor.pure          as mmp
@@ -95,7 +94,7 @@ def main(global_config, api_client_config, log_config,
     apiclient = apc.AirtimeApiClient.create_right_config(log=log,
             config_path=api_client_config)
 
-    ReplayGainUpdater.start_reply_gain(apiclient)
+    #ReplayGainUpdater.start_reply_gain(apiclient)
 
     manager = Manager()
 
diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py
index 1b51a13f8..ea8950d41 100644
--- a/python_apps/pypo/pypocli.py
+++ b/python_apps/pypo/pypocli.py
@@ -24,6 +24,8 @@ from recorder import Recorder
 from listenerstat import ListenerStat
 from pypomessagehandler import PypoMessageHandler
 
+from media.update.replaygainupdater import ReplayGainUpdater
+
 from configobj import ConfigObj
 
 # custom imports
@@ -174,6 +176,9 @@ if __name__ == '__main__':
         sys.exit()
 
     api_client = api_client.AirtimeApiClient()
+    
+    ReplayGainUpdater.start_reply_gain(api_client)
+
     api_client.register_component("pypo")
 
     pypoFetch_q = Queue()

From 2e59eca1317f97f3f8e65e7e965a2dbf4dd92908 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 14 Nov 2012 14:43:33 -0500
Subject: [PATCH 22/33] moved files to pypo dir

---
 python_apps/pypo/media/__init__.py            |   0
 python_apps/pypo/media/update/__init__.py     |   0
 python_apps/pypo/media/update/replaygain.py   | 152 ++++++++++++++++++
 .../pypo/media/update/replaygainupdater.py    |  82 ++++++++++
 4 files changed, 234 insertions(+)
 create mode 100644 python_apps/pypo/media/__init__.py
 create mode 100644 python_apps/pypo/media/update/__init__.py
 create mode 100644 python_apps/pypo/media/update/replaygain.py
 create mode 100644 python_apps/pypo/media/update/replaygainupdater.py

diff --git a/python_apps/pypo/media/__init__.py b/python_apps/pypo/media/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/python_apps/pypo/media/update/__init__.py b/python_apps/pypo/media/update/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/python_apps/pypo/media/update/replaygain.py b/python_apps/pypo/media/update/replaygain.py
new file mode 100644
index 000000000..5af7cd4a1
--- /dev/null
+++ b/python_apps/pypo/media/update/replaygain.py
@@ -0,0 +1,152 @@
+from subprocess import Popen, PIPE
+import re
+import os
+import sys
+import shutil
+import tempfile
+import logging
+
+
+logger = logging.getLogger()
+
+def get_process_output(command):
+    """
+    Run subprocess and return stdout
+    """
+    logger.debug(command)
+    p = Popen(command, shell=True, stdout=PIPE)
+    return p.communicate()[0].strip()
+
+def run_process(command):
+    """
+    Run subprocess and return "return code"
+    """
+    p = Popen(command, shell=True)
+    return os.waitpid(p.pid, 0)[1]
+
+def get_mime_type(file_path):
+    """
+    Attempts to get the mime type but will return prematurely if the process
+    takes longer than 5 seconds. Note that this function should only be called
+    for files which do not have a mp3/ogg/flac extension.
+    """
+
+    return get_process_output("timeout 5 file -b --mime-type %s" % file_path)
+
+def duplicate_file(file_path):
+    """
+    Makes a duplicate of the file and returns the path of this duplicate file.
+    """
+    fsrc = open(file_path, 'r')
+    fdst = tempfile.NamedTemporaryFile(delete=False)
+
+    logger.info("Copying %s to %s" % (file_path, fdst.name))
+
+    shutil.copyfileobj(fsrc, fdst)
+
+    fsrc.close()
+    fdst.close()
+
+    return fdst.name
+
+def get_file_type(file_path):
+    file_type = None
+    if re.search(r'mp3$', file_path, re.IGNORECASE):
+        file_type = 'mp3'
+    elif re.search(r'og(g|a)$', file_path, re.IGNORECASE):
+        file_type = 'vorbis'
+    elif re.search(r'flac$', file_path, re.IGNORECASE):
+        file_type = 'flac'
+    else:
+        mime_type = get_mime_type(file_path)
+        if 'mpeg' in mime_type:
+            file_type = 'mp3'
+        elif 'ogg' in mime_type:
+            file_type = 'vorbis'
+        elif 'flac' in mime_type:
+            file_type = 'flac'
+
+    return file_type
+
+
+def calculate_replay_gain(file_path):
+    """
+    This function accepts files of type mp3/ogg/flac and returns a calculated
+    ReplayGain value in dB.
+    If the value cannot be calculated for some reason, then we default to 0
+    (Unity Gain).
+
+    http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification
+    """
+
+    try:
+        """
+        Making a duplicate is required because the ReplayGain extraction utilities we use
+        make unwanted modifications to the file.
+        """
+
+        search = None
+        temp_file_path = duplicate_file(file_path)
+
+        file_type = get_file_type(file_path)
+        nice_level = '15'
+
+        if file_type:
+            if file_type == 'mp3':
+                if run_process("which mp3gain > /dev/null") == 0:
+                    command = 'nice -n %s mp3gain -q "%s" 2> /dev/null' \
+                            % (nice_level, temp_file_path)
+                    out = get_process_output(command)
+                    search = re.search(r'Recommended "Track" dB change: (.*)', \
+                                       out)
+                else:
+                    logger.warn("mp3gain not found")
+            elif file_type == 'vorbis':
+                command = "which vorbisgain > /dev/null  && which ogginfo > \
+                        /dev/null"
+                if run_process(command) == 0:
+                    command = 'nice -n %s vorbisgain -q -f "%s" 2>/dev/null \
+                                >/dev/null' % (nice_level,temp_file_path)
+                    run_process(command)
+
+                    out = get_process_output('ogginfo "%s"' % temp_file_path)
+                    search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
+                else:
+                    logger.warn("vorbisgain/ogginfo not found")
+            elif file_type == 'flac':
+                if run_process("which metaflac > /dev/null") == 0:
+
+                    command = 'nice -n %s metaflac --add-replay-gain "%s"' \
+                            % (nice_level, temp_file_path)
+                    run_process(command)
+
+                    command = 'nice -n %s metaflac \
+                            --show-tag=REPLAYGAIN_TRACK_GAIN "%s"' \
+                            % (nice_level, temp_file_path)
+
+                    out = get_process_output(command)
+                    search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
+                else: logger.warn("metaflac not found")
+
+    except Exception, e:
+        logger.error(str(e))
+    finally:
+        #no longer need the temp, file simply remove it.
+        try: os.remove(temp_file_path)
+        except: pass
+
+    replay_gain = 0
+    if search:
+        matches = search.groups()
+        if len(matches) == 1:
+            replay_gain = matches[0]
+        else:
+            logger.warn("Received more than 1 match in: '%s'" % str(matches))
+
+    return replay_gain
+
+
+# Example of running from command line:
+# python replay_gain.py /path/to/filename.mp3
+if __name__ == "__main__":
+    print calculate_replay_gain(sys.argv[1])
diff --git a/python_apps/pypo/media/update/replaygainupdater.py b/python_apps/pypo/media/update/replaygainupdater.py
new file mode 100644
index 000000000..2f52c0a23
--- /dev/null
+++ b/python_apps/pypo/media/update/replaygainupdater.py
@@ -0,0 +1,82 @@
+from threading import Thread
+
+import traceback
+import os
+import time
+import logging
+
+from media.update import replaygain
+
+class ReplayGainUpdater(Thread):
+    """
+    The purpose of the class is to query the server for a list of files which
+    do not have a ReplayGain value calculated. This class will iterate over the
+    list calculate the values, update the server and repeat the process until
+    the server reports there are no files left.
+
+    This class will see heavy activity right after a 2.1->2.2 upgrade since 2.2
+    introduces ReplayGain normalization. A fresh install of Airtime 2.2 will
+    see this class not used at all since a file imported in 2.2 will
+    automatically have its ReplayGain value calculated.
+    """
+
+    @staticmethod
+    def start_reply_gain(apc):
+        me = ReplayGainUpdater(apc)
+        me.daemon = True
+        me.start()
+
+    def __init__(self,apc):
+        Thread.__init__(self)
+        self.api_client = apc
+        self.logger = logging.getLogger()
+
+    def main(self):
+        raw_response = self.api_client.list_all_watched_dirs()
+        if 'dirs' not in raw_response:
+            self.logger.error("Could not get a list of watched directories \
+                               with a dirs attribute. Printing full request:")
+            self.logger.error( raw_response )
+            return
+
+        directories = raw_response['dirs']
+
+        for dir_id, dir_path in directories.iteritems():
+            try:
+                # keep getting few rows at a time for current music_dir (stor
+                # or watched folder).
+                total = 0
+                while True:
+                    # return a list of pairs where the first value is the
+                    # file's database row id and the second value is the
+                    # filepath
+                    files = self.api_client.get_files_without_replay_gain_value(dir_id)
+                    processed_data = []
+                    for f in files:
+                        full_path = os.path.join(dir_path, f['fp'])
+                        processed_data.append((f['id'], replaygain.calculate_replay_gain(full_path)))
+
+                    try:
+                        self.api_client.update_replay_gain_values(processed_data)
+                    except Exception as e: self.unexpected_exception(e)
+
+                    if len(files) == 0: break
+                self.logger.info("Processed: %d songs" % total)
+
+            except Exception, e:
+                self.logger.error(e)
+                self.logger.debug(traceback.format_exc())
+    def run(self):
+        try:
+            while True:
+                self.logger.info("Runnning replaygain updater")
+                self.main()
+                # Sleep for 5 minutes in case new files have been added
+                time.sleep(60 * 5)
+        except Exception, e:
+            self.logger.error('ReplayGainUpdater Exception: %s', traceback.format_exc())
+            self.logger.error(e)
+
+if __name__ == "__main__":
+    rgu = ReplayGainUpdater()
+    rgu.main()

From fcfa5cceeee3c53d7c381f7ae67c71dc56fa4d45 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 14 Nov 2012 14:49:28 -0500
Subject: [PATCH 23/33] removed useless comment

---
 python_apps/media-monitor2/mm2.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 2705ef0ee..bee3d7818 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -94,8 +94,6 @@ def main(global_config, api_client_config, log_config,
     apiclient = apc.AirtimeApiClient.create_right_config(log=log,
             config_path=api_client_config)
 
-    #ReplayGainUpdater.start_reply_gain(apiclient)
-
     manager = Manager()
 
     airtime_receiver = AirtimeMessageReceiver(config,manager)

From dd139e5028c193edd4ff99d1f05ed5dd3e04a432 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 14 Nov 2012 16:50:34 -0500
Subject: [PATCH 24/33] added default value for exception class parameter

---
 python_apps/media-monitor2/media/monitor/exceptions.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python_apps/media-monitor2/media/monitor/exceptions.py b/python_apps/media-monitor2/media/monitor/exceptions.py
index b7b834920..f7d022fb1 100644
--- a/python_apps/media-monitor2/media/monitor/exceptions.py
+++ b/python_apps/media-monitor2/media/monitor/exceptions.py
@@ -23,7 +23,7 @@ class FailedToObtainLocale(Exception):
 
 class CouldNotCreateIndexFile(Exception):
     """exception whenever index file cannot be created"""
-    def __init__(self, path, cause):
+    def __init__(self, path, cause=None):
         self.path = path
         self.cause = cause
     def __str__(self): return "Failed to create touch file '%s'" % self.path

From ce4dbf00280e9ec56ba2cc1f62d0f57014e56c3b Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Wed, 14 Nov 2012 22:06:09 -0500
Subject: [PATCH 25/33] added launcher that should replace most of mm2.py

---
 .../media-monitor2/media/saas/launcher.py     | 75 +++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 python_apps/media-monitor2/media/saas/launcher.py

diff --git a/python_apps/media-monitor2/media/saas/launcher.py b/python_apps/media-monitor2/media/saas/launcher.py
new file mode 100644
index 000000000..fd7bacdc7
--- /dev/null
+++ b/python_apps/media-monitor2/media/saas/launcher.py
@@ -0,0 +1,75 @@
+import os
+
+from media.saas.thread import InstanceThread, user, apc
+from media.monitor.log import Loggable
+from media.monitor.exceptions import CouldNotCreateIndexFile
+from media.monitor.toucher          import ToucherThread
+from media.monitor.airtime          import AirtimeNotifier, \
+                                           AirtimeMessageReceiver
+from media.monitor.watchersyncer    import WatchSyncer
+from media.monitor.eventdrainer     import EventDrainer
+from media.monitor.manager          import Manager
+
+class MM2(InstanceThread, Loggable):
+
+    def index_create(self, index_create_attempt=False):
+        config = user().mm_config
+        if not index_create_attempt:
+            if not os.path.exists(config['index_path']):
+                self.log.info("Attempting to create index file:...")
+                try:
+                    with open(config['index_path'], 'w') as f: f.write(" ")
+                except Exception as e:
+                    self.log.info("Failed to create index file with exception: %s" \
+                             % str(e))
+                else:
+                    self.log.info("Created index file, reloading configuration:")
+                    self.index_create(index_create_attempt=True)
+        else:
+            self.log.info("Already tried to create index. Will not try again ")
+
+        if not os.path.exists(config['index_path']):
+            raise CouldNotCreateIndexFile(config['index_path'])
+
+    def run(self):
+        self.index_create_attempt()
+        manager = Manager()
+        apiclient = apc()
+        config = user().mm_config
+        watch_syncer = WatchSyncer(signal='watch',
+                                   chunking_number=config['chunking_number'],
+                                   timeout=config['request_max_wait'])
+        airtime_receiver = AirtimeMessageReceiver(config,manager)
+        airtime_notifier = AirtimeNotifier(config, airtime_receiver)
+
+        store = apiclient.setup_media_monitor()
+
+        self.log.info(
+                "Initing with the following airtime response:%s" % str(store))
+
+        airtime_receiver.change_storage({ 'directory':store[u'stor'] })
+
+        for watch_dir in store[u'watched_dirs']:
+            if not os.path.exists(watch_dir):
+                # Create the watch_directory here
+                try: os.makedirs(watch_dir)
+                except Exception:
+                    self.log.error("Could not create watch directory: '%s' \
+                            (given from the database)." % watch_dir)
+            if os.path.exists(watch_dir):
+                airtime_receiver.new_watch({ 'directory':watch_dir }, restart=True)
+            else: self.log.info("Failed to add watch on %s" % str(watch_dir))
+
+        ed = EventDrainer(airtime_notifier.connection,
+                interval=float(config['rmq_event_wait']))
+
+        # Launch the toucher that updates the last time when the script was
+        # ran every n seconds.
+        # TODO : verify that this does not interfere with bootstrapping because the
+        # toucher thread might update the last_ran variable too fast
+        tt = ToucherThread(path=config['index_path'],
+                           interval=int(config['touch_interval']))
+
+        apiclient.register_component('media-monitor')
+
+        return manager.loop()

From 7ed1f08e07f882c808cc5964ec4694345233d1ae Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:10:52 -0500
Subject: [PATCH 26/33] Added new launch script for mm.

---
 python_apps/media-monitor2/mm2.py | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index bee3d7818..4f36965a3 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -16,6 +16,8 @@ from media.monitor.airtime          import AirtimeNotifier, \
 from media.monitor.watchersyncer    import WatchSyncer
 from media.monitor.eventdrainer     import EventDrainer
 from std_err_override               import LogWriter
+from media.saas.launcher import MM2
+from media.saas.airtimeinstance import AirtimeInstance
 
 import media.monitor.pure          as mmp
 from api_clients import api_client as apc
@@ -47,8 +49,22 @@ def setup_global(log):
                 Logging exception.")
         log.info(str(e))
 
+def main(global_config, api_client_config, log_config):
+    cfg = {
+        'api_client'    : api_client_config,
+        'media_monitor' : global_config,
+        'logging'       : log_config,
+    }
+    ai = AirtimeInstance('hosted_install', '/', cfg)
+    log = setup_logger( log_config, ai.mm_config['logpath'] )
+    setup_global(log)
+    apc.AirtimeApiClient.create_right_config(log=log,
+            config_path=api_client_config)
+    apc.AirtimeApiClient(api_client_config)
+    mm = MM2(ai)
+    mm.start()
 
-def main(global_config, api_client_config, log_config,
+def main2(global_config, api_client_config, log_config,
         index_create_attempt=False):
     for cfg in [global_config, api_client_config]:
         if not os.path.exists(cfg): raise NoConfigFile(cfg)

From ab35263869cbc9cbc7353b000fa5af25f66bcebd Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:11:18 -0500
Subject: [PATCH 27/33] inject apc properly into AirtimeNotifier

---
 python_apps/media-monitor2/media/monitor/airtime.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/airtime.py b/python_apps/media-monitor2/media/monitor/airtime.py
index b920ecd65..11436009a 100644
--- a/python_apps/media-monitor2/media/monitor/airtime.py
+++ b/python_apps/media-monitor2/media/monitor/airtime.py
@@ -15,7 +15,7 @@ from media.monitor.exceptions import DirectoryIsNotListed
 from media.monitor.bootstrap  import Bootstrapper
 from media.monitor.listeners  import FileMediator
 
-from api_clients import api_client as apc
+from media.saas.thread import apc
 
 class AirtimeNotifier(Loggable):
     """
@@ -98,7 +98,7 @@ class AirtimeMessageReceiver(Loggable):
         if (not directory_id) and (not directory):
             raise ValueError("You must provide either directory_id or \
                     directory")
-        sdb = AirtimeDB(apc.AirtimeApiClient.create_right_config())
+        sdb = AirtimeDB(apc())
         if directory            : directory = os.path.normpath(directory)
         if directory_id == None : directory_id = sdb.to_id(directory)
         if directory    == None : directory = sdb.to_directory(directory_id)

From 15f4212360c4ec9eabe9ae148431b5ac0bd76766 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:11:48 -0500
Subject: [PATCH 28/33] make ManagerTimeout provide the correct AirtimeInstance
 object.

---
 python_apps/media-monitor2/media/monitor/manager.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/manager.py b/python_apps/media-monitor2/media/monitor/manager.py
index e64d1c985..89141157e 100644
--- a/python_apps/media-monitor2/media/monitor/manager.py
+++ b/python_apps/media-monitor2/media/monitor/manager.py
@@ -1,5 +1,4 @@
 import pyinotify
-import threading
 import time
 from pydispatch import dispatcher
 
@@ -9,10 +8,11 @@ from media.monitor.log       import Loggable
 from media.monitor.listeners import StoreWatchListener, OrganizeListener
 from media.monitor.handler   import ProblemFileHandler
 from media.monitor.organizer import Organizer
+from media.saas.thread import InstanceInheritingThread
 import media.monitor.pure as mmp
 
 
-class ManagerTimeout(threading.Thread,Loggable):
+class ManagerTimeout(InstanceInheritingThread,Loggable):
     """ The purpose of this class is to flush the organize directory
     every 3 secnods. This used to be just a work around for cc-4235
     but recently became a permanent solution because it's "cheap" and
@@ -20,7 +20,7 @@ class ManagerTimeout(threading.Thread,Loggable):
     def __init__(self, manager, interval=1.5):
         # TODO : interval should be read from config and passed here instead
         # of just using the hard coded value
-        threading.Thread.__init__(self)
+        super(ManagerTimeout, self).__init__()
         self.manager  = manager
         self.interval = interval
     def run(self):

From fa66f33ffa74c89b3b0a3ca878320d4307a2ec1a Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:12:19 -0500
Subject: [PATCH 29/33] changed RequestSync to use injected apc and injected it
 in ThreadedRequestedSync.

---
 python_apps/media-monitor2/media/monitor/request.py | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/request.py b/python_apps/media-monitor2/media/monitor/request.py
index a49c6bb25..e2c5fb5ef 100644
--- a/python_apps/media-monitor2/media/monitor/request.py
+++ b/python_apps/media-monitor2/media/monitor/request.py
@@ -1,14 +1,12 @@
 # -*- coding: utf-8 -*-
 
-import threading
-
 from media.monitor.exceptions import BadSongFile
 from media.monitor.log        import Loggable
-import api_clients.api_client as ac
+from media.saas.thread import apc, InstanceInheritingThread
 
-class ThreadedRequestSync(threading.Thread, Loggable):
+class ThreadedRequestSync(InstanceInheritingThread, Loggable):
     def __init__(self, rs):
-        threading.Thread.__init__(self)
+        super(ThreadedRequestSync, self).__init__()
         self.rs = rs
         self.daemon = True
         self.start()
@@ -22,7 +20,7 @@ class RequestSync(Loggable):
     for some number of times """
     @classmethod
     def create_with_api_client(cls, watcher, requests):
-        apiclient = ac.AirtimeApiClient.create_right_config()
+        apiclient = apc()
         self = cls(watcher, requests, apiclient)
         return self
 

From 5073ced732bc0d1cfcfd512a7cf4a6d6d8dbec90 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:12:47 -0500
Subject: [PATCH 30/33] Made timeout watcher provide AirtimeInstnace

---
 python_apps/media-monitor2/media/monitor/watchersyncer.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python_apps/media-monitor2/media/monitor/watchersyncer.py b/python_apps/media-monitor2/media/monitor/watchersyncer.py
index d2df9ed3a..a913bb506 100644
--- a/python_apps/media-monitor2/media/monitor/watchersyncer.py
+++ b/python_apps/media-monitor2/media/monitor/watchersyncer.py
@@ -1,5 +1,4 @@
 # -*- coding: utf-8 -*-
-import threading
 import time
 import copy
 
@@ -9,15 +8,16 @@ from media.monitor.exceptions      import BadSongFile
 from media.monitor.eventcontractor import EventContractor
 from media.monitor.events          import EventProxy
 from media.monitor.request         import ThreadedRequestSync, RequestSync
+from media.saas.thread import InstanceInheritingThread
 
-class TimeoutWatcher(threading.Thread,Loggable):
+class TimeoutWatcher(InstanceInheritingThread,Loggable):
     """
     The job of this thread is to keep an eye on WatchSyncer and force a
     request whenever the requests go over time out
     """
     def __init__(self, watcher, timeout=5):
         self.logger.info("Created timeout thread...")
-        threading.Thread.__init__(self)
+        super(TimeoutWatcher, self).__init__()
         self.watcher = watcher
         self.timeout = timeout
 

From 4ac3efe228df28c0442ba9ed2b62e782d1ac1839 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:13:33 -0500
Subject: [PATCH 31/33] renamed typo log to logger.

---
 .../media-monitor2/media/saas/launcher.py      | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/python_apps/media-monitor2/media/saas/launcher.py b/python_apps/media-monitor2/media/saas/launcher.py
index fd7bacdc7..cd35f712e 100644
--- a/python_apps/media-monitor2/media/saas/launcher.py
+++ b/python_apps/media-monitor2/media/saas/launcher.py
@@ -16,23 +16,23 @@ class MM2(InstanceThread, Loggable):
         config = user().mm_config
         if not index_create_attempt:
             if not os.path.exists(config['index_path']):
-                self.log.info("Attempting to create index file:...")
+                self.logger.info("Attempting to create index file:...")
                 try:
                     with open(config['index_path'], 'w') as f: f.write(" ")
                 except Exception as e:
-                    self.log.info("Failed to create index file with exception: %s" \
+                    self.logger.info("Failed to create index file with exception: %s" \
                              % str(e))
                 else:
-                    self.log.info("Created index file, reloading configuration:")
+                    self.logger.info("Created index file, reloading configuration:")
                     self.index_create(index_create_attempt=True)
         else:
-            self.log.info("Already tried to create index. Will not try again ")
+            self.logger.info("Already tried to create index. Will not try again ")
 
         if not os.path.exists(config['index_path']):
             raise CouldNotCreateIndexFile(config['index_path'])
 
     def run(self):
-        self.index_create_attempt()
+        self.index_create()
         manager = Manager()
         apiclient = apc()
         config = user().mm_config
@@ -44,7 +44,7 @@ class MM2(InstanceThread, Loggable):
 
         store = apiclient.setup_media_monitor()
 
-        self.log.info(
+        self.logger.info(
                 "Initing with the following airtime response:%s" % str(store))
 
         airtime_receiver.change_storage({ 'directory':store[u'stor'] })
@@ -54,11 +54,11 @@ class MM2(InstanceThread, Loggable):
                 # Create the watch_directory here
                 try: os.makedirs(watch_dir)
                 except Exception:
-                    self.log.error("Could not create watch directory: '%s' \
+                    self.logger.error("Could not create watch directory: '%s' \
                             (given from the database)." % watch_dir)
             if os.path.exists(watch_dir):
                 airtime_receiver.new_watch({ 'directory':watch_dir }, restart=True)
-            else: self.log.info("Failed to add watch on %s" % str(watch_dir))
+            else: self.logger.info("Failed to add watch on %s" % str(watch_dir))
 
         ed = EventDrainer(airtime_notifier.connection,
                 interval=float(config['rmq_event_wait']))
@@ -72,4 +72,4 @@ class MM2(InstanceThread, Loggable):
 
         apiclient.register_component('media-monitor')
 
-        return manager.loop()
+        manager.loop()

From 8a7535774c508a4877c3e787b6eba1ae2185692d Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:13:52 -0500
Subject: [PATCH 32/33] fixed import typo

---
 python_apps/media-monitor2/media/saas/airtimeinstance.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python_apps/media-monitor2/media/saas/airtimeinstance.py b/python_apps/media-monitor2/media/saas/airtimeinstance.py
index 293fc84c2..2cb2176c5 100644
--- a/python_apps/media-monitor2/media/saas/airtimeinstance.py
+++ b/python_apps/media-monitor2/media/saas/airtimeinstance.py
@@ -3,7 +3,7 @@ import os
 from media.monitor.exceptions import NoConfigFile
 from media.monitor.pure import LazyProperty
 from media.monitor.config import MMConfig
-from api_clients import AirtimeApiClient
+from api_clients.api_client import AirtimeApiClient
 
 class AirtimeInstance(object):
     """ AirtimeInstance is a class that abstracts away every airtime

From 0b0e96f49c719fba879d33b7241e9ab939b21e34 Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <rudi.grinberg@gmail.com>
Date: Thu, 15 Nov 2012 12:46:57 -0500
Subject: [PATCH 33/33] removed old runner code

---
 python_apps/media-monitor2/mm2.py | 82 -------------------------------
 1 file changed, 82 deletions(-)

diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 4f36965a3..53e5f3c17 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -64,88 +64,6 @@ def main(global_config, api_client_config, log_config):
     mm = MM2(ai)
     mm.start()
 
-def main2(global_config, api_client_config, log_config,
-        index_create_attempt=False):
-    for cfg in [global_config, api_client_config]:
-        if not os.path.exists(cfg): raise NoConfigFile(cfg)
-    # MMConfig is a proxy around ConfigObj instances. it does not allow
-    # itself users of MMConfig instances to modify any config options
-    # directly through the dictionary. Users of this object must use the
-    # correct methods designated for modification
-    try: config = MMConfig(global_config)
-    except NoConfigFile as e:
-        print("Cannot run mediamonitor2 without configuration file.")
-        print("Current config path: '%s'" % global_config)
-        sys.exit(1)
-    except Exception as e:
-        print("Unknown error reading configuration file: '%s'" % global_config)
-        print(str(e))
-    
-    log = setup_logger( log_config, config['logpath'] )
-
-    if not index_create_attempt:
-        if not os.path.exists(config['index_path']):
-            log.info("Attempting to create index file:...")
-            try:
-                with open(config['index_path'], 'w') as f: f.write(" ")
-            except Exception as e:
-                log.info("Failed to create index file with exception: %s" \
-                         % str(e))
-            else:
-                log.info("Created index file, reloading configuration:")
-                main( global_config,  api_client_config, log_config,
-                        index_create_attempt=True )
-    else:
-        log.info("Already tried to create index. Will not try again ")
-
-    if not os.path.exists(config['index_path']):
-        log.info("Index file does not exist. Terminating")
-
-    setup_global(log)
-
-    watch_syncer = WatchSyncer(signal='watch',
-                               chunking_number=config['chunking_number'],
-                               timeout=config['request_max_wait'])
-
-    apiclient = apc.AirtimeApiClient.create_right_config(log=log,
-            config_path=api_client_config)
-
-    manager = Manager()
-
-    airtime_receiver = AirtimeMessageReceiver(config,manager)
-    airtime_notifier = AirtimeNotifier(config, airtime_receiver)
-
-    store = apiclient.setup_media_monitor()
-
-    log.info("Initing with the following airtime response:%s" % str(store))
-
-    airtime_receiver.change_storage({ 'directory':store[u'stor'] })
-
-    for watch_dir in store[u'watched_dirs']:
-        if not os.path.exists(watch_dir):
-            # Create the watch_directory here
-            try: os.makedirs(watch_dir)
-            except Exception as e:
-                log.error("Could not create watch directory: '%s' \
-                        (given from the database)." % watch_dir)
-        if os.path.exists(watch_dir):
-            airtime_receiver.new_watch({ 'directory':watch_dir }, restart=True)
-        else: log.info("Failed to add watch on %s" % str(watch_dir))
-
-    ed = EventDrainer(airtime_notifier.connection,
-            interval=float(config['rmq_event_wait']))
-
-    # Launch the toucher that updates the last time when the script was
-    # ran every n seconds.
-    # TODO : verify that this does not interfere with bootstrapping because the
-    # toucher thread might update the last_ran variable too fast
-    tt = ToucherThread(path=config['index_path'],
-                       interval=int(config['touch_interval']))
-
-    apiclient.register_component('media-monitor')
-
-    return manager.loop()
-
 __doc__ = """
 Usage:
     mm2.py --config=<path> --apiclient=<path> --log=<path>