# Created By: Virgil Dupras # Created On: 2007-10-06 # Copyright 2015 Hardcoded Software (http://www.hardcoded.net) # This software is licensed under the "GPLv3" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html import logging import time import traceback import sys from .CocoaProxy import CocoaProxy proxy = CocoaProxy() def autoreleasepool(func): def wrapper(*args, **kwargs): proxy.createPool() try: func(*args, **kwargs) finally: proxy.destroyPool() return wrapper def as_fetch(as_list, as_type, step_size=1000): """When fetching items from a very big list through applescript, the connection with the app will timeout. This function is to circumvent that. 'as_type' is the type of the items in the list (found in appscript.k). If we don't pass it to the 'each' arg of 'count()', it doesn't work. applescript is rather stupid...""" result = [] # no timeout. default timeout is 60 secs, and it is reached for libs > 30k songs item_count = as_list.count(each=as_type, timeout=0) steps = item_count // step_size if item_count % step_size: steps += 1 logging.info('Fetching %d items in %d steps' % (item_count, steps)) # Don't forget that the indexes are 1-based and that the upper limit is included for step in range(steps): begin = step * step_size + 1 end = min(item_count, begin + step_size - 1) if end > begin: result += as_list[begin:end](timeout=0) else: # When there is only one item, the stupid fuck gives it directly instead of putting it in a list. result.append(as_list[begin:end](timeout=0)) time.sleep(.1) logging.info('%d items fetched' % len(result)) return result def extract_tb_noline(tb): # Same as traceback.extract_tb(), but without line fetching limit = 100 list = [] n = 0 while tb is not None and (limit is None or n < limit): f = tb.tb_frame lineno = tb.tb_lineno co = f.f_code filename = co.co_filename name = co.co_name list.append((filename, lineno, name, None)) tb = tb.tb_next n = n+1 return list def safe_format_exception(type, value, tb): """Format exception from type, value and tb and fallback if there's a problem. In some cases in threaded exceptions under Cocoa, I get tracebacks targeting pyc files instead of py files, which results in traceback.format_exception() trying to print lines from pyc files and then crashing when trying to interpret that binary data as utf-8. We want a fallback in these cases. """ try: return traceback.format_exception(type, value, tb) except Exception: result = ['Traceback (most recent call last):\n'] result.extend(traceback.format_list(extract_tb_noline(tb))) result.extend(traceback.format_exception_only(type, value)) return result def install_exception_hook(github_url): def report_crash(type, value, tb): app_identifier = proxy.bundleIdentifier() app_version = proxy.appVersion() osx_version = proxy.osxVersion() s = "Application Identifier: {}\n".format(app_identifier) s += "Application Version: {}\n".format(app_version) s += "Mac OS X Version: {}\n\n".format(osx_version) s += ''.join(safe_format_exception(type, value, tb)) if LOG_BUFFER: s += '\nRelevant Console logs:\n\n' s += '\n'.join(LOG_BUFFER) proxy.reportCrash_withGithubUrl_(s, github_url) sys.excepthook = report_crash # A global log buffer to use for error reports LOG_BUFFER = [] class CocoaHandler(logging.Handler): def emit(self, record): msg = record.getMessage() proxy.log_(msg) LOG_BUFFER.append(msg) del LOG_BUFFER[:-20] def install_cocoa_logger(): logging.getLogger().addHandler(CocoaHandler()) def patch_threaded_job_performer(): # _async_run, under cocoa, has to be run within an autorelease pool to prevent leaks. # You only need this patch is you use one of CocoaProxy's function (which allocate objc # structures) inside a threaded job. from hscommon.jobprogress.performer import ThreadedJobPerformer ThreadedJobPerformer._async_run = autoreleasepool(ThreadedJobPerformer._async_run)