codemonkey

jtvStreamsUpdater.py

Nov 4th, 2011
207
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.12 KB | None | 0 0
  1. # ================================
  2. # Ensure env variables are correct
  3. # ================================
  4.  
  5. from os.path import abspath, dirname, join
  6. from os import environ
  7. from sys import path
  8.  
  9. # Get absolute paths from relative paths
  10. honsappPath = abspath(dirname(__file__))
  11. honstreamsPath = abspath(join(honsappPath, '../'))
  12.  
  13. # Make sure they are in sys.path
  14. if honsappPath not in path:
  15.     path.append(honsappPath)
  16. if honstreamsPath not in path:
  17.     path.append(honstreamsPath)
  18.  
  19. # Make sure DJANGO_SETTINGS_MODULE is set
  20. if 'DJANGO_SETTINGS_MODULE' not in environ.keys():
  21.     environ['DJANGO_SETTINGS_MODULE'] = 'settings'
  22.  
  23. # =======
  24. # Imports
  25. # =======
  26.  
  27. from jtvSettings import *
  28. from honsapp.models import Stream
  29. import sys
  30. import time
  31. import math
  32. import datetime
  33. import json
  34. import logging
  35. import socket
  36.  
  37. # =======
  38. # Globals
  39. # =======
  40.  
  41. DEBUG = False
  42.  
  43. # Interval length is 5 minutes in seconds
  44. INTERVAL_LENGTH = 60 * 5
  45.  
  46. # Update return codes
  47. UPDATED = 1
  48. SKIPPED = 2
  49. UPDATED_TO_ONLINE = 3
  50. UPDATED_TO_OFFLINE = 4
  51. NOTHING_TO_DO = 5
  52.  
  53. # =============
  54. # Configuration
  55. # =============
  56.  
  57. # Set up logging
  58. logging.basicConfig(filename=join(honsappPath, 'jtvStreamsUpdater.log'),
  59.                     level=logging.ERROR,
  60.                     format='%(asctime)s:%(levelname)s: %(message)s')
  61.  
  62. # ====================
  63. # Function definitions
  64. # ====================
  65.  
  66. def getJtvClient():
  67.     "Fetch an authenticated jtv client"
  68.    
  69.     from jtvClient import JtvClient
  70.     from jtvSettings import CONSUMER_KEY, CONSUMER_SECRET
  71.    
  72.     return JtvClient(CONSUMER_KEY, CONSUMER_SECRET)
  73.  
  74. def updateStreamStatus(stream):
  75.    
  76.     returncode = SKIPPED
  77.    
  78.     # Create a jtv client
  79.     client = getJtvClient()
  80.    
  81.     # Poll the jtv API
  82.     response = None
  83.     attempts = 0
  84.    
  85.     while response == None:
  86.         try:
  87.             # Try to fetch status
  88.             response = client.get('/stream/list.json?channel=%s' % stream.name)
  89.         except socket.timeout:
  90.             # Debug message
  91.             if DEBUG:
  92.                 print 'Failed fetching status for %s. Retrying...' \
  93.                        % stream.name
  94.            
  95.             if attempts < 3:
  96.                 # Try 3 times
  97.                 attempts += 1
  98.                 continue
  99.             else:
  100.                 # Then give up
  101.                 error = 'Failed fetching status %d times in a row. Exiting'
  102.                 print error
  103.                 logging.critical(error)
  104.                 exit()
  105.    
  106.     result = json.loads(response.read())
  107.    
  108.     # Check for errors
  109.     if 'error' in result:
  110.         print result['error']
  111.         logging.error(result['error'])
  112.         exit()
  113.    
  114.     if len(result) > 0:
  115.         # The stream is live
  116.         if stream.online != True:
  117.             stream.online = True
  118.             returncode = UPDATED_TO_ONLINE
  119.     else:
  120.         # The stream is not live
  121.         if stream.online != False:
  122.             stream.online = False
  123.             returncode = UPDATED_TO_OFFLINE
  124.    
  125.     # Set the stream to updated now
  126.     stream.statusUpdated = datetime.datetime.now()
  127.    
  128.     # Save the stream
  129.     stream.save()
  130.    
  131.     return returncode
  132.  
  133. def updateStreamInfo(stream):
  134.    
  135.     # Import StreamInfo
  136.     from honsapp.models import StreamInfo
  137.    
  138.     returncode = SKIPPED
  139.    
  140.     # Get a jtv client
  141.     client = getJtvClient()
  142.    
  143.     # Poll jtv API for channel info
  144.     response = client.get('/channel/show/%s.json' % stream.name)
  145.  
  146.     result = json.loads(response.read())
  147.    
  148.     # Check for errors
  149.     if 'error' in result:
  150.         print result['error']
  151.         logging.error(result['error'])
  152.         exit()
  153.    
  154.     # Create StreamInfo object
  155.     sinfo = StreamInfo()
  156.     sinfo.title = result['title']
  157.     sinfo.description = result['description']
  158.     sinfo.about = result['about']
  159.    
  160.     sinfo.stream_url = result['channel_url']
  161.    
  162.     sinfo.backgroundImage = result['channel_background_image_url']
  163.     sinfo.headerImage = result['channel_header_image_url']
  164.     sinfo.mediumChannelImage = result['image_url_medium']
  165.     sinfo.largeChannelImage = result['image_url_large']
  166.    
  167.     sinfo.mediumScreenCap = result['screen_cap_url_medium']
  168.     sinfo.largeScreenCap = result['screen_cap_url_large']
  169.    
  170.     sinfo.backgroundColor = result['channel_background_color']
  171.     sinfo.columnColor = result['channel_column_color']
  172.     sinfo.textColor = result['channel_text_color']
  173.     sinfo.linkColor = result['channel_link_color']
  174.    
  175.     # Compare the new stream info to the old
  176.     if stream.info == sinfo:
  177.         # If identical, set returncode to skipped
  178.         returncode = SKIPPED
  179.     else:
  180.         # Save the new steam info
  181.         sinfo.save()
  182.        
  183.         # If there's an old one, delete it
  184.         if stream.info != None:
  185.             stream.info.delete()
  186.        
  187.         # Link the new stream info to the stream
  188.         stream.info = sinfo
  189.        
  190.         # Set the returncode to updated
  191.         returncode = UPDATED
  192.    
  193.     # Mark stream as updated
  194.     stream.infoUpdated = datetime.datetime.now()
  195.     stream.save()
  196.    
  197.     return returncode
  198.  
  199. def mainUpdateLoop():
  200.     """
  201.    Updates the status of all streams to either online or offline, as well as
  202.    updating the stream info, respecting the global restrictions.
  203.    """
  204.    
  205.     def streamText(stream):
  206.         """
  207.        Returns a string representation of the stream displaying last updated
  208.        information
  209.        """
  210.         # return '<Stream name="%s", statusUpdated="%s", infoUpdated="%s">' \
  211.         #             % (stream.name, stream.statusUpdated, stream.infoUpdated)
  212.        
  213.         return '<Stream name="%s">' % stream.name
  214.    
  215.     # Run for ever!
  216.     while True:
  217.         # Start time
  218.         intervalStart = time.time()
  219.        
  220.         # The total amount of ticks this interval
  221.         totalTicks = MAINLOOP_STATUSUPDATES + MAINLOOP_INFOUPDATES
  222.        
  223.         # Get all jtv streams sorted by last updated status
  224.         statusStreams = Stream.objects.filter(network__name='justin.tv')\
  225.                                       .order_by('statusUpdated', 'id')
  226.                                      
  227.         # Get all jtv streams sorted by last updated info
  228.         infoStreams = Stream.objects.filter(network__name='justin.tv')\
  229.                                     .order_by('infoUpdated', 'id')
  230.        
  231.         # Update the status of the streams with the least recently updated
  232.         # status
  233.         for i, stream in enumerate(statusStreams):
  234.            
  235.             # Remember the limits
  236.             if i > (MAINLOOP_STATUSUPDATES - 1):
  237.                 break
  238.            
  239.             print 'Updating status on %s...' % streamText(stream),
  240.            
  241.             # Update stream here
  242.             result = updateStreamStatus(stream)
  243.            
  244.             # Print out what happened
  245.             if result == SKIPPED:
  246.                 print 'Unchanged'
  247.             elif result == UPDATED_TO_ONLINE:
  248.                 print 'Updated to online'
  249.             elif result == UPDATED_TO_OFFLINE:
  250.                 print 'Updated to offline'
  251.        
  252.         # Update the info of the streams with the least recently updated info
  253.         for i, stream in enumerate(infoStreams):
  254.            
  255.             # Remember the limits
  256.             if i > (MAINLOOP_INFOUPDATES - 1):
  257.                 break
  258.            
  259.             print 'Updating info on %s...' % streamText(stream),
  260.            
  261.             # Update stream here
  262.             result = updateStreamInfo(stream)
  263.            
  264.             # Print out what happened
  265.             if result == SKIPPED:
  266.                 print 'Unchanged'
  267.             else:
  268.                 print 'Saved new info'
  269.        
  270.         # Wait 5 minutes
  271.         while True:
  272.             timeWaited = math.ceil(time.time() - intervalStart)
  273.            
  274.             # If we've waited over 5 minutes
  275.             # if timeWaited > 10:
  276.             if timeWaited > INTERVAL_LENGTH:
  277.                 break
  278.            
  279.             if DEBUG:
  280.                 # Print a message every 5 seconds
  281.                 if timeWaited % 30 == 0:
  282.                     print '### %d seconds to next interval...' \
  283.                           % (INTERVAL_LENGTH - timeWaited)
  284.  
  285.             time.sleep(1)
  286.  
  287. def fetchNewInfo():
  288.     # Get all streams that has no info
  289.     streams = Stream.objects.filter(info__isnull=True)
  290.    
  291.     if(len(streams) < 1):
  292.         # If there are no streams to update
  293.         return NOTHING_TO_DO
  294.    
  295.     for i, stream in enumerate(streams):
  296.         print 'Updating info on %s...' % stream,
  297.        
  298.         # Update the info of this stream
  299.         result = updateStreamInfo(stream)
  300.        
  301.         # Output result
  302.         if result == SKIPPED:
  303.             print 'Unchanged'
  304.         else:
  305.             print 'Saved new information'
  306.        
  307.         # Respect global limits
  308.         if (i + 1) >= MAX_REQUESTS_PER_5MIN:
  309.             # Pause every time the max request limit is met
  310.             if i % MAX_REQUESTS_PER_5MIN == 0:
  311.                 print 'Limit met! Sleeping for 5 minutes before continuing...'
  312.                
  313.                 # Count down
  314.                 for i in xrange(INTERVAL_LENGTH):
  315.                     sys.stdout.write('\r' + ' ' * 80)
  316.                     sys.stdout.write('\r' + 'Countdown: %d' % (INTERVAL_LENGTH - i))
  317.                     sys.stdout.flush()
  318.                     time.sleep(1)
  319.  
  320. # ===============
  321. # Run this script
  322. # ===============
  323. if(__name__ == '__main__'):
  324.     # Dependencies
  325.     import argparse
  326.     import textwrap
  327.    
  328.     # Some constants
  329.     commandchoices = ['scrapeloop', 'newinfo']
  330.     commandhelp = '''This module handles refreshing Justin TV streams
  331.  
  332. Commands:
  333.  scrapeloop    Starts a loop that updates all the streams statuses, respecting
  334.                the global limits. Also updated stream information, but at a
  335.                much slower rate
  336.          
  337.  newinfo       Fetches stream information for streams that still has no
  338.                information row'''
  339.    
  340.     # Create a parser
  341.     parser = argparse.ArgumentParser(description=commandhelp,
  342.                 formatter_class=argparse.RawDescriptionHelpFormatter)
  343.    
  344.     # Take exactly one argument out of the command choices
  345.     parser.add_argument('command', metavar='command', type=str,
  346.                         choices=commandchoices,
  347.                         help='The command to give the script')
  348.    
  349.     # Flag for debug
  350.     parser.add_argument('-d', '--debug', action='store_true',
  351.                         help='Print debug messages')
  352.    
  353.     # Get arguments
  354.     args = parser.parse_args()
  355.     command = args.command
  356.    
  357.     # Debug?
  358.     if args.debug:
  359.         DEBUG = True
  360.    
  361.     # Start the if loop
  362.     if(command == 'scrapeloop'):
  363.         mainUpdateLoop()
  364.        
  365.     elif(command == 'newinfo'):
  366.         print 'Now making sure all jtv streams has info'
  367.        
  368.         # Fetch new info
  369.         result = fetchNewInfo()
  370.        
  371.         # Print results
  372.         if result == NOTHING_TO_DO:
  373.             print 'All streams already has info'
  374.         else:
  375.             print 'All streams now has info'
  376.  
  377.  
Add Comment
Please, Sign In to add comment