#!/usr/bin/env python2.4
# vim: set nocp noai ruler et ts=8 sts=4 rulerformat=%<%h%m%r%=\"%f\"\ \ %b\ 0x%B\ \ %l,%c%V\ %P:

import datetime;
import os;
import re;
import smtplib;
import sys;
import types;
import zipfile;

import appscript;

ftp_serv = u'****';
ftp_user = u'****';
ftp_pass = u'****';
ftp_mode = appscript.k.replace;

smtp_serv = u'****';
smtp_user = u'****';
smtp_pass = u'****';

smtp_to = u'****';
smtp_from = u'****';
smtp_message = u'To: Jordan Breeding <%s>\n' % (unicode(smtp_to));
smtp_message += u'From: Jordan Breeding <%s>\n' % (unicode(smtp_from));
smtp_message += u'Subject: File Transfer Status\n';
smtp_message += u'The file transfers are done.';

class mystr(str):
    def quote(self, charsToQuote = None):
        if (not charsToQuote):
            return(self);
        else:
            return(re.sub('([%s])' % (charsToQuote), r'\\\1', self));

class mystr(unicode):
    def quote(self, charsToQuote = None):
        if (not charsToQuote):
            return(self);
        else:
            return(re.sub(u'([%s])' % (unicode(charsToQuote)), ur'\\\1', self));

class mylist(list):
    def unique(self):
        tempList = [];
        [tempList.append(__item) for __item in self if (__item not in tempList)];
        self = tempList;
        return(self);

class archiveSpec:
    app_ref = None;
    target_playlist_ref = None;
    archive_limit = pow(1024, 3);
    current_archive_size = 0;
    date = None;
    playlist_date_string = u'';
    playlist_name = u'';
    playlist_ref = None;
    archive_date_string = u'';
    archive_name = u'';
    archive_tracks = [];
    last_track_added = None;
    def __init__(self, app_ref, target_playlist_ref):
        self.app_ref = app_ref;
        self.target_playlist_ref = target_playlist_ref;
        self.current_archive_size = 0;
        self.date = datetime.datetime.now();
        self.playlist_date_string = self.date.strftime('%Y-%m-%d %H:%M:%S');
        self.archive_date_string = self.date.strftime('%Y-%m-%d %H_%M_%S');
        self.playlist_name = self.playlist_date_string;
        self.archive_name = u'Music%s.zip' % (self.archive_date_string);
        self.archive_tracks = [];
    def add(self, sizeOfFiles, tracksToAdd):
        self.current_archive_size += sizeOfFiles;
        if (self.current_archive_size <= self.archive_limit):
            self.archive_tracks.extend(tracksToAdd);
            return(True);
        else:
            self.current_archive_size -= sizeOfFiles;
            return(False);
    def update(self):
        self.date = datetime.datetime.now();
        self.playlist_date_string = self.date.strftime('%Y-%m-%d %H:%M:%S');
        self.playlist_name = self.playlist_date_string;
        self.archive_date_string = self.date.strftime('%Y-%m-%d %H_%M_%S');
        self.archive_name = u'Music%s.zip' % (self.archive_date_string);
    def create_playlist(self):
        self.playlist_ref = self.target_playlist_ref.make(new = appscript.k.playlist, with_properties = {appscript.k.name: self.playlist_name});
        for __item in self.archive_tracks:
            self.last_track_added = __item[u'track'].duplicate(to = self.playlist_ref);

class iTunes:
    app_ref = None;
    fixed_indexing_backup = None;
    def __init__(self, app_ref):
        self.app_ref = app_ref;
    def setup(self):
        self.fixed_indexing_backup = self.app_ref.fixed_indexing.get();
        self.app_ref.fixed_indexing.set(True);
    def reset(self):
        self.app_ref.fixed_indexing.set(self.fixed_indexing_backup);
    def report(self):
        print(u'Backup fixed_indexing: %s' % (self.fixed_indexing_backup));
        print(u'Current fixed_indexing: %s' % (self.app_ref.fixed_indexing.get()));

def remove_dsstore(name):
    for root, dirs, files in os.walk(name):
        if (u'.DS_Store' in files):
            os.unlink(os.path.join(root, u'.DS_Store'));

def remove_dirs(name):
    try:
        os.rmdir(name);
    except OSError:
        remove_dsstore(name);
        os.rmdir(name);
    head, tail = os.path.split(name);
    if (not tail):
        head, tail = os.path.split(head);
    while (head and tail):
        try:
            os.rmdir(head);
        except OSError:
            try:
                remove_dsstore(head);
                os.rmdir(head);
            except OSError:
                break;
        head, tail = os.path.split(head);

def register_with_growl():
    allNotificationsList = [u'Automator notification'];
    enabledNotificationsList = [u'Automator notification'];
    appscript.app(u'GrowlHelperApp').register(as_application = u'Automator', all_notifications = allNotificationsList,
                                              default_notifications = enabledNotificationsList,
                                              icon_of_application = u'Automator');

def send_to_growl(title, description):
    appscript.app(u'GrowlHelperApp').notify(with_name = u'Automator notification', title = title,
                                            description = description, application_name = u'Automator');

def sortTrack(lhs, rhs):
    artist_cmp = cmp(re.sub(r'^[tT][hH][eE][[:space:]]', u'', lhs[u'artist']),
                     re.sub(r'^[tT][hH][eE][[:space:]]', u'', rhs[u'artist']));
    if (artist_cmp != 0):
        return(artist_cmp);
    else:
        album_cmp = cmp(lhs[u'album'], rhs[u'album']);
        if (album_cmp != 0):
            return(album_cmp);
        else:
            disc_cmp = cmp(lhs[u'disc number'], rhs[u'disc number']);
            if (disc_cmp != 0):
                return(disc_cmp);
            else:
                return(cmp(lhs[u'track number'], rhs[u'track number']));

def remove_files(filesToRemove = []):
    if (filesToRemove):
        for __item in filesToRemove:
            if (os.access(__item, os.R_OK) and os.access(os.path.dirname(__item), os.W_OK)):
                os.unlink(__item);
        try:
            for __item in mylist([os.path.dirname(__item) for __item in filesToRemove]).unique():
                remove_dirs(__item);
        except OSError:
            pass;

register_with_growl();

itunes_ref = appscript.app(u'iTunes');

itunes_inst = iTunes(itunes_ref);

itunes_inst.setup();

send_to_growl(u'Getting Track Information', u'Getting track information from iTunes.');

target_playlist_ref = itunes_ref.sources[u'Library'].playlists[u'Sent'];

if (not (target_playlist_ref and (target_playlist_ref.special_kind.get() == appscript.k.folder))):
    send_to_growl(u'ERROR', u'Could not find suitable "Sent" playlist.');
    raise(StandardError(u'Could not find suitable "Sent" playlist.'));

playlist_ref = itunes_ref.sources[u'Library'].playlists[u'Send'];

track_array = playlist_ref.file_tracks;

track_array = zip(track_array.get(),
                  track_array.location.get(),
                  track_array.database_ID.get(),
                  track_array.album.get(),
                  track_array.artist.get(),
                  track_array.disc_number.get(),
                  track_array.track_number.get(),
                  track_array.compilation.get(),
                  track_array.size.get());

if (not track_array):
    send_to_growl(u'ERROR', u'Nothing to archive.');
    raise(StandardError(u'Nothing to archive.'));

field_map = {u'track': 0, u'location': 1, u'database id': 2, u'album': 3,
             u'artist': 4, u'disc number': 5, u'track number': 6, u'compilation': 7,
             u'size': 8};

def generate_artist(trackRef):
    if (trackRef[field_map[u'compilation']]):
        return(u'Compilations');
    else:
        return(trackRef[field_map[u'artist']]);

track_array = [{u'track': item[field_map[u'track']],
                u'file path': item[field_map[u'location']].path,
                u'database id': item[field_map[u'database id']],
                u'album': item[field_map[u'album']],
                u'artist': generate_artist(item),
                u'disc number': item[field_map[u'disc number']],
                u'track number': item[field_map[u'track number']],
                u'size': item[field_map[u'size']]} for item in track_array];

track_array.sort(sortTrack);

archive_array = [];

current_archive = archiveSpec(itunes_ref, target_playlist_ref);

send_to_growl(u'Starting To Sort', u'Starting the sorting process.');

for item in mylist([item[u'album'] for item in track_array]).unique():
    local_track_array = [_item for _item in track_array if (_item[u'album'] == item)];
    album_size = 0;
    if (local_track_array):
        for _item in local_track_array:
            album_size += _item[u'size'];
        if (not current_archive.add(album_size, local_track_array)):
            archive_array.append(current_archive);
            current_archive = archiveSpec(itunes_ref, target_playlist_ref);
            current_archive.add(album_size, local_track_array)
    track_array = [_item for _item in track_array if (_item not in local_track_array)];

if (current_archive.current_archive_size > 0):
    archive_array.append(current_archive);

send_to_growl(u'Sorting Complete', u'The sorting process is complete.');

send_to_growl(u'Starting To Archive', u'Starting the archiving process.');

archive_dir = os.path.expanduser(u'~/Desktop/Music');

if (not os.path.isdir(archive_dir)):
    if (not os.path.exists(archive_dir)):
        if (os.path.lexists(archive_dir)):
            os.unlink(archive_dir);
        os.makedirs(archive_dir);
    else:
        raise(StandardError(u'"~/Desktop/Music" is not a directory.'));

for item in archive_array:
    item.update();
    send_to_growl(u'Creating Archive', u'Creating the archive "%s".' % (item.archive_name));
    new_zipfile = zipfile.ZipFile(os.path.join(archive_dir, item.archive_name), u'w', zipfile.ZIP_DEFLATED);
    for _item in item.archive_tracks:
        file_name_to_use = re.sub(u':', u'_', u'Music/%s/%s/%s' % (_item[u'artist'], _item[u'album'], os.path.basename(_item[u'file path'])));
        new_zipfile.write(_item[u'file path'], file_name_to_use.encode(u'ascii', u'replace'), zipfile.ZIP_STORED);
    new_zipfile.close();

send_to_growl(u'Archival Complete', u'The archiving process is complete.');

def connect_to_server(ftp_session_ref, ftp_serv, ftp_user, ftp_pass):
    if (ftp_session_ref.connect(to = ftp_serv, as_user = ftp_user, with_password = ftp_pass, with_connection_type = appscript.k.SFTP)):
        send_to_growl(u'Connected To Site', u'Connected to %s using %s.' % (ftp_serv, u'SFTP'));
        return(True);
    elif (ftp_session_ref.connect(to = ftp_serv, as_user = ftp_user, with_password = ftp_pass, with_connection_type = appscript.k.FTP_TLS_SSL)):
        send_to_growl(u'Connected To Site', u'Connected to %s using %s.' % (ftp_serv, u'FTP with TLS/SSL'));
        return(True);
    elif (ftp_session_ref.connect(to = ftp_serv, as_user = ftp_user, with_password = ftp_pass, with_connection_type = appscript.k.FTP_implicit_SSL)):
        send_to_growl(u'Connected To Site', u'Connected to %s using %s.' % (ftp_serv, u'FTP with SSL'));
        return(True);
    elif (ftp_session_ref.connect(to = ftp_serv, as_user = ftp_user, with_password = ftp_pass, with_connection_type = appscript.k.FTP)):
        send_to_growl(u'Connected To Site', u'Connected to %s using %s.' % (ftp_serv, u'FTP'));
        return(True);
    else:
        return(False);

def transfer_file(ftp_session_ref, ftp_serv, ftp_user, ftp_pass, to_dir, from_dir, file_to_transfer, resume_mode):
    if (not ftp_session_ref.is_connected.get()):
        if (not connect_to_server(ftp_session_ref, ftp_serv, ftp_user, ftp_pass)):
            raise(StandardError(u'Could not connect to %s.' % (ftp_serv)));
    else:
        ftp_session_ref.disconnect();
        if (not connect_to_server(ftp_session_ref, ftp_serv, ftp_user, ftp_pass)):
            raise(StandardError(u'Could not connect to %s.' % (ftp_serv)));
    if ((ftp_session_ref.their_stuff.get() == to_dir) or ftp_session_ref.set_their_stuff(to = to_dir)):
        if ((ftp_session_ref.your_stuff.get() == from_dir) or ftp_session_ref.set_your_stuff(to = from_dir)):
            send_to_growl(u'Sending Archive', u'Sending the archive "%s".' % (item.archive_name));
            if (ftp_session_ref.upload(item = os.path.join(archive_dir, item.archive_name), with_resume_mode = ftp_mode, timeout = 0)):
                remove_files([os.path.join(archive_dir, file_to_transfer)]);
                item.create_playlist();
                ftp_session_ref.disconnect();
            else:
                raise(StandardError(u'Upload failed.'));
        else:
            raise(StandardError(u'Can not change local directory.'));
    else:
        raise(StandardError(u'Can not change remote directory.'));
    return(False);

try:
    send_to_growl(u'Starting To Transfer', u'Starting the transfer process.');
    transmit_ref = appscript.app(u'Transmit');
    transmit_ref.SuppressAppleScriptAlerts.set(True);
    if (len(transmit_ref.documents.get()) == 0):
        transmit_document_ref = transmit_ref.documents.end.make(new = appscript.k.document);
    else:
        transmit_document_ref = transmit_ref.documents[0];
    if (transmit_document_ref.current_session.is_connected.get()):
        transmit_document_ref = transmit_ref.documents.end.make(new = appscript.k.document);
        transmit_session_ref = transmit_document_ref.current_session;
    else:
        transmit_session_ref = transmit_document_ref.current_session;
    while (archive_array):
        item = archive_array[0];
        transfer_file(transmit_session_ref, ftp_serv, ftp_user, ftp_pass, u'/', os.path.expanduser(u'~'), item.archive_name, ftp_mode);
        archive_array.remove(item);
    if (archive_array):
        remove_files([os.path.join(archive_dir, _item.archive_name) for _item in archive_array]);
    transmit_document_ref.close(saving = appscript.k.no_);
    if (len(transmit_ref.documents.get()) == 0):
        transmit_ref.quit(saving = appscript.k.no_);
    send_to_growl(u'Transfer Complete', u'The transfer process is complete.');
except:
    if (unicode(sys.exc_info()[1]) != u''):
        send_to_growl(u'ERROR', u'There was an exception: %s: %s' % (sys.exc_info()[0], sys.exc_info()[1]));
        print((u'ERROR: There was an exception: %s: %s' % (sys.exc_info()[0], sys.exc_info()[1])).encode(u'ascii', u'replace'));
    else:
        send_to_growl(u'ERROR', u'There was an exception: %s.' % (sys.exc_info()[0]));
        print((u'ERROR: There was an exception: %s.' % (sys.exc_info()[0])).encode(u'ascii', u'replace'));
    if (archive_array):
        remove_files([os.path.join(archive_dir, _item.archive_name) for _item in archive_array]);
    transmit_document_ref.close(saving = appscript.k.no_);
    if (len(transmit_ref.documents.get()) == 0):
        transmit_ref.quit(saving = appscript.k.no_);
    itunes_inst.reset();
    sys.exit(1);

try:
    send_to_growl(u'Sending Notification E-mail', u'Sending notification e-mail to jordan.breeding@mac.com');
    smtp_ref = smtplib.SMTP(smtp_serv);
    smtp_ref.starttls();
    smtp_ref.login(smtp_user, smtp_pass);
    smtp_ref.sendmail(smtp_to, smtp_from, smtp_message);
    smtp_ref.close();
except:
    pass;

itunes_inst.reset();

sys.exit(0);

