def merge_existing_contacts(source_contact, target_contact, delete_source_contact): # create temp files for each vcard # source vcard source_tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) source_temp_file_name = source_tf.name source_tf.write("# merge from %s\n%s" \ % (source_contact.get_full_name(), helpers.get_existing_contact_template(source_contact))) source_tf.close() # target vcard target_tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) target_temp_file_name = target_tf.name target_tf.write("# merge into %s\n%s" \ % (target_contact.get_full_name(), helpers.get_existing_contact_template(target_contact))) target_tf.close() # start editor to edit contact template child = subprocess.Popen([Config().get_merge_editor(), source_temp_file_name, target_temp_file_name]) streamdata = child.communicate()[0] # template of source vcard is not required anymore os.remove(source_temp_file_name) # instead we are interested in the target template contents target_tf = open(target_temp_file_name, "r") merged_contact = CarddavObject.from_existing_contact_with_new_user_input(target_contact, target_tf.read()) target_tf.close() os.remove(target_temp_file_name) # compare them if target_contact == merged_contact: print("Merge unsuccessfull: Target contact was not modified") return while True: if delete_source_contact: input_string = raw_input( "Merge contact %s from address book %s into contact %s from address book %s\n\n" \ "To be removed\n\n%s\n\nMerged\n\n%s\n\nAre you sure? (y/n): " \ % (source_contact.get_full_name(), source_contact.get_address_book().get_name(), merged_contact.get_full_name(), merged_contact.get_address_book().get_name(), source_contact.print_vcard(), merged_contact.print_vcard())) else: input_string = raw_input( "Merge contact %s from address book %s into contact %s from address book %s\n\n" \ "Keep unchanged\n\n%s\n\nMerged:\n\n%s\n\nAre you sure? (y/n): " \ % (source_contact.get_full_name(), source_contact.get_address_book().get_name(), merged_contact.get_full_name(), merged_contact.get_address_book().get_name(), source_contact.print_vcard(), merged_contact.print_vcard())) if input_string.lower() in ["", "n", "q"]: print("Canceled") return if input_string.lower() == "y": break # save merged_contact to disk and delete source contact merged_contact.write_to_file(overwrite=True) if delete_source_contact: source_contact.delete_vcard_file() print("Merge successful\n\n%s" % merged_contact.print_vcard())
def modify_existing_contact(old_contact): # create temp file and open it with the specified text editor tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name tf.write("# Edit contact: %s\n%s" \ % (old_contact.get_full_name(), helpers.get_existing_contact_template(old_contact))) tf.close() # start editor to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] # read temp file contents after editing tf = open(temp_file_name, "r") new_contact = CarddavObject.from_existing_contact_with_new_user_input( old_contact, tf.read()) tf.close() os.remove(temp_file_name) # check if the user changed anything if old_contact == new_contact: print("Nothing changed.") else: new_contact.write_to_file(overwrite=True) print("Modification successful\n\n%s" % new_contact.print_vcard())
def create_new_contact(address_book): # create temp file tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name old_contact_template = "# create new contact\n%s" % helpers.get_new_contact_template(address_book.get_name()) tf.write(old_contact_template) tf.close() # start vim to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] # read temp file contents after editing tf = open(temp_file_name, "r") new_contact_template = tf.read() tf.close() os.remove(temp_file_name) # create carddav object from temp file if old_contact_template == new_contact_template: print("Canceled") else: new_contact = CarddavObject.from_user_input(address_book, new_contact_template) new_contact.write_to_file() print("Creation successful\n\n%s" % new_contact.print_vcard())
def create_new_contact(address_book): # create temp file tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name old_contact_template = "# create new contact\n%s" % helpers.get_new_contact_template( address_book.get_name()) tf.write(old_contact_template) tf.close() # start vim to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] # read temp file contents after editing tf = open(temp_file_name, "r") new_contact_template = tf.read() tf.close() os.remove(temp_file_name) # create carddav object from temp file if old_contact_template == new_contact_template: print("Canceled") else: new_contact = CarddavObject.from_user_input(address_book, new_contact_template) new_contact.write_to_file() print("Creation successful\n\n%s" % new_contact.print_vcard())
def modify_existing_contact(old_contact): # create temp file and open it with the specified text editor tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name tf.write("# Edit contact: %s\n%s" \ % (old_contact.get_full_name(), old_contact.get_template())) tf.close() temp_file_creation = helpers.file_modification_date(temp_file_name) while True: # start editor to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] if temp_file_creation == helpers.file_modification_date(temp_file_name): print "not modified" new_contact = None os.remove(temp_file_name) break # read temp file contents after editing tf = open(temp_file_name, "r") new_contact_template = tf.read() tf.close() # try to create contact from user input try: new_contact = CarddavObject.from_existing_contact_with_new_user_input( old_contact, new_contact_template) except ValueError as e: print("\n%s\n" % e) while True: input_string = raw_input("Do you want to open the editor again (y/n)? ") if input_string.lower() in ["", "n", "q"]: print("Canceled") os.remove(temp_file_name) sys.exit(0) if input_string.lower() == "y": break else: os.remove(temp_file_name) break # check if the user changed anything if new_contact is None \ or old_contact == new_contact: print("Nothing changed\n\n%s" % old_contact.print_vcard()) else: new_contact.write_to_file(overwrite=True) print("Modification successful\n\n%s" % new_contact.print_vcard())
def create_new_contact(address_book): # create temp file tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name old_contact_template = "# create new contact\n%s" % helpers.get_new_contact_template(address_book.get_name()) tf.write(old_contact_template) tf.close() temp_file_creation = helpers.file_modification_date(temp_file_name) while True: # start vim to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] if temp_file_creation == helpers.file_modification_date(temp_file_name): new_contact = None os.remove(temp_file_name) break # read temp file contents after editing tf = open(temp_file_name, "r") new_contact_template = tf.read() tf.close() # try to create new contact try: new_contact = CarddavObject.from_user_input(address_book, new_contact_template) except ValueError as e: print("\n%s\n" % e) while True: input_string = raw_input("Do you want to open the editor again (y/n)? ") if input_string.lower() in ["", "n", "q"]: print("Canceled") os.remove(temp_file_name) sys.exit(0) if input_string.lower() == "y": break else: os.remove(temp_file_name) break # create carddav object from temp file if new_contact is None \ or old_contact_template == new_contact_template: print("Canceled") else: new_contact.write_to_file() print("Creation successful\n\n%s" % new_contact.print_vcard())
def create_new_contact(addressbook): # create temp file tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name tf.write(helpers.get_new_contact_template(addressbook['name'])) tf.close() # start vim to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] # read temp file contents after editing tf = open(temp_file_name, "r") new_contact_template = tf.read() tf.close() os.remove(temp_file_name) # create carddav object from temp file vcard = CarddavObject(addressbook['name'], addressbook['path']) vcard.process_user_input(new_contact_template) vcard.write_to_file() print "Creation successful\n\n%s" % vcard.print_vcard()
def modify_existing_contact(old_contact): # create temp file and open it with the specified text editor tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) temp_file_name = tf.name tf.write("# Edit contact: %s\n%s" \ % (old_contact.get_full_name(), helpers.get_existing_contact_template(old_contact))) tf.close() # start editor to edit contact template child = subprocess.Popen([Config().get_editor(), temp_file_name]) streamdata = child.communicate()[0] # read temp file contents after editing tf = open(temp_file_name, "r") new_contact = CarddavObject.from_existing_contact_with_new_user_input(old_contact, tf.read()) tf.close() os.remove(temp_file_name) # check if the user changed anything if old_contact == new_contact: print("Nothing changed.") else: new_contact.write_to_file(overwrite=True) print("Modification successful\n\n%s" % new_contact.print_vcard())
def __init__(self): self.config = None self.address_book_list = [] self.uid_dict = {} # load config file xdg_config_home = os.environ.get("XDG_CONFIG_HOME") or \ os.path.expanduser("~/.config") config_file = os.environ.get("KHARD_CONFIG") or \ os.path.join(xdg_config_home, "khard", "khard.conf") if os.path.exists(config_file) == False: print("Config file %s not available" % config_file) sys.exit(2) self.config = ConfigObj(config_file, interpolation=False) # general settings if self.config.has_key("general") == False: print("Error in config file\nMissing main section \"[general]\".") sys.exit(2) # editor self.config['general']['editor'] = self.config['general'].get("editor") \ or os.environ.get("EDITOR") if self.config['general']['editor'] is None: print("Error in config file\n" \ "Set path to your preferred text editor in khard's config file or the $EDITOR shell variable\n" \ "Example for khard.conf: editor = vim") sys.exit(2) self.config['general']['editor'] = find_executable( os.path.expanduser(self.config['general']['editor'])) if self.config['general']['editor'] is None: print("Error in config file\nInvalid editor path or executable not found.") sys.exit(2) # merge editor self.config['general']['merge_editor'] = self.config['general'].get("merge_editor") \ or os.environ.get("MERGE_EDITOR") if self.config['general']['merge_editor'] is None: print("Error in config file\n" \ "Set path to your preferred text merge editor in khard's config file or the $MERGE_EDITOR shell variable\n" \ "Example for khard.conf: merge_editor = vimdiff") sys.exit(2) self.config['general']['merge_editor'] = find_executable( os.path.expanduser(self.config['general']['merge_editor'])) if self.config['general']['merge_editor'] is None: print("Error in config file\nInvalid merge editor path or executable not found.") sys.exit(2) # default values for action and nickname settings if self.config['general'].has_key("default_action") == False: print("Error in config file\nMissing default action parameter.") sys.exit(2) elif self.config['general']['default_action'] not in self.get_list_of_actions(): print("Error in config file\n" \ "Non existing value for default action parameter\n" \ "Possible values are: %s" % ', '.join(self.get_list_of_actions())) sys.exit(2) if self.config['general'].has_key("show_nicknames") == False: self.config['general']['show_nicknames'] = False elif self.config['general']['show_nicknames'] == "yes": self.config['general']['show_nicknames'] = True elif self.config['general']['show_nicknames'] == "no": self.config['general']['show_nicknames'] = False else: print("Error in config file\nshow_nicknames parameter must be yes or no.") sys.exit(2) # load address books and contacts error_counter = 0 number_of_contacts = 0 if self.config.has_key("addressbooks") == False: print("Error in config file\nMissing main section \"[addressbooks]\".") sys.exit(2) if len(self.config['addressbooks'].keys()) == 0: print("Error in config file\nNo address book entries available.") sys.exit(2) for name in self.config['addressbooks'].keys(): # create address book object try: address_book = AddressBook(name, self.config['addressbooks'][name]['path']) except KeyError as e: print("Error in config file\nMissing path to the \"%s\" address book." % name) sys.exit(2) except IOError as e: print("Error in config file\n%s" % e) sys.exit(2) # load all vcard files for filename in glob.glob(os.path.join(address_book.get_path(), "*.vcf")): try: address_book.add_contact( CarddavObject.from_file(address_book, filename)) number_of_contacts += 1 except IOError as e: print("Error: Could not open file %s\n%s" % (filename, e)) error_counter += 1 except vobject.base.ParseError as e: print("Error: Could not parse file %s\n%s" % (filename, e)) error_counter += 1 # add address book to list self.address_book_list.append(address_book) # check if one or more contacts could not be parsed if error_counter > 0: print("\n%d of %d vcard files could not be parsed" % (error_counter, number_of_contacts)) sys.exit(2) # check, if multiple contacts have the same uid length_of_shortest_uid = 100 number_of_contacts_with_uid = 0 for address_book in self.address_book_list: for contact in address_book.get_contact_list(): uid = contact.get_uid() if uid != "": matching_contact = self.uid_dict.get(uid) if matching_contact is None: self.uid_dict[uid] = contact number_of_contacts_with_uid += 1 if len(uid) < length_of_shortest_uid: length_of_shortest_uid = len(uid) else: print("The contact %s from address book %s" \ " and the contact %s from address book %s have the same uid %s" \ % (matching_contact.get_full_name(), matching_contact.get_address_book().get_name(), contact.get_full_name(), contact.get_address_book().get_name(), contact.get_uid()) ) sys.exit(2) # now we can be sure, that all uid's are unique but we don't want to enter # the whole uid, if we choose a contact by the -u / --uid option # so clear previously filled uid_dict and recreate with the shortest possible uid, so # that it's still unique and easier to enter # with around 100 contacts that short id should not be longer then two or three characters length_of_uid = 1 while True: self.uid_dict.clear() for address_book in self.address_book_list: for contact in address_book.get_contact_list(): uid = contact.get_uid()[:length_of_uid] if uid != "": self.uid_dict[uid] = contact if len(self.uid_dict.keys()) != number_of_contacts_with_uid: length_of_uid += 1 else: break if length_of_uid == length_of_shortest_uid: # prevent infinit loop, # should not be necessary, cause we checked the uid uniqueness in the previous step # so it's just a precaution print("Could not create the dictionary of the short uid's") sys.exit(2)
def __init__(self): self.config = None self.address_book_list = [] # load config file xdg_config_home = os.environ.get("XDG_CONFIG_HOME") or \ os.path.expanduser("~/.config") config_file = os.environ.get("KHARD_CONFIG") or \ os.path.join(xdg_config_home, "khard", "khard.conf") if os.path.exists(config_file) == False: print("Config file %s not available" % config_file) sys.exit(2) self.config = ConfigObj(config_file, interpolation=False) # general settings if self.config.has_key("general") == False: print("Error in config file\nMissing main section \"[general]\".") sys.exit(2) # editor self.config['general']['editor'] = self.config['general'].get("editor") \ or os.environ.get("EDITOR") if self.config['general']['editor'] is None: print("Error in config file\n" \ "Set path to your preferred text editor in khard's config file or the $EDITOR shell variable\n" \ "Example for khard.conf: editor = vim") sys.exit(2) self.config['general']['editor'] = find_executable( os.path.expanduser(self.config['general']['editor'])) if self.config['general']['editor'] is None: print("Error in config file\nInvalid editor path or executable not found.") sys.exit(2) # merge editor self.config['general']['merge_editor'] = self.config['general'].get("merge_editor") \ or os.environ.get("MERGE_EDITOR") if self.config['general']['merge_editor'] is None: print("Error in config file\n" \ "Set path to your preferred text merge editor in khard's config file or the $MERGE_EDITOR shell variable\n" \ "Example for khard.conf: merge_editor = vimdiff") sys.exit(2) self.config['general']['merge_editor'] = find_executable( os.path.expanduser(self.config['general']['merge_editor'])) if self.config['general']['merge_editor'] is None: print("Error in config file\nInvalid merge editor path or executable not found.") sys.exit(2) # default values for action and nickname settings if self.config['general'].has_key("default_action") == False: print("Error in config file\nMissing default action parameter.") sys.exit(2) elif self.config['general']['default_action'] not in self.get_list_of_actions(): print("Error in config file\n" \ "Non existing value for default action parameter\n" \ "Possible values are: %s" % ', '.join(self.get_list_of_actions())) sys.exit(2) if self.config['general'].has_key("show_nicknames") == False: self.config['general']['show_nicknames'] = False elif self.config['general']['show_nicknames'] == "yes": self.config['general']['show_nicknames'] = True elif self.config['general']['show_nicknames'] == "no": self.config['general']['show_nicknames'] = False else: print("Error in config file\nshow_nicknames parameter must be yes or no.") sys.exit(2) # load address books error_counter = 0 if self.config.has_key("addressbooks") == False: print("Error in config file\nMissing main section \"[addressbooks]\".") sys.exit(2) if len(self.config['addressbooks'].keys()) == 0: print("Error in config file\nNo address book entries available.") sys.exit(2) for name in self.config['addressbooks'].keys(): # create address book object try: address_book = AddressBook(name, self.config['addressbooks'][name]['path']) except KeyError as e: print("Error in config file\nMissing path to the \"%s\" address book." % name) sys.exit(2) except IOError as e: print("Error in config file\n%s" % e) sys.exit(2) # load all vcard files for filename in glob.glob(os.path.join(address_book.get_path(), "*.vcf")): try: address_book.add_contact( CarddavObject.from_file(address_book, filename)) except IOError as e: print("Error: Could not open file %s\n%s" % (filename, e)) error_counter += 1 except vobject.base.ParseError as e: print("Error: Could not parse file %s\n%s" % (filename, e)) error_counter += 1 # add address book to list self.address_book_list.append(address_book) # check if one or more contacts could not be parsed if error_counter > 0: if error_counter == 1: print("\n1 vcard file could not be parsed") elif error_counter > 1: print("\n%d vcard files could not be parsed" % error_counter) sys.exit(2)
def main(): # create the args parser parser = argparse.ArgumentParser( description = "Khard is a carddav address book for the console", formatter_class = argparse.RawTextHelpFormatter) parser.add_argument("-a", "--addressbook", default="", help="Specify address book names as comma separated list") parser.add_argument("-r", "--reverse", action="store_true", help="Sort contacts in reverse order") parser.add_argument("-s", "--search", default="", help="Search in all contact data fields\n" \ " default: -s \"contact\"\n" \ " merge: -s \"source contact,target contact\"\n" \ " copy/move: -s \"source contact,target address book\"") parser.add_argument("-t", "--sort", default="alphabetical", help="Sort contacts list. Possible values: alphabetical, addressbook") parser.add_argument("-v", "--version", action="store_true", help="Get current program version") parser.add_argument("action", nargs="?", default="", help="Possible actions:\n" \ " list, details, email, phone, source,\n" \ " new, add-email, merge, modify, copy, move and remove") args = parser.parse_args() # version if args.version == True: print("Khard version %s" % khard_version) sys.exit(0) # validate value for action if args.action == "": args.action = Config().get_default_action() if args.action not in Config().get_list_of_actions(): print("Unsupported action. Possible values are: %s" % ', '.join(Config().get_list_of_actions())) sys.exit(1) # load address books which are defined in the configuration file selected_address_books = [] if args.addressbook == "": selected_address_books = Config().get_all_address_books() else: for name in args.addressbook.split(","): if Config().get_address_book(name) is None: print("Error: The entered address book \"%s\" does not exist.\nPossible values are: %s" \ % (name, ', '.join([ str(book) for book in Config().get_all_address_books() ]))) sys.exit(1) else: selected_address_books.append(Config().get_address_book(name)) # search parameter # may either contain one search term for a standard search or two terms, devided by a "," to # search for two contacts to merge them search_terms = args.search.split(",") if len(search_terms) == 1: search_terms.append("") # sort criteria if args.sort not in ["alphabetical", "addressbook"]: print("Unsupported sort criteria. Possible values: alphabetical, addressbook") sys.exit(1) # create a list of all found vcard objects vcard_list = get_contact_list_by_user_selection( selected_address_books, args.sort, args.reverse, search_terms[0], False) # create new contact if args.action == "new": if len(selected_address_books) != 1: if args.addressbook == "": print("Error: You must specify an address book for the new contact\nPossible values are: %s" \ % ', '.join([ str(book) for book in Config().get_all_address_books() ])) else: print("Please enter only one address book name") sys.exit(1) # if there is some data in stdin if not sys.stdin.isatty(): # create new contact from stdin new_contact = CarddavObject.from_user_input( selected_address_books[0], sys.stdin.read()) print(new_contact.print_vcard()) new_contact.write_to_file() else: # open editor create_new_contact(selected_address_books[0]) # add email address to contact or create a new one if necessary if args.action == "add-email": # get name and email address email_address = "" name = "" for line in sys.stdin: if line.startswith("From:"): try: name = line[6:line.index("<")-1] email_address = line[line.index("<")+1:line.index(">")] except ValueError as e: email_address = line[6:].strip() break # reopen stdin to get user input sys.stdin = open('/dev/tty') print("Khard: Add email address to contact") if not email_address: print("Found no email address") sys.exit(1) print("Email address: %s" % email_address) if not name: name = raw_input("Contact's name: ") else: # remove quotes from name string, otherwise decoding fails name = name.replace("\"", "") # fix encoding of senders name name, encoding = decode_header(name)[0] if encoding: name = name.decode(encoding).encode("utf-8").replace("\"", "") # query user input. user_input = raw_input("Contact's name [%s]: " % name) # if empty, use the extracted name from above name = user_input or name # search for an existing contact selected_vcard = choose_vcard_from_list( get_contact_list_by_user_selection( selected_address_books, args.sort, args.reverse, name, True)) if selected_vcard is None: # create new contact while True: input_string = raw_input("Contact %s does not exist. Do you want to create it (y/n)? " % name) if input_string.lower() in ["", "n", "q"]: print("Canceled") sys.exit(0) if input_string.lower() == "y": break # ask for address book, in which to create the new contact print("Available address books: %s" \ % ', '.join([ str(book) for book in Config().get_all_address_books() ])) while True: book_name = raw_input("Address book [%s]: " % selected_address_books[0].get_name()) \ or selected_address_books[0].get_name() if Config().get_address_book(book_name) is not None: break # ask for name and organisation of new contact while True: first_name = raw_input("First name: ") last_name = raw_input("Last name: ") organization = raw_input("Organization: ") if not first_name and not last_name and not organization: print("Error: All fields are empty.") else: break selected_vcard = CarddavObject.new_contact(Config().get_address_book(book_name)) selected_vcard.set_name_and_organisation("", first_name, last_name, organization, "") # check if the contact already contains the email address for email_entry in selected_vcard.get_email_addresses(): if email_entry['value'] == email_address: print("The contact %s already contains the email address %s" % (selected_vcard, email_address)) sys.exit(0) # ask for confirmation again while True: input_string = raw_input("Do you want to add the email address %s to the contact %s (y/n)? " \ % (email_address, selected_vcard.get_full_name())) if input_string.lower() in ["", "n", "q"]: print("Canceled") sys.exit(0) if input_string.lower() == "y": break # ask for the email label print("\nAdding email address %s to contact %s" % (email_address, selected_vcard)) label = raw_input("email label [home]: ") or "home" # add email address to vcard object selected_vcard.add_email_address(label, email_address) # save to disk selected_vcard.write_to_file(overwrite=True) print("Done.\n\n%s" % selected_vcard.print_vcard()) # print phone application friendly contacts table if args.action == "phone": all_phone_numbers_list = [] matching_phone_number_list = [] regexp = re.compile(search_terms[0].replace("*", ".*").replace(" ", ".*"), re.IGNORECASE) for vcard in vcard_list: for tel_entry in vcard.get_phone_numbers(): phone_number_line = "%s\t%s\t%s" \ % (tel_entry['value'], vcard.get_full_name(), tel_entry['type']) if len(re.sub("\D", "", search_terms[0])) >= 3: # the user likely searches for a phone number cause the search string contains # at least three digits # so we remove all non-digit chars from the phone number field and match against that if regexp.search(re.sub("\D", "", tel_entry['value'])) != None: matching_phone_number_list.append(phone_number_line) else: # the user doesn't search for a phone number so we can perform a standard search # without removing all non-digit chars from the phone number string if regexp.search(phone_number_line) != None: matching_phone_number_list.append(phone_number_line) # collect all phone numbers in a different list as fallback all_phone_numbers_list.append(phone_number_line) if len(matching_phone_number_list) > 0: print('\n'.join(matching_phone_number_list)) elif len(all_phone_numbers_list) > 0: print('\n'.join(all_phone_numbers_list)) else: sys.exit(1) # print mail client friendly contacts table # compatible to mutt and alot # output format: # single line of text # email_address\tname\ttype # email_address\tname\ttype # [...] if args.action == "email": matching_email_address_list = [] all_email_address_list = [] regexp = re.compile(search_terms[0].replace("*", ".*").replace(" ", ".*"), re.IGNORECASE) for vcard in vcard_list: for email_entry in vcard.get_email_addresses(): email_address_line = "%s\t%s\t%s" \ % (email_entry['value'], vcard.get_full_name(), email_entry['type']) if regexp.search(email_address_line) != None: matching_email_address_list.append(email_address_line) # collect all email addresses in a different list as fallback all_email_address_list.append(email_address_line) print("searching for '%s' ..." % search_terms[0]) if len(matching_email_address_list) > 0: print('\n'.join(matching_email_address_list)) elif len(all_email_address_list) > 0: print('\n'.join(all_email_address_list)) else: sys.exit(1) # print user friendly contacts table if args.action == "list": if len(vcard_list) == 0: print("Found no contacts") sys.exit(1) list_contacts(vcard_list) # show source or details, modify or remove contact if args.action in ["details", "modify", "remove", "source"]: selected_vcard = choose_vcard_from_list(vcard_list) if selected_vcard is None: print("Found no contact") sys.exit(1) if args.action == "details": print selected_vcard.print_vcard() elif args.action == "modify": modify_existing_contact(selected_vcard) elif args.action == "remove": while True: input_string = raw_input("Deleting contact %s from address book %s. Are you sure? (y/n): " \ % (selected_vcard.get_full_name(), selected_vcard.get_address_book().get_name())) if input_string.lower() in ["", "n", "q"]: print("Canceled") sys.exit(0) if input_string.lower() == "y": break selected_vcard.delete_vcard_file() print("Contact deleted successfully") elif args.action == "source": child = subprocess.Popen([Config().get_editor(), selected_vcard.get_filename()]) streamdata = child.communicate()[0] # merge contacts if args.action == "merge": # get the source vcard, from which to merge source_vcard = choose_vcard_from_list(vcard_list) if source_vcard is None: print("Found no source contact for merging") sys.exit(1) # get the target vcard, into which to merge print("Merge from %s from address book %s\n\nNow choose the contact into which to merge:" \ % (source_vcard.get_full_name(), source_vcard.get_address_book().get_name())) target_vcard = choose_vcard_from_list( get_contact_list_by_user_selection( selected_address_books, args.sort, args.reverse, search_terms[1], False)) if target_vcard is None: print("Found no target contact for merging") sys.exit(1) # merging if source_vcard == target_vcard: print("The selected contacts are already identical") else: merge_existing_contacts(source_vcard, target_vcard, True) # copy or move contact if args.action in ["copy", "move"]: # get the source vcard, which to copy or move source_vcard = choose_vcard_from_list(vcard_list) if source_vcard is None: print("Found no contact") sys.exit(1) # get target address book from search query if provided available_address_books = [ book for book in Config().get_all_address_books() if book != source_vcard.get_address_book() ] target_address_book = None if search_terms[1] != "": target_address_book = Config().get_address_book(search_terms[1]) if target_address_book == None: print("The given target address book %s does not exist\n" % search_terms[1]) elif target_address_book not in available_address_books: print("The contact %s is already in the address book %s" \ % (source_vcard.get_full_name(), target_address_book.get_name())) sys.exit(1) # otherwise query the target address book name from user if target_address_book == None: print("%s contact %s from address book %s\n\nAvailable address books:\n %s" % (args.action.title(), source_vcard.get_full_name(), source_vcard.get_address_book().get_name(), '\n '.join([ str(book) for book in available_address_books ]))) while target_address_book is None: input_string = raw_input("Into address book: ") if input_string == "": print("Canceled") sys.exit(0) if Config().get_address_book(input_string) in available_address_books: print("") target_address_book = Config().get_address_book(input_string) # check if a contact already exists in the target address book target_vcard = choose_vcard_from_list( get_contact_list_by_user_selection( [target_address_book], args.sort, args.reverse, source_vcard.get_full_name(), True)) # if the target contact doesn't exist, move or copy the source contact into the target # address book without further questions if target_vcard is None: copy_contact(source_vcard, target_address_book, args.action == "move") else: if source_vcard == target_vcard: # source and target contact are identical if args.action == "move": copy_contact(source_vcard, target_address_book, True) else: print("The selected contacts are already identical") else: # source and target contacts are different # either overwrite the target one or merge into target contact print("The address book %s already contains the contact %s\n\n" \ "Source\n\n%s\n\nTarget\n\n%s\n\n" \ "Possible actions:\n" \ " o: Overwrite target contact\n m: merge from source into target contact\n q: quit" \ % (target_vcard.get_address_book().get_name(), source_vcard.get_full_name(), source_vcard.print_vcard(), target_vcard.print_vcard())) while True: input_string = raw_input("Your choice: ") if input_string.lower() == "o": copy_contact(source_vcard, target_address_book, args.action == "move") break if input_string.lower() == "m": merge_existing_contacts(source_vcard, target_vcard, args.action == "move") break if input_string.lower() in ["", "q"]: print("Canceled") break
def merge_existing_contacts(source_contact, target_contact, delete_source_contact): # create temp files for each vcard # source vcard source_tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) source_temp_file_name = source_tf.name source_tf.write("# merge from %s\n%s" \ % (source_contact.get_full_name(), helpers.get_existing_contact_template(source_contact))) source_tf.close() # target vcard target_tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) target_temp_file_name = target_tf.name target_tf.write("# merge into %s\n%s" \ % (target_contact.get_full_name(), helpers.get_existing_contact_template(target_contact))) target_tf.close() # start editor to edit contact template child = subprocess.Popen([ Config().get_merge_editor(), source_temp_file_name, target_temp_file_name ]) streamdata = child.communicate()[0] # template of source vcard is not required anymore os.remove(source_temp_file_name) # instead we are interested in the target template contents target_tf = open(target_temp_file_name, "r") merged_contact = CarddavObject.from_existing_contact_with_new_user_input( target_contact, target_tf.read()) target_tf.close() os.remove(target_temp_file_name) # compare them if target_contact == merged_contact: print("Merge unsuccessfull: Target contact was not modified") return while True: if delete_source_contact: input_string = raw_input( "Merge contact %s from address book %s into contact %s from address book %s\n\n" \ "To be removed\n\n%s\n\nMerged\n\n%s\n\nAre you sure? (y/n): " \ % (source_contact.get_full_name(), source_contact.get_address_book().get_name(), merged_contact.get_full_name(), merged_contact.get_address_book().get_name(), source_contact.print_vcard(), merged_contact.print_vcard())) else: input_string = raw_input( "Merge contact %s from address book %s into contact %s from address book %s\n\n" \ "Keep unchanged\n\n%s\n\nMerged:\n\n%s\n\nAre you sure? (y/n): " \ % (source_contact.get_full_name(), source_contact.get_address_book().get_name(), merged_contact.get_full_name(), merged_contact.get_address_book().get_name(), source_contact.print_vcard(), merged_contact.print_vcard())) if input_string.lower() in ["", "n", "q"]: print("Canceled") return if input_string.lower() == "y": break # save merged_contact to disk and delete source contact merged_contact.write_to_file(overwrite=True) if delete_source_contact: source_contact.delete_vcard_file() print("Merge successful\n\n%s" % merged_contact.print_vcard())
def main(): # create the args parser parser = argparse.ArgumentParser( description="Khard is a carddav address book for the console", formatter_class=argparse.RawTextHelpFormatter) parser.add_argument( "-a", "--addressbook", default="", help="Specify address book names as comma separated list") parser.add_argument("-r", "--reverse", action="store_true", help="Sort contacts in reverse order") parser.add_argument("-s", "--search", default="", help="Search in all contact data fields\n" \ " default: -s \"contact\"\n" \ " merge: -s \"source contact,target contact\"\n" \ " copy/move: -s \"source contact,target address book\"") parser.add_argument( "-t", "--sort", default="alphabetical", help="Sort contacts list. Possible values: alphabetical, addressbook") parser.add_argument("-v", "--version", action="store_true", help="Get current program version") parser.add_argument("action", nargs="?", default="", help="Possible actions:\n" \ " list, details, source, mutt, alot, phone,\n" \ " new, add-email, merge, modify, copy, move and remove") args = parser.parse_args() # version if args.version == True: print("Khard version %s" % khard_version) sys.exit(0) # validate value for action if args.action == "": args.action = Config().get_default_action() if args.action not in Config().get_list_of_actions(): print("Unsupported action. Possible values are: %s" % ', '.join(Config().get_list_of_actions())) sys.exit(1) # load address books which are defined in the configuration file selected_address_books = [] if args.addressbook == "": selected_address_books = Config().get_all_address_books() else: for name in args.addressbook.split(","): if Config().get_address_book(name) is None: print("Error: The entered address book \"%s\" does not exist.\nPossible values are: %s" \ % (name, ', '.join([ str(book) for book in Config().get_all_address_books() ]))) sys.exit(1) else: selected_address_books.append(Config().get_address_book(name)) # search parameter # may either contain one search term for a standard search or two terms, devided by a "," to # search for two contacts to merge them search_terms = args.search.split(",") if len(search_terms) == 1: search_terms.append("") # sort criteria if args.sort not in ["alphabetical", "addressbook"]: print( "Unsupported sort criteria. Possible values: alphabetical, addressbook" ) sys.exit(1) # create a list of all found vcard objects vcard_list = get_contact_list_by_user_selection(selected_address_books, args.sort, args.reverse, search_terms[0], False) # create new contact if args.action == "new": if len(selected_address_books) != 1: if args.addressbook == "": print("Error: You must specify an address book for the new contact\nPossible values are: %s" \ % ', '.join([ str(book) for book in Config().get_all_address_books() ])) else: print("Please enter only one address book name") sys.exit(1) create_new_contact(selected_address_books[0]) # add email address to contact or create a new one if necessary if args.action == "add-email": # get name and email address email_address = "" name = "" for line in sys.stdin: if line.startswith("From:"): try: name = line[6:line.index("<") - 1] email_address = line[line.index("<") + 1:line.index(">")] except ValueError as e: email_address = line[6:].strip() break # reopen stdin to get user input sys.stdin = open('/dev/tty') print("Khard: Add email address to contact") if not email_address: print("Found no email address") sys.exit(1) print("Email address: %s" % email_address) if not name: name = raw_input("Contact's name: ") else: # remove quotes from name string, otherwise decoding fails name = name.replace("\"", "") # fix encoding of senders name name, encoding = decode_header(name)[0] if encoding: name = name.decode(encoding).encode("utf-8").replace("\"", "") # query user input. user_input = raw_input("Contact's name [%s]: " % name) # if empty, use the extracted name from above name = user_input or name # search for an existing contact selected_vcard = choose_vcard_from_list( get_contact_list_by_user_selection(selected_address_books, args.sort, args.reverse, name, True)) if selected_vcard is None: # create new contact while True: input_string = raw_input( "Contact %s does not exist. Do you want to create it (y/n)? " % name) if input_string.lower() in ["", "n", "q"]: print("Canceled") sys.exit(0) if input_string.lower() == "y": break # ask for address book, in which to create the new contact print("Available address books: %s" \ % ', '.join([ str(book) for book in Config().get_all_address_books() ])) while True: book_name = raw_input("Address book [%s]: " % selected_address_books[0].get_name()) \ or selected_address_books[0].get_name() if Config().get_address_book(book_name) is not None: break # ask for name and organisation of new contact while True: first_name = raw_input("First name: ") last_name = raw_input("Last name: ") organization = raw_input("Organization: ") if not first_name and not last_name and not organization: print("Error: All fields are empty.") else: break selected_vcard = CarddavObject.new_contact( Config().get_address_book(book_name)) selected_vcard.set_name_and_organisation( first_name.decode("utf-8"), last_name.decode("utf-8"), organization.decode("utf-8")) # check if the contact already contains the email address for email_entry in selected_vcard.get_email_addresses(): if email_entry['value'] == email_address: print("The contact %s already contains the email address %s" % (selected_vcard, email_address)) sys.exit(0) # ask for confirmation again while True: input_string = raw_input("Do you want to add the email address %s to the contact %s (y/n)? " \ % (email_address, selected_vcard.get_full_name())) if input_string.lower() in ["", "n", "q"]: print("Canceled") sys.exit(0) if input_string.lower() == "y": break # ask for the email label print("\nAdding email address %s to contact %s" % (email_address, selected_vcard)) label = raw_input("email label [home]: ") or "home" # add email address to vcard object selected_vcard.add_email_address(label.decode("utf-8"), email_address.decode("utf-8")) # save to disk selected_vcard.write_to_file(overwrite=True) print("Done.\n\n%s" % selected_vcard.print_vcard()) # print phone application friendly contacts table if args.action == "phone": phone_list = [] regexp = re.compile(search_terms[0].replace(" ", ".*"), re.IGNORECASE) for vcard in vcard_list: for tel_entry in vcard.get_phone_numbers(): phone_number_line = "%s\t%s\t%s" \ % (tel_entry['value'], vcard.get_full_name(), tel_entry['type']) if len(re.sub("\D", "", search_terms[0])) >= 3: # the user likely searches for a phone number cause the search string contains # at least three digits # so we remove all non-digit chars from the phone number field and match against that if regexp.search(re.sub("\D", "", tel_entry['value'])) != None: phone_list.append(phone_number_line) else: # the user doesn't search for a phone number so we can perform a standard search # without removing all non-digit chars from the phone number string if regexp.search(phone_number_line) != None: phone_list.append(phone_number_line) print('\n'.join(phone_list)) if len(phone_list) == 0: sys.exit(1) # print mutt friendly contacts table if args.action == "mutt": address_list = ["searching for '%s' ..." % search_terms[0]] regexp = re.compile(search_terms[0].replace(" ", ".*"), re.IGNORECASE) for vcard in vcard_list: for email_entry in vcard.get_email_addresses(): email_address_line = "%s\t%s\t%s" \ % (email_entry['value'], vcard.get_full_name(), email_entry['type']) if regexp.search(email_address_line) != None: address_list.append(email_address_line) print('\n'.join(address_list)) if len(address_list) <= 1: sys.exit(1) # print alot friendly contacts table if args.action == "alot": address_list = [] regexp = re.compile(search_terms[0].replace(" ", ".*"), re.IGNORECASE) for vcard in vcard_list: for email_entry in vcard.get_email_addresses(): email_address_line = "\"%s %s\" <%s>" \ % (vcard.get_full_name(), email_entry['type'], email_entry['value']) if regexp.search(email_address_line) != None: address_list.append(email_address_line) print('\n'.join(address_list)) if len(address_list) == 0: sys.exit(1) # print user friendly contacts table if args.action == "list": if len(vcard_list) == 0: print("Found no contacts") sys.exit(1) list_contacts(vcard_list) # show source or details, modify or remove contact if args.action in ["details", "modify", "remove", "source"]: selected_vcard = choose_vcard_from_list(vcard_list) if selected_vcard is None: print("Found no contact") sys.exit(1) if args.action == "details": print selected_vcard.print_vcard() elif args.action == "modify": modify_existing_contact(selected_vcard) elif args.action == "remove": while True: input_string = raw_input("Deleting contact %s from address book %s. Are you sure? (y/n): " \ % (selected_vcard.get_full_name(), selected_vcard.get_address_book().get_name())) if input_string.lower() in ["", "n", "q"]: print("Canceled") sys.exit(0) if input_string.lower() == "y": break selected_vcard.delete_vcard_file() print("Contact deleted successfully") elif args.action == "source": child = subprocess.Popen( [Config().get_editor(), selected_vcard.get_filename()]) streamdata = child.communicate()[0] # merge contacts if args.action == "merge": # get the source vcard, from which to merge source_vcard = choose_vcard_from_list(vcard_list) if source_vcard is None: print("Found no source contact for merging") sys.exit(1) # get the target vcard, into which to merge print("Merge from %s from address book %s\n\nNow choose the contact into which to merge:" \ % (source_vcard.get_full_name(), source_vcard.get_address_book().get_name())) target_vcard = choose_vcard_from_list( get_contact_list_by_user_selection(selected_address_books, args.sort, args.reverse, search_terms[1], False)) if target_vcard is None: print("Found no target contact for merging") sys.exit(1) # merging if source_vcard == target_vcard: print("The selected contacts are already identical") else: merge_existing_contacts(source_vcard, target_vcard, True) # copy or move contact if args.action in ["copy", "move"]: # get the source vcard, which to copy or move source_vcard = choose_vcard_from_list(vcard_list) if source_vcard is None: print("Found no contact") sys.exit(1) # get target address book from search query if provided available_address_books = [ book for book in Config().get_all_address_books() if book != source_vcard.get_address_book() ] target_address_book = None if search_terms[1] != "": target_address_book = Config().get_address_book(search_terms[1]) if target_address_book == None: print("The given target address book %s does not exist\n" % search_terms[1]) elif target_address_book not in available_address_books: print("The contact %s is already in the address book %s" \ % (source_vcard.get_full_name(), target_address_book.get_name())) sys.exit(1) # otherwise query the target address book name from user if target_address_book == None: print( "%s contact %s from address book %s\n\nAvailable address books:\n %s" % (args.action.title(), source_vcard.get_full_name(), source_vcard.get_address_book().get_name(), '\n '.join( [str(book) for book in available_address_books]))) while target_address_book is None: input_string = raw_input("Into address book: ") if input_string == "": print("Canceled") sys.exit(0) if Config().get_address_book( input_string) in available_address_books: print("") target_address_book = Config().get_address_book(input_string) # check if a contact already exists in the target address book target_vcard = choose_vcard_from_list( get_contact_list_by_user_selection([target_address_book], args.sort, args.reverse, source_vcard.get_full_name(), True)) # if the target contact doesn't exist, move or copy the source contact into the target # address book without further questions if target_vcard is None: copy_contact(source_vcard, target_address_book, args.action == "move") else: if source_vcard == target_vcard: # source and target contact are identical if args.action == "move": copy_contact(source_vcard, target_address_book, True) else: print("The selected contacts are already identical") else: # source and target contacts are different # either overwrite the target one or merge into target contact print("The address book %s already contains the contact %s\n\n" \ "Source\n\n%s\n\nTarget\n\n%s\n\n" \ "Possible actions:\n" \ " o: Overwrite target contact\n m: merge from source into target contact\n q: quit" \ % (target_vcard.get_address_book().get_name(), source_vcard.get_full_name(), source_vcard.print_vcard(), target_vcard.print_vcard())) while True: input_string = raw_input("Your choice: ") if input_string.lower() == "o": copy_contact(source_vcard, target_address_book, args.action == "move") break if input_string.lower() == "m": merge_existing_contacts(source_vcard, target_vcard, args.action == "move") break if input_string.lower() in ["", "q"]: print("Canceled") break
def merge_existing_contacts(source_contact, target_contact, delete_source_contact): # create temp files for each vcard # source vcard source_tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) source_temp_file_name = source_tf.name source_tf.write("# merge from %s\n%s" \ % (source_contact.get_full_name(), source_contact.get_template())) source_tf.close() # target vcard target_tf = tempfile.NamedTemporaryFile(mode='w+t', delete=False) target_temp_file_name = target_tf.name target_tf.write("# merge into %s\n%s" \ % (target_contact.get_full_name(), target_contact.get_template())) target_tf.close() target_temp_file_creation = helpers.file_modification_date(target_temp_file_name) while True: # start editor to edit contact template child = subprocess.Popen([Config().get_merge_editor(), source_temp_file_name, target_temp_file_name]) streamdata = child.communicate()[0] if target_temp_file_creation == helpers.file_modification_date(target_temp_file_name): merged_contact = None os.remove(source_temp_file_name) os.remove(target_temp_file_name) break # load target template contents target_tf = open(target_temp_file_name, "r") merged_contact_template = target_tf.read() target_tf.close() # try to create contact from user input try: merged_contact = CarddavObject.from_existing_contact_with_new_user_input( target_contact, merged_contact_template) except ValueError as e: print("\n%s\n" % e) while True: input_string = raw_input("Do you want to open the editor again (y/n)? ") if input_string.lower() in ["", "n", "q"]: print("Canceled") os.remove(source_temp_file_name) os.remove(target_temp_file_name) sys.exit(0) if input_string.lower() == "y": break else: os.remove(source_temp_file_name) os.remove(target_temp_file_name) break # compare them if merged_contact is None \ or target_contact == merged_contact: print("Target contact unmodified\n\n%s" % target_contact.print_vcard()) sys.exit(0) while True: if delete_source_contact: input_string = raw_input( "Merge contact %s from address book %s into contact %s from address book %s\n\n" \ "To be removed\n\n%s\n\nMerged\n\n%s\n\nAre you sure? (y/n): " \ % (source_contact.get_full_name(), source_contact.get_address_book().get_name(), merged_contact.get_full_name(), merged_contact.get_address_book().get_name(), source_contact.print_vcard(), merged_contact.print_vcard())) else: input_string = raw_input( "Merge contact %s from address book %s into contact %s from address book %s\n\n" \ "Keep unchanged\n\n%s\n\nMerged:\n\n%s\n\nAre you sure? (y/n): " \ % (source_contact.get_full_name(), source_contact.get_address_book().get_name(), merged_contact.get_full_name(), merged_contact.get_address_book().get_name(), source_contact.print_vcard(), merged_contact.print_vcard())) if input_string.lower() in ["", "n", "q"]: print("Canceled") return if input_string.lower() == "y": break # save merged_contact to disk and delete source contact merged_contact.write_to_file(overwrite=True) if delete_source_contact: source_contact.delete_vcard_file() print("Merge successful\n\n%s" % merged_contact.print_vcard())
def __init__(self): # load config file config_file = os.path.join(os.path.expanduser("~"), ".config", "khard", "khard.conf") if os.path.exists(config_file) == False: print "Config file %s not available" % config_file sys.exit(2) self.config = ConfigObj(config_file, interpolation=False) # general settings if self.config.has_key("general") == False: print "Error in config file\nMissing main section \"[general]\"." sys.exit(2) if self.config['general'].has_key("editor") == False: print "Error in config file\nMissing editor parameter. Example: editor = /usr/bin/vim." sys.exit(2) elif os.path.exists(self.config['general']['editor']) == False: print "Error in config file\nInvalid editor path." sys.exit(2) if self.config['general'].has_key("default_country") == False: print "Error in config file\nMissing default country parameter." sys.exit(2) if self.config['general'].has_key("default_action") == False: print "Error in config file\nMissing default action parameter." sys.exit(2) elif self.config['general']['default_action'] not in [ "list", "details", "new", "modify", "remove" ]: print "Error in config file\nNon existing value for default action parameter. \ Possible values are: list, details, new, modify and remove" sys.exit(2) # load address books if self.config.has_key("addressbooks") == False: print "Error in config file\nMissing main section \"[addressbooks]\"." sys.exit(2) if self.config['addressbooks'].keys().__len__() == 0: print "Error in config file\nNo address book entries available." sys.exit(2) for name in self.config['addressbooks'].keys(): addressbook = self.config['addressbooks'][name] if addressbook.has_key("path") == False: print "Error in config file\nMissing path to the \"%s\" address book." % name sys.exit(2) if addressbook['path'].startswith("~"): addressbook['path'] = addressbook['path'].replace( "~", os.path.expanduser("~")) if os.path.exists(addressbook['path']) == False: print "Error in config file\nThe path %s to the address book %s does not exist." % ( addressbook['path'], name) sys.exit(2) # set address book name addressbook['name'] = name # load all vcard files addressbook['vcards'] = [] for filename in glob.glob( os.path.join(addressbook['path'], "*.vcf")): addressbook['vcards'].append( CarddavObject(addressbook['name'], addressbook['path'], filename))
def __init__(self): self.config = None self.address_book_list = [] # load config file xdg_config_home = os.environ.get("XDG_CONFIG_HOME") or \ os.path.expanduser("~/.config") config_file = os.environ.get("KHARD_CONFIG") or \ os.path.join(xdg_config_home, "khard", "khard.conf") if os.path.exists(config_file) == False: print("Config file %s not available" % config_file) sys.exit(2) self.config = ConfigObj(config_file, interpolation=False) # general settings if self.config.has_key("general") == False: print( "Error in config file\nMissing main section \"[general]\"." ) sys.exit(2) # editor self.config['general']['editor'] = self.config['general'].get("editor") \ or os.environ.get("EDITOR") if self.config['general']['editor'] is None: print("Error in config file\n" \ "Set path to your preferred text editor in khard's config file or the $EDITOR shell variable\n" \ "Example for khard.conf: editor = vim") sys.exit(2) self.config['general']['editor'] = find_executable( os.path.expanduser(self.config['general']['editor'])) if self.config['general']['editor'] is None: print( "Error in config file\nInvalid editor path or executable not found." ) sys.exit(2) # merge editor self.config['general']['merge_editor'] = self.config['general'].get("merge_editor") \ or os.environ.get("MERGE_EDITOR") if self.config['general']['merge_editor'] is None: print("Error in config file\n" \ "Set path to your preferred text merge editor in khard's config file or the $MERGE_EDITOR shell variable\n" \ "Example for khard.conf: merge_editor = vimdiff") sys.exit(2) self.config['general']['merge_editor'] = find_executable( os.path.expanduser(self.config['general']['merge_editor'])) if self.config['general']['merge_editor'] is None: print( "Error in config file\nInvalid merge editor path or executable not found." ) sys.exit(2) # default values for action and nickname settings if self.config['general'].has_key("default_action") == False: print( "Error in config file\nMissing default action parameter.") sys.exit(2) elif self.config['general'][ 'default_action'] not in self.get_list_of_actions(): print("Error in config file\n" \ "Non existing value for default action parameter\n" \ "Possible values are: %s" % ', '.join(self.get_list_of_actions())) sys.exit(2) if self.config['general'].has_key("show_nicknames") == False: self.config['general']['show_nicknames'] = False elif self.config['general']['show_nicknames'] == "yes": self.config['general']['show_nicknames'] = True elif self.config['general']['show_nicknames'] == "no": self.config['general']['show_nicknames'] = False else: print( "Error in config file\nshow_nicknames parameter must be yes or no." ) sys.exit(2) # load address books error_counter = 0 if self.config.has_key("addressbooks") == False: print( "Error in config file\nMissing main section \"[addressbooks]\"." ) sys.exit(2) if len(self.config['addressbooks'].keys()) == 0: print( "Error in config file\nNo address book entries available.") sys.exit(2) for name in self.config['addressbooks'].keys(): # create address book object try: address_book = AddressBook( name, self.config['addressbooks'][name]['path']) except KeyError as e: print( "Error in config file\nMissing path to the \"%s\" address book." % name) sys.exit(2) except IOError as e: print("Error in config file\n%s" % e) sys.exit(2) # load all vcard files for filename in glob.glob( os.path.join(address_book.get_path(), "*.vcf")): try: address_book.add_contact( CarddavObject.from_file(address_book, filename)) except IOError as e: print("Error: Could not open file %s\n%s" % (filename, e)) error_counter += 1 except vobject.base.ParseError as e: print("Error: Could not parse file %s\n%s" % (filename, e)) error_counter += 1 # add address book to list self.address_book_list.append(address_book) # check if one or more contacts could not be parsed if error_counter > 0: if error_counter == 1: print("\n1 vcard file could not be parsed") elif error_counter > 1: print("\n%d vcard files could not be parsed" % error_counter) sys.exit(2)
def __init__(self): self.config = None self.address_book_list = [] self.uid_dict = {} # load config file xdg_config_home = os.environ.get("XDG_CONFIG_HOME") or \ os.path.expanduser("~/.config") config_file = os.environ.get("KHARD_CONFIG") or \ os.path.join(xdg_config_home, "khard", "khard.conf") if os.path.exists(config_file) == False: print("Config file %s not available" % config_file) sys.exit(2) self.config = ConfigObj(config_file, interpolation=False) # general settings if self.config.has_key("general") == False: print("Error in config file\nMissing main section \"[general]\".") sys.exit(2) # editor self.config['general']['editor'] = self.config['general'].get("editor") \ or os.environ.get("EDITOR") if self.config['general']['editor'] is None: print("Error in config file\n" \ "Set path to your preferred text editor in khard's config file or the $EDITOR shell variable\n" \ "Example for khard.conf: editor = vim") sys.exit(2) self.config['general']['editor'] = find_executable( os.path.expanduser(self.config['general']['editor'])) if self.config['general']['editor'] is None: print("Error in config file\nInvalid editor path or executable not found.") sys.exit(2) # merge editor self.config['general']['merge_editor'] = self.config['general'].get("merge_editor") \ or os.environ.get("MERGE_EDITOR") if self.config['general']['merge_editor'] is None: print("Error in config file\n" \ "Set path to your preferred text merge editor in khard's config file or the $MERGE_EDITOR shell variable\n" \ "Example for khard.conf: merge_editor = vimdiff") sys.exit(2) self.config['general']['merge_editor'] = find_executable( os.path.expanduser(self.config['general']['merge_editor'])) if self.config['general']['merge_editor'] is None: print("Error in config file\nInvalid merge editor path or executable not found.") sys.exit(2) # default action if self.config['general'].has_key("default_action") == False: print("Error in config file\nMissing default action parameter.") sys.exit(2) elif self.config['general']['default_action'] not in self.get_list_of_actions(): print("Error in config file\n" \ "Invalid value for default_action parameter\n" \ "Possible values: %s" % ', '.join(self.get_list_of_actions())) sys.exit(2) # contact table settings if self.config.has_key("contact table") == False: self.config['contact table'] = {} # sort contacts table by first or last name if self.config['contact table'].has_key("sort") == False: self.config['contact table']['sort'] = "first_name" elif self.config['contact table']['sort'] not in ["first_name", "last_name"]: print("Error in config file\n" \ "Invalid value for sort parameter\n" \ "Possible values: first_name, last_name") sys.exit(2) # reverse contact table if self.config['contact table'].has_key("reverse") == False: self.config['contact table']['reverse'] = False elif self.config['contact table']['reverse'] == "yes": self.config['contact table']['reverse'] = True elif self.config['contact table']['reverse'] == "no": self.config['contact table']['reverse'] = False else: print("Error in config file\n" \ "Invalid value for reverse parameter\n" \ "Possible values: yes, no") sys.exit(2) # group contact table by address book if self.config['contact table'].has_key("group_by_addressbook") == False: self.config['contact table']['group_by_addressbook'] = False elif self.config['contact table']['group_by_addressbook'] == "yes": self.config['contact table']['group_by_addressbook'] = True elif self.config['contact table']['group_by_addressbook'] == "no": self.config['contact table']['group_by_addressbook'] = False else: print("Error in config file\n" \ "Invalid value for group_by_addressbook parameter\n" \ "Possible values: yes, no") sys.exit(2) # nickname if self.config['contact table'].has_key("show_nicknames") == False: self.config['contact table']['show_nicknames'] = False elif self.config['contact table']['show_nicknames'] == "yes": self.config['contact table']['show_nicknames'] = True elif self.config['contact table']['show_nicknames'] == "no": self.config['contact table']['show_nicknames'] = False else: print("Error in config file\n" \ "Invalid value for show_nicknames parameter\n" \ "Possible values: yes, no") sys.exit(2) # show uids if self.config['contact table'].has_key("show_uids") == False: self.config['contact table']['show_uids'] = True elif self.config['contact table']['show_uids'] == "yes": self.config['contact table']['show_uids'] = True elif self.config['contact table']['show_uids'] == "no": self.config['contact table']['show_uids'] = False else: print("Error in config file\n" \ "Invalid value for show_uids parameter\n" \ "Possible values: yes, no") sys.exit(2) # load address books and contacts error_counter = 0 number_of_contacts = 0 if self.config.has_key("addressbooks") == False: print("Error in config file\nMissing main section \"[addressbooks]\".") sys.exit(2) if len(self.config['addressbooks'].keys()) == 0: print("Error in config file\nNo address book entries available.") sys.exit(2) for name in self.config['addressbooks'].keys(): # create address book object try: address_book = AddressBook(name, self.config['addressbooks'][name]['path']) except KeyError as e: print("Error in config file\nMissing path to the \"%s\" address book." % name) sys.exit(2) except IOError as e: print("Error in config file\n%s" % e) sys.exit(2) # load all vcard files for filename in glob.glob(os.path.join(address_book.get_path(), "*.vcf")): try: address_book.add_contact( CarddavObject.from_file(address_book, filename)) number_of_contacts += 1 except IOError as e: print("Error: Could not open file %s\n%s" % (filename, e)) error_counter += 1 except vobject.base.ParseError as e: print("Error: Could not parse file %s\n%s" % (filename, e)) error_counter += 1 # add address book to list self.address_book_list.append(address_book) # check if one or more contacts could not be parsed if error_counter > 0: print("\n%d of %d vcard files could not be parsed" % (error_counter, number_of_contacts)) sys.exit(2) # check uniqueness of vcard uids and create short uid dictionary # that can be disabled with the show_uids option in the config file, if desired if self.config['contact table']['show_uids']: # check, if multiple contacts have the same uid flat_contact_list = [] for address_book in self.address_book_list: for contact in address_book.get_contact_list(): uid = contact.get_uid() if bool(uid): matching_contact = self.uid_dict.get(uid) if matching_contact is None: self.uid_dict[uid] = contact else: print("The contact %s from address book %s" \ " and the contact %s from address book %s have the same uid %s" \ % (matching_contact.get_full_name(), matching_contact.get_address_book().get_name(), contact.get_full_name(), contact.get_address_book().get_name(), contact.get_uid()) ) sys.exit(2) # add to flat contact list flat_contact_list.append(contact) # now we can be sure, that all uid's are unique but we don't want to enter # the whole uid, if we choose a contact by the -u / --uid option # so clear previously filled uid_dict and recreate with the shortest possible uid, so # that it's still unique and easier to enter # with around 100 contacts that short id should not be longer then two or three characters self.uid_dict.clear() flat_contact_list.sort(key = lambda x: x.get_uid()) if len(flat_contact_list) == 1: current = flat_contact_list[0] self.uid_dict[current.get_uid()[:1]] = current elif len(flat_contact_list) > 1: # first list element current = flat_contact_list[0] next = flat_contact_list[1] same = helpers.compare_uids(current.get_uid(), next.get_uid()) self.uid_dict[current.get_uid()[:same+1]] = current # list elements 1 to len(flat_contact_list)-1 for index in range(1, len(flat_contact_list)-1): prev = flat_contact_list[index-1] current = flat_contact_list[index] next = flat_contact_list[index+1] same = max( helpers.compare_uids(prev.get_uid(), current.get_uid()), helpers.compare_uids(current.get_uid(), next.get_uid())) self.uid_dict[current.get_uid()[:same+1]] = current # last list element prev = flat_contact_list[-2] current = flat_contact_list[-1] same = helpers.compare_uids(prev.get_uid(), current.get_uid()) self.uid_dict[current.get_uid()[:same+1]] = current