Esempio n. 1
0
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)
Esempio n. 2
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)
Esempio n. 3
0
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)
Esempio n. 8
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
            })