def main(): # Initialize the argument parser... argument_parser = argparse.ArgumentParser( description=_('Query the status of a Helios server.')) # Add common arguments to argument parser... add_common_arguments(argument_parser) # Parse the command line... arguments = argument_parser.parse_args() # Initialize terminal colour... colorama.init() # Try to query server status... success = False try: # If no host provided, use Zeroconf auto detection... if not arguments.host: arguments.host = zeroconf_find_server()[0] # Create a client... client = helios.client(token=arguments.token, host=arguments.host, port=arguments.port, verbose=arguments.verbose) # Perform query... server_status = client.get_server_status() success = True # Helios exception... except helios.exceptions.ExceptionBase as someException: print(someException.what()) # Some other kind of exception... except Exception as someException: print(_(f"An unknown exception occurred: {print(someException)}")) # Show server information if received and verbosity enabled... if success and arguments.verbose: pprint(attr.asdict(server_status)) # Show success status or failure... if success: print(_(f"Server has {server_status.songs} songs.")) else: print( f"{colored(_('There was a problem verifying the server status.'), 'red')}" ) # If unsuccessful, bail... if not success: sys.exit(1) # Done... sys.exit(0)
def _main(): # Get some time interval options. parser = argparse.ArgumentParser( description="Import time entries from Toggl to JIRA" ) add_common_arguments(parser) parser.add_argument( "--start", "-s", action="store", required=False, default=None, help="First day to import data for in the YYYY-MM-DD format. Defaults to 5 days ago at midnight.", ) parser.add_argument( "--end", "-e", action="store", required=False, default=None, help="Last day to import data for in the YYYY-MM-DD format. Defaults to current time.", ) # Read the options from the command line. args = parser.parse_args() if args.start is not None: start = _user_str_to_utc_timezone(args.start) else: # Go back as far as 5 days ago to import data. today_at_midnight = datetime.datetime.now().replace( hour=0, minute=0, second=0, microsecond=0 ) start = today_at_midnight - datetime.timedelta(days=14) + UTC_OFFSET if args.end is not None: end = _user_str_to_utc_timezone(args.end) else: # Otherwise now is pretty much the further something can be logged. end = datetime.datetime.utcnow() # Log into Shotgun and toggl. (toggl, wid) = connect_to_toggl(args.headless) print() print("Updating JIRA issues...") print("===========================") tickets = JiraTickets(args.headless) _export_tickets(toggl, wid, tickets, start, end) print() print("Updating Toggl projects...") print("===========================") _import_tickets(toggl, wid, tickets)
def _main(): parser = ArgumentParser( description="Import time entries from Toggl to Shotgun") add_common_arguments(parser) args = parser.parse_args() # Log into Shotgun (sg, sg_self), (toggl, wid) = connect(args.headless) # Get Toggl project information toggl_projects = dict(get_projects_from_toggl(toggl)) # For each ticket in Shotgun, create or update one in Toggl. for ticket_id, ticket_title in get_tickets_from_shotgun(sg, sg_self): # Compute the project title. project_title = "#%d %s" % (ticket_id, ticket_title) # If the ticket is already imported into Toggl if ticket_id in toggl_projects: project_desc, project_id = toggl_projects[ticket_id] # Make sure the description part of the project name matches the title in shotgun. if project_desc != ticket_title: # No match, so update! toggl.Projects.update( project_id, data={"project": { "name": project_title }}) print "Updated project '%s'" % (project_title, ) else: print "Ticket %d %s is already in Toggl." % (ticket_id, ticket_title) else: # Project is missing, create in Toggl. toggl.Projects.create( {"project": { "name": project_title, "wid": wid }}) print "Created project '%s'" % (project_title, )
'--edit-year', dest='song_edit_year', required=False, nargs='?', help=_('Year to replace existing field.'), type=int) # Entry point... if __name__ == '__main__': # Initialize the argument parser... argument_parser = argparse.ArgumentParser( description=_('Modify a song in a Helios server.')) # Add common arguments to argument parser... add_common_arguments(argument_parser) # Add arguments specific to this utility to argument parser... add_arguments(argument_parser) # Parse the command line... arguments = argument_parser.parse_args() # Initialize terminal colour... colorama.init() # Prepare to modify a song... patch_song_dict = {} # If --edit-file was explicitly passed an empty string, then instruct server # to delete it's file, if it had one...
def main(): # Initialize the argument parser... argument_parser = argparse.ArgumentParser( description=_('Delete a remote song or songs on a Helios server.')) # Add common arguments to argument parser... add_common_arguments(argument_parser) # Add arguments specific to this utility to argument parser... add_arguments(argument_parser) # Parse the command line... arguments = argument_parser.parse_args() # Initialize terminal colour... colorama.init() # Status on whether there were any errors... success = False # Try to delete a single song or all songs... try: # A progress bar we may or may not construct later... progress_bar = None # If no host provided, use Zeroconf auto detection... if not arguments.host: arguments.host = zeroconf_find_server()[0] # Create a client... client = helios.client(host=arguments.host, port=arguments.port, token=arguments.token, verbose=arguments.verbose) # Counter of the number of songs deleted... total_deleted = 0 # Only requested a single song to delete... if arguments.song_id or arguments.song_reference: # Submit request to server... song_deleted = delete_song( client, song_id=arguments.song_id, song_reference=arguments.song_reference, delete_file_only=arguments.delete_file_only) # Update deletion counter... if song_deleted: total_deleted += 1 # Note success... success = True # User requested to delete all song files only, keeping metadata... elif arguments.delete_all and arguments.delete_file_only: # Prompt user to make sure they are certain they know what they are # doing... verification = input( _('Are you sure you know what you are doing? Type \'YES\': ')) if verification != _('YES'): sys.exit(1) # How many songs are currently in the database? server_status = client.get_server_status() # Keep deleting songs while there are some... songs_remaining = True current_page = 1 progress_bar = tqdm(desc=_('Deleting files'), total=server_status.songs, unit=_(' songs')) while songs_remaining: # Get a batch of songs... all_songs_list = client.get_all_songs(page=current_page, page_size=100) # No more songs left... if len(all_songs_list) == 0: songs_remaining = False # Delete each one's song file... for song in all_songs_list: # Submit request to server... song_deleted = delete_song(client, song_id=song.id, song_reference=song.reference, delete_file_only=True) # Update deletion counter... if song_deleted: total_deleted += 1 # Update progress bar... progress_bar.update(1) # Advance to next page... current_page += 1 # Done... progress_bar.close() success = True # User requested to delete all songs and their files... elif arguments.delete_all and not arguments.delete_file_only: # Prompt user to make sure they are certain they know what they are # doing... verification = input( _('Are you sure you know what you are doing? Type \'YES\': ')) if verification != _('YES'): sys.exit(1) # How many songs are currently in the database? server_status = client.get_server_status() # Keep deleting songs while there are some... songs_remaining = True progress_bar = tqdm(desc=_('Deleting metadata and files'), total=server_status.songs, unit=_(' songs')) while songs_remaining: # Get a batch of songs. We can always start chomping at the # first page because everything after it is shifted up the # first page after being deleted... all_songs_list = client.get_all_songs(page=1, page_size=100) # No more songs left... if len(all_songs_list) == 0: songs_remaining = False # Delete each one... for song in all_songs_list: # Submit request to server... song_deleted = delete_song(client, song_id=song.id, song_reference=song.reference, delete_file_only=False) # Update deletion counter... if song_deleted: total_deleted += 1 # Update progress bar... progress_bar.update(1) # Done... progress_bar.close() success = True # User trying to abort... except KeyboardInterrupt: sys.exit(1) # Helios exception... except helios.exceptions.ExceptionBase as someException: print(someException.what()) # Some other kind of exception... except Exception as someException: print(_(f"An unknown exception occurred: {print(someException)}")) # Cleanup any resources... finally: # Dump any new output beginning on a new line... print('') # Cleanup progress bar, if one was constructed... if progress_bar: progress_bar.close() # Show deletion statistics... print(F'Deleted {total_deleted} successfully.') # If unsuccessful, bail... if not success: sys.exit(1) # Done... sys.exit(0)
def main(): # Initialize the argument parser... argument_parser = argparse.ArgumentParser( description=_('Batch import songs into Helios.')) # Add common arguments to argument parser... add_common_arguments(argument_parser) # Add arguments specific to this utility to argument parser... add_arguments(argument_parser) # Parse the command line... arguments = argument_parser.parse_args() arguments.offset = max(arguments.offset, 1) # Setup logging... format = "%(asctime)s: %(message)s" if arguments.verbose: logging.basicConfig(format=format, level=logging.DEBUG, datefmt="%H:%M:%S") else: logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S") # Initialize terminal colour... colorama.init() # Success flag to determine exit code... success = False # Total number of songs in the input catalogue file... songs_total = 0 # Prepare column fieldnames from CSV input catalogue... fieldnames = [ 'reference', 'album', 'artist', 'title', 'genre', 'isrc', 'year', 'path' ] # Batch importer will be constructed within try block... batch_importer = None # Try to process the catalogue file... try: # If no host provided, use Zeroconf auto detection... if not arguments.host: arguments.host = zeroconf_find_server()[0] # Create a client... client = helios.client( token=arguments.token, host=arguments.host, port=arguments.port, verbose=arguments.verbose) # Count the number of songs in the input catalogue... with open(arguments.catalogue_file, 'r') as catalogue_file: for line in catalogue_file: if line.strip(): songs_total += 1 # Verify we can reach the server... server_status = client.get_server_status() # If user requested to autodetect the number of threads, set to the # number of logical cores on the server... if arguments.threads == 0: arguments.threads = server_status.cpu.cores # Initialize batch song importer... batch_importer = BatchSongImporter(arguments, songs_total) # Open catalogue file again, but this time for parsing. Set newline # empty per <https://docs.python.org/3/library/csv.html#id3> catalogue_file = open(arguments.catalogue_file, 'r', newline='') # Initialize the CSV reader... reader = csv.DictReader( f=catalogue_file, fieldnames=fieldnames, delimiter=arguments.delimiter, doublequote=False, escapechar='\\', quoting=csv.QUOTE_MINIMAL, skipinitialspace=True, strict=True) # Submit the songs... batch_importer.start(reader) # Determine how we will exit... success = True #(errors_remaining == arguments.maximum_errors) # User trying to abort... except KeyboardInterrupt: print(_('Aborting, please wait a moment...')) success = False # Helios exception... except helios.exceptions.ExceptionBase as someException: print(someException.what()) # Some other kind of exception... except Exception as someException: print(_(f"An unknown exception occurred: {str(someException)}")) # Cleanup... finally: # Stop batch import, in case it hasn't already... if batch_importer: batch_importer.stop() del batch_importer # Close input catalogue file... catalogue_file.close() # Exit with status code based on whether we were successful or not... if success: sys.exit(0) else: sys.exit(1)
def main(): # Initialize the argument parser... argument_parser = argparse.ArgumentParser( description=_('Add a song to a Helios server.')) # Add common arguments to argument parser... add_common_arguments(argument_parser) # Add arguments specific to this utility to argument parser... add_arguments(argument_parser) # Parse the command line... arguments = argument_parser.parse_args() # Initialize terminal colour... colorama.init() # Success flag to determine exit code... success = False # Try to submit the song... try: # If no host provided, use Zeroconf auto detection... if not arguments.host: arguments.host = zeroconf_find_server()[0] # Create a client... client = helios.client(token=arguments.token, host=arguments.host, port=arguments.port, verbose=arguments.verbose) # Prepare new song data... new_song_dict = { 'file': base64.b64encode(open(arguments.song_file, 'rb').read()).decode('ascii'), 'reference': arguments.song_reference } # Progress bar to be allocated by tqdm as soon as we know the total size... progress_bar = None # Progress bar callback... def progress_callback(bytes_read, new_bytes, bytes_total): # Reference the outer function... nonlocal progress_bar # If the progress bar hasn't been allocated already, do so now... if not progress_bar: progress_bar = tqdm(desc=_('Uploading'), leave=False, smoothing=1.0, total=bytes_total, unit_scale=True, unit='B') # Update the progress bar with the bytes just read... progress_bar.update(new_bytes) # time.sleep(0.001) # If we're done uploading, update description to analysis stage... if bytes_read == bytes_total: #progress_bar.n = 0 progress_bar.set_description(_('Analyzing')) # Redraw the progress bar on the terminal... progress_bar.refresh() # Submit the song... stored_song = client.add_song(new_song_dict=new_song_dict, store=arguments.store, progress_callback=progress_callback) # Note the success... success = True # User trying to abort... except KeyboardInterrupt: sys.exit(1) # Helios exception... except helios.exceptions.ExceptionBase as someException: print(someException.what()) # Some other kind of exception... except Exception as someException: print(_(f"An unknown exception occurred: {print(someException)}")) # Cleanup... finally: if progress_bar: progress_bar.close() # Show stored song model produced by server if successful and verbosity enabled... if success and arguments.verbose: pprint(attr.asdict(stored_song)) # Show success status or failure... if success: print(_(f"[{colored(_(' OK '), 'green')}]: {arguments.song_file}")) else: print(_(f"[{colored(_('FAIL'), 'red')}]: {arguments.song_file}")) # If unsuccessful, bail... if not success: sys.exit(1) # Done... sys.exit(0)
def _main(): # Get some time interval options. parser = argparse.ArgumentParser(description="Import time entries from Toggl to Shotgun") add_common_arguments(parser) parser.add_argument( "--start", "-s", action="store", required=False, default=None, help="First day to import data for in the YYYY-MM-DD format. Defaults to 5 days ago at midnight." ) parser.add_argument( "--end", "-e", action="store", required=False, default=None, help="Last day to import data for in the YYYY-MM-DD format. Defaults to current time." ) # Read the options from the command line. args = parser.parse_args() if args.start is not None: start = _user_str_to_utc_timezone(args.start) else: # Go back as far as 5 days ago to import data. today_at_midnight = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) start = today_at_midnight - datetime.timedelta(days=7) + UTC_OFFSET if args.end is not None: end = _user_str_to_utc_timezone(args.end) else: # Otherwise now is pretty much the further something can be logged. end = datetime.datetime.utcnow() # Log into Shotgun and toggl. (sg, sg_self), (toggl, wid) = connect(args.headless) # Get Toggl project information toggl_projects = get_projects_from_toggl(toggl) # Create a map that goes from Toggl project id to a Shotgun ticket id. toggl_projects_to_sg = {project_id: ticket_id for ticket_id, (_, project_id) in toggl_projects} # Get the entries that the user requested. time_entries = toggl.TimeEntries.get( start_date=_to_toggl_date_format(start), end_date=_to_toggl_date_format(end) ) previous_day = None # Group tasks by day, project and task name so we can compute and save a duration for a given task # on a given project on a give day. for (day, pid, task_name), time_entries in _sort_time_entries(_massage_time_entries(time_entries)): # Task names are optional. If any, set to a empty string. task_name = task_name or "" # if the project is not tracked in Shotgun, skip it! ticket_id = toggl_projects_to_sg.get(pid) if ticket_id is None: continue # If we're on a new day, print its header. if previous_day != day: print day previous_day = day # Sum all the durations, except the one in progress if it is present (durtion < 9()) total_task_duration = int(sum((entry["duration"] for entry in time_entries if entry["duration"] >= 0)) / 60.0) # Show some progress. print " Ticket %s, Task %s %s" % ( ticket_id, task_name.ljust(40), _to_hours_minutes(total_task_duration) ) ticket_link = {"type": "Ticket", "id": ticket_id} # Find if we have an entry for this time log. timelog_entity = sg.find_one( "TimeLog", [ ["entity", "is", ticket_link], ["description", "is", task_name], ["date", "is", day] ] ) # Create or update the entry in Shotgun. if timelog_entity: sg.update( "TimeLog", timelog_entity["id"], {"duration": total_task_duration} ) else: sg.create("TimeLog", { "entity": ticket_link, "description": task_name, "duration": max(total_task_duration, 1), "project": {"type": "Project", "id": 12}, "date": day })