Commits

infrared  committed 251b783

Cheesecake daemon now can get list of packages via command line.

  • Participants
  • Parent commits 8d4bc65

Comments (0)

Files changed (6)

File cheesecake_daemon.py

 from config import TIMESTAMP_FILE
 from config import TIME_CORRECTION
 
+from commands_stream import CommandsStream
 from pypi import get_releases_list, post_results
 from store import Store
 from store import CheesecakeRun
 def main():
     daemonize()
 
+    # Create a command stream for communication with other processes.
+    commands = CommandsStream()
+
     # Upon SIGINT daemon should gracefully terminate.
     signal_set()
 
     store = Store(directory=LOG_DIRECTORY, timestamp_file=TIMESTAMP_FILE)
 
+    # Handy helper.
+    def log_new_list_info(message, list):
+        store.log(message % (len(list), ', '.join([('%s-%s' % tuple(x)) for x in list])))
+
     store.log("Cheesecake service started... How about a little red Leicester?")
 
     while not interrupt_signal_received:
         # Allow user to interrupt this one.
         signal_unset()
         try:
-            # Don't contact PyPI to often or RJ will get mad at us.
-            if store.timestamp + 60 > int(time.time()):
-                time.sleep(60)
+            to_score = []
+            while not to_score:
+                # Don't contact PyPI to often or RJ will get mad at us.
+                if store.timestamp + 60 > int(time.time()):
+                    time.sleep(60)
 
-            # Add some seconds to overcome time differences between our host
-            # and PyPI.
-            to_score = get_releases_list(store.timestamp + TIME_CORRECTION)
-            store.log("Got list of %d new packages from PyPI: %s." % \
-                      (len(to_score),
-                       ', '.join([('%s-%s' % tuple(x)) for x in to_score])))
+                # Add some seconds to overcome time differences between our host
+                # and PyPI.
+                to_score_from_pypi = get_releases_list(store.timestamp + TIME_CORRECTION)
+                if to_score_from_pypi:
+                    log_new_list_info("Got list of %d new packages from PyPI: %s.", to_score_from_pypi)
+
+                to_score_from_stream = commands.get_releases_list()
+                if to_score_from_stream:
+                    log_new_list_info("Got list of %d new packages from command stream: %s.", to_score_from_stream)
+
+                to_score = to_score_from_pypi + to_score_from_stream
         except Exception, e:
             if str(e):
                 store.log("Interrupted by exception: %s. Terminating..." % str(e))
         store.update_timestamp()
 
     store.close()
+    commands.close()
 
 
 if __name__ == '__main__':

File commands_stream.py

+import os
+import socket
+
+from config import COMMUNICATION_SOCKET_PATH
+
+class CommandsStream(object):
+    def __init__(self):
+        if os.path.exists(COMMUNICATION_SOCKET_PATH):
+            os.remove(COMMUNICATION_SOCKET_PATH)
+
+        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        self.sock.bind(COMMUNICATION_SOCKET_PATH)
+
+        # Make socket non-blocking.
+        self.sock.setblocking(False)
+
+    def get_releases_list(self):
+        """Return list of releases send over a socket, each release
+        in a form of a tuple (package, version).
+        """
+        try:
+            data = self.sock.recv(1024)
+            return map(lambda line: tuple(line.split('==')),
+                       data.splitlines())
+        except socket.error:
+            return []
+
+    def close(self):
+        self.sock.close()
+        os.remove(COMMUNICATION_SOCKET_PATH)
+
+
+def send_releases_list(releases):
+    """Send releases list to a socket.
+
+    Each release will be send in separate line, in format of
+    "package==version".
+    """
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+    sock.connect(COMMUNICATION_SOCKET_PATH)
+    sock.send("\n".join(releases))
+    sock.close()
 # Path of a directory where logs are stored.
 LOG_DIRECTORY = '/tmp/cheesecake_logs/'
 
-# Path of a file which last access time will store last
-#   PyPI connection time.
+# Path of a file which last access time will correspond
+#   to last PyPI connection time.
 TIMESTAMP_FILE ='/tmp/cheesecake_service_timestamp'
 
+# Path to a socket which will be used to pass commands
+#   to the daemon.
+COMMUNICATION_SOCKET_PATH = '/tmp/cheesecake_socket'
+
 # Maximum number of times we will try to score a single
 #   package before giving up.
 MAX_NUMBER_OF_FAILURES = 3

File contrib/clean_database.py

+#!/usr/bin/env python
+
 import sys
 sys.path.insert(0, '../')
 

File contrib/score_releases.py

+#!/usr/bin/env python
+
+import socket
+import sys
+sys.path.insert(0, '../')
+
+from commands_stream import send_releases_list
+
+
+def check_list_of_releases(args):
+    """Make sure list of releases has proper format.
+
+    Raises exception upon an error, returns None if args are OK.
+    """
+    for arg in args:
+        if '==' not in arg:
+            print "Please specify a version after a package name (%s)." % arg
+            sys.exit(1)
+
+if __name__ == '__main__':
+    if len(sys.argv) < 2:
+        print "usage: score_releases.py package==version package==version..."
+        print
+        print "For example: score_releases.py Cheesecake==0.6 twill==0.8.5"
+        sys.exit(1)
+
+
+    releases = sys.argv[1:]
+    check_list_of_releases(releases)
+    try:
+        send_releases_list(releases)
+        print "Success."
+    except socket.error:
+        print "Cheesecake daemon is not running, failed to send a command."
+        sys.exit(1)
 
 def get_releases_list(timestamp):
     """Get list of updated releases since given `timestamp`.
-
-    This function may wait a long time, but it ensures that non-empty
-    list will be returned.
     """
-    ret = None
-
-    while True:
-        try:
-            server = xmlrpclib.Server(PYPI_ADDRESS)
-            ret = server.updated_releases(timestamp)
-            if ret:
-                break
-        except (xmlrpclib.ProtocolError, socket.error), e:
-            pass
-        # Wait one minute and try again.
-        time.sleep(60)
-
-    return ret
+    try:
+        server = xmlrpclib.Server(PYPI_ADDRESS)
+        return server.updated_releases(timestamp)
+    except (xmlrpclib.ProtocolError, socket.error), e:
+        return []
 
 def post_results(name, version, score_data):
     """Post score of a release to PyPI.