diff --git a/client/osx-ui/DJRandomUploader/Preferences.h b/client/osx-ui/DJRandomUploader/Preferences.h new file mode 100644 index 0000000000000000000000000000000000000000..0afa5866ea96aa86279b3142dbc9d25e47815086 --- /dev/null +++ b/client/osx-ui/DJRandomUploader/Preferences.h @@ -0,0 +1,17 @@ +// +// Preferences.h +// DJRandomUploader +// +// Created by Alessandro Preite Martinez on 01/11/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + + +@interface Preferences : NSWindowController { +@private + +} + +@end diff --git a/client/osx-ui/DJRandomUploader/Preferences.m b/client/osx-ui/DJRandomUploader/Preferences.m new file mode 100644 index 0000000000000000000000000000000000000000..81632bf9b52b658991097e90adac55d4531cf32e --- /dev/null +++ b/client/osx-ui/DJRandomUploader/Preferences.m @@ -0,0 +1,36 @@ +// +// Preferences.m +// DJRandomUploader +// +// Created by Alessandro Preite Martinez on 01/11/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "Preferences.h" + + +@implementation Preferences + +- (id)initWithWindow:(NSWindow *)window +{ + self = [super initWithWindow:window]; + if (self) { + // Initialization code here. + } + + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. +} + +@end diff --git a/client/osx-ui/DJRandomUploader/Preferences.xib b/client/osx-ui/DJRandomUploader/Preferences.xib new file mode 100644 index 0000000000000000000000000000000000000000..7f1b38acb1e5b296ed183682d67956771dbfbb34 --- /dev/null +++ b/client/osx-ui/DJRandomUploader/Preferences.xib @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.050000190734863"> + <data> + <int key="IBDocument.SystemTarget">1060</int> + <string key="IBDocument.SystemVersion">10A216</string> + <string key="IBDocument.InterfaceBuilderVersion">708</string> + <string key="IBDocument.AppKitVersion">994.4</string> + <string key="IBDocument.HIToolboxVersion">404.00</string> + <object class="NSMutableDictionary" key="IBDocument.PluginVersions"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="NS.object.0">708</string> + </object> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1001"> + <string key="NSClassName">NSObject</string> + </object> + <object class="NSCustomObject" id="1003"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1004"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSWindowTemplate" id="1005"> + <int key="NSWindowStyleMask">15</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{196, 240}, {480, 270}}</string> + <int key="NSWTFlags">544735232</int> + <string key="NSWindowTitle">Window</string> + <string key="NSWindowClass">NSWindow</string> + <nil key="NSViewClass"/> + <string key="NSWindowContentMaxSize">{3.40282e+38, 3.40282e+38}</string> + <object class="NSView" key="NSWindowView" id="1006"> + <nil key="NSNextResponder"/> + <int key="NSvFlags">256</int> + <string key="NSFrameSize">{480, 270}</string> + </object> + <string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string> + <string key="NSMaxSize">{3.40282e+38, 3.40282e+38}</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1002"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1000"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1001"/> + <reference key="parent" ref="1002"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1003"/> + <reference key="parent" ref="1002"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1004"/> + <reference key="parent" ref="1002"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">1</int> + <reference key="object" ref="1005"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1006"/> + </object> + <reference key="parent" ref="1002"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">2</int> + <reference key="object" ref="1006"/> + <reference key="parent" ref="1005"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>1.IBPluginDependency</string> + <string>1.IBWindowTemplateEditedContentRect</string> + <string>1.NSWindowTemplate.visibleAtLaunch</string> + <string>1.WindowOrigin</string> + <string>1.editorWindowContentRectSynchronizationRect</string> + <string>2.IBPluginDependency</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{357, 418}, {480, 270}}</string> + <integer value="1"/> + <string>{196, 240}</string> + <string>{{357, 418}, {480, 270}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">2</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"/> + <int key="IBDocument.localizationMode">0</int> + <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> + <nil key="IBDocument.LastKnownRelativeProjectPath"/> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> diff --git a/server/djrandom/fingerprint/fingerprint.py b/server/djrandom/fingerprint/fingerprint.py index c1fd67c859edb892bf11addfde1ef5828a57474b..a332536a47e17a854eb6ed4c4387b22d384df2f2 100644 --- a/server/djrandom/fingerprint/fingerprint.py +++ b/server/djrandom/fingerprint/fingerprint.py @@ -8,16 +8,19 @@ from djrandom import daemonize from djrandom import utils from djrandom.model.mp3 import MP3 from djrandom.database import Session, init_db +from djrandom.model import processor log = logging.getLogger(__name__) -class Fingerprinter(object): +class Fingerprinter(processor.Processor): def __init__(self, codegen_path): + processor.Processor.__init__(self) self.codegen_path = codegen_path def process(self, mp3): + log.info('fingerprinting %s' % mp3.sha1) pipe = subprocess.Popen( [self.codegen_path, mp3.path, '10', '30'], close_fds=False, @@ -28,29 +31,14 @@ class Fingerprinter(object): # (Ugly Hack!) mp3.set_fingerprint(fp_json[2:-2]) - def compute_fingerprints(self, run_once): - """Compute fingerprints of new files.""" - while True: - mp3 = MP3.get_with_no_fingerprint().limit(1).first() - if not mp3: - if run_once: - break - Session.remove() - time.sleep(300) - continue - log.info('fingerprinting %s' % mp3.sha1) - try: - self.process(mp3) - except Exception, e: - log.error(traceback.format_exc()) - Session.add(mp3) - Session.commit() + def query(self): + return MP3.get_with_no_fingerprint() def run_fingerprinter(db_url, codegen_path, run_once): init_db(db_url) scanner = Fingerprinter(codegen_path) - scanner.compute_fingerprints(run_once) + scanner.loop(run_once=run_once) def main(): diff --git a/server/djrandom/metadata_fixer/metadata_fixer.py b/server/djrandom/metadata_fixer/metadata_fixer.py index a0ffcd378256fbfd930516d75c78d3177508505c..bae0d0f930cd25937ec3278c79c2c3245663e69c 100644 --- a/server/djrandom/metadata_fixer/metadata_fixer.py +++ b/server/djrandom/metadata_fixer/metadata_fixer.py @@ -11,6 +11,7 @@ from djrandom import utils from djrandom.model.mp3 import MP3 from djrandom.database import Session, init_db, indexer from djrandom.scanner import metadata +from djrandom.model import processor log = logging.getLogger(__name__) @@ -19,12 +20,15 @@ class NoMetadataError(Exception): pass -class MetadataFixer(object): +class MetadataFixer(processor.Processor): ECHONEST_API_URL = 'http://developer.echonest.com/api/v4/song/identify' - def __init__(self, echonest_api_key): + def __init__(self, echonest_api_key, partial, dry_run): + processor.Processor.__init__(self, dry_run) self.api_key = echonest_api_key + self.partial = partial + self.n_bad = self.n_ok = self.n_err = 0 def identify_song(self, mp3): json_fp = mp3.get_fingerprint() @@ -56,16 +60,9 @@ class MetadataFixer(object): log.debug('retrying...') - def process(self, mp3): - info = self.identify_song(mp3) - mp3.title = metadata.normalize_string(info['title']) - mp3.artist = metadata.normalize_string(info['artist_name']) - - def scan(self, dry_run, partial): - """Scan the database for new files.""" - n_bad = n_ok = n_err = 0 - if partial: - entries = MP3.query.filter( + def query(self): + if self.partial: + return MP3.query.filter( (MP3.state == MP3.READY) & (MP3.has_fingerprint == True) & ((MP3.artist == None) @@ -73,44 +70,42 @@ class MetadataFixer(object): | (MP3.title == '') | (MP3.title == None))) else: - entries = MP3.get_with_bad_metadata() - for mp3 in entries: - n_bad += 1 - log.info('searching metadata for %s' % mp3.sha1) - try: - self.process(mp3) - mp3.state = MP3.READY - log.info('found: %s / %s' % (mp3.artist, mp3.title)) - n_ok += 1 - except NoMetadataError: - mp3.state = MP3.ERROR - n_err += 1 - except Exception, e: - log.error(traceback.format_exc()) - n_err += 1 - mp3.state = MP3.ERROR - indexer.add_mp3(mp3) - Session.add(mp3) - if not dry_run: - Session.commit() - indexer.commit() - log.debug('total: %d songs, found: %d' % (n_bad, n_ok)) - - def run(self, run_once, dry_run, partial): - while True: - self.scan(dry_run, partial) - if run_once: - break - Session.remove() - time.sleep(600) + return MP3.get_with_bad_metadata() + + def _process(self, mp3): + log.info('searching metadata for %s' % mp3.sha1) + info = self.identify_song(mp3) + mp3.title = metadata.normalize_string(info['title']) + mp3.artist = metadata.normalize_string(info['artist_name']) + + def process(self, mp3): + self.n_bad += 1 + try: + self._process(mp3) + mp3.state = MP3.READY + log.info('found: %s / %s' % (mp3.artist, mp3.title)) + self.n_ok += 1 + except NoMetadataError: + mp3.state = MP3.ERROR + self.n_err += 1 + except Exception, e: + log.error(traceback.format_exc()) + self.n_err += 1 + mp3.state = MP3.ERROR + indexer.add_mp3(mp3) + + def commit(self): + log.debug('%d songs, found: %d (%d errs)' % ( + self.n_bad, self.n_ok, self.n_err)) + indexer.commit() def run_fixer(solr_url, echonest_api_key, db_url, dry_run, partial, run_once): socket.setdefaulttimeout(300) init_db(db_url, solr_url) - fixer = MetadataFixer(echonest_api_key) - fixer.run(run_once, dry_run, partial) + fixer = MetadataFixer(echonest_api_key, partial, dry_run) + fixer.loop(run_once=run_once) def main(): diff --git a/server/djrandom/model/processor.py b/server/djrandom/model/processor.py new file mode 100644 index 0000000000000000000000000000000000000000..8b541ae1e2a94ca282665baef16e8b9b91fc5ca5 --- /dev/null +++ b/server/djrandom/model/processor.py @@ -0,0 +1,60 @@ +import logging +import traceback +from djrandom.database import Session + +log = logging.getLogger(__name__) + + +class Processor(object): + """Base class to perform operations on database objects. + + Used as the basis of most cron-based jobs, it provides enough + abstraction to support a conversion to an event-based model if + necessary. + + Subclasses should define at least the 'query' and 'process' + methods. + """ + + def __init__(self, dry_run=False): + self._dry_run = dry_run + + def full_scan(self): + n = 0 + items = self.query() + for item in items: + n += 1 + try: + self.process(item) + Session.add(item) + except Exception, e: + log.error( + 'Unexpected exception processing item %s:\n %s' % ( + item, traceback.format_exc())) + if n > 0 and not self._dry_run: + Session.commit() + self.commit() + return n + + def commit(self): + """Optional commit hook for subclasses.""" + pass + + def query(self): + raise NotImplemented() + + def process(self, item): + raise NotImplemented() + + def loop(self, run_once=False, period=300): + while True: + n = 0 + try: + n = self.full_scan() + except Exception, e: + log.error('Temporary error: %s' % e) + if run_once: + break + if not n: + Session.remove() + time.sleep(period) diff --git a/server/djrandom/scanner/scanner.py b/server/djrandom/scanner/scanner.py index 39b2e14515be8a23e3798ceb9ee3bf8d069344d9..021bb035fef567e4d7f6a6cb9bb0111c977308ca 100644 --- a/server/djrandom/scanner/scanner.py +++ b/server/djrandom/scanner/scanner.py @@ -8,6 +8,7 @@ from djrandom import utils from djrandom.model.mp3 import MP3 from djrandom.database import Session, init_db, indexer from djrandom.scanner import metadata +from djrandom.model import processor log = logging.getLogger(__name__) @@ -16,46 +17,40 @@ class BadMetadataError(Exception): pass -class Scanner(object): +class Scanner(processor.Processor): - def process(self, mp3): + def _process(self, mp3): mp3_info = metadata.analyze_mp3(mp3.path) if not mp3_info.get('artist') or not mp3_info.get('title'): raise BadMetadataError() for key, value in mp3_info.iteritems(): setattr(mp3, key, value) - def scan_db(self, run_once): + def query(self): + return MP3.query.filter_by(state=MP3.INCOMING) + + def process(self, mp3): """Scan the database for new files.""" - while True: - mp3 = MP3.query.filter_by(state=MP3.INCOMING - ).limit(1).first() - if not mp3: - Session.remove() - indexer.commit() - if run_once: - break - time.sleep(60) - continue - log.info('processing %s' % mp3.sha1) - try: - self.process(mp3) - mp3.state = MP3.READY - except BadMetadataError: - log.info('bad metadata for %s' % mp3.sha1) - mp3.state = MP3.BAD_METADATA - except Exception, e: - log.error(traceback.format_exc()) - mp3.state = MP3.ERROR - indexer.add_mp3(mp3) - Session.add(mp3) - Session.commit() + log.info('processing %s' % mp3.sha1) + try: + self._process(mp3) + mp3.state = MP3.READY + except BadMetadataError: + log.info('bad metadata for %s' % mp3.sha1) + mp3.state = MP3.BAD_METADATA + except Exception, e: + log.error(traceback.format_exc()) + mp3.state = MP3.ERROR + indexer.add_mp3(mp3) + + def commit(self): + indexer.commit() def run_scanner(solr_url, db_url, run_once): init_db(db_url, solr_url) scanner = Scanner() - scanner.scan_db(run_once) + scanner.loop(run_once=run_once) def main(): diff --git a/server/djrandom/test/test_scanner.py b/server/djrandom/test/test_scanner.py index 90cccfe8198c2fdfa5348df9079131588ee0333b..432bf85cfe24f7bdcbbc237015f0873e80e7062f 100644 --- a/server/djrandom/test/test_scanner.py +++ b/server/djrandom/test/test_scanner.py @@ -40,7 +40,7 @@ class ScannerTest(DbTestCase): mp3 = MP3.query.get('1') sc = scanner.Scanner() - sc.process(mp3) + sc._process(mp3) self.assertEquals(u'artist', mp3.artist) self.assertEquals(u'title', mp3.title) @@ -52,20 +52,20 @@ class ScannerTest(DbTestCase): mp3 = MP3.query.get('1') sc = scanner.Scanner() self.assertRaises(scanner.BadMetadataError, - sc.process, mp3) + sc._process, mp3) def test_scanner_run(self): sc = scanner.Scanner() mp3 = MP3.query.get('1') - self.mox.StubOutWithMock(sc, 'process') - sc.process(mp3) + self.mox.StubOutWithMock(sc, '_process') + sc._process(mp3) indexer.add_mp3(mp3) indexer.commit() self.mox.ReplayAll() - sc.scan_db(run_once=True) + sc.loop(run_once=True) # Verify changes to the mp3 object. mp3b = MP3.query.get('1') @@ -75,14 +75,14 @@ class ScannerTest(DbTestCase): sc = scanner.Scanner() mp3 = MP3.query.get('1') - self.mox.StubOutWithMock(sc, 'process') - sc.process(mp3).AndRaise(scanner.BadMetadataError()) + self.mox.StubOutWithMock(sc, '_process') + sc._process(mp3).AndRaise(scanner.BadMetadataError()) indexer.add_mp3(mp3) indexer.commit() self.mox.ReplayAll() - sc.scan_db(run_once=True) + sc.loop(run_once=True) # Verify changes to the mp3 object. mp3b = MP3.query.get('1') @@ -92,14 +92,14 @@ class ScannerTest(DbTestCase): sc = scanner.Scanner() mp3 = MP3.query.get('1') - self.mox.StubOutWithMock(sc, 'process') - sc.process(mp3).AndRaise(Exception('something bad!')) + self.mox.StubOutWithMock(sc, '_process') + sc._process(mp3).AndRaise(Exception('something bad!')) indexer.add_mp3(mp3) indexer.commit() self.mox.ReplayAll() - sc.scan_db(run_once=True) + sc.loop(run_once=True) # Verify changes to the mp3 object. mp3b = MP3.query.get('1')