This commit is contained in:
Geoffrey Frogeye 2019-11-01 18:34:45 +01:00
parent 90ca29d35c
commit 45058b4272
5 changed files with 162 additions and 45 deletions

View file

@ -29,6 +29,7 @@ import email.message
import email.parser
import html
import logging
import mailcap
import os
import pdb
import re
@ -45,6 +46,7 @@ import progressbar
import xdg.BaseDirectory
MailLocation = typing.NewType('MailLocation', typing.Tuple[str, str, str])
# MessageAction = typing.Callable[[notmuch.Message], None]
class MelEngine:
@ -82,7 +84,7 @@ class MelEngine:
self.accounts[name] = section
def __init__(self, config_path: str) -> None:
self.log = logging.getLogger("Mel")
self.log = logging.getLogger("MelEngine")
self.config = self.load_config(config_path)
@ -93,7 +95,6 @@ class MelEngine:
# All the emails the user is represented as:
self.aliases: typing.Set[str] = set()
# TODO If the user send emails to himself, maybe that wont cut it.
self.mailbox_colors: typing.Dict[str, str] = dict()
self.generate_aliases()
@ -179,25 +180,6 @@ class MelEngine:
assert state in {'cur', 'tmp', 'new'}
return (mailbox, folder, state)
def get_mailbox_color(self, mailbox: str) -> str:
"""
Return the color of the given mailbox in a ready to print
string with ASCII escape codes.
"""
assert self.config
if mailbox not in self.mailbox_colors:
# RGB colors (not supported everywhere)
# color_str = self.config[mailbox]["color"]
# color_str = color_str[1:] if color_str[0] == '#' else color_str
# R = int(color_str[0:2], 16)
# G = int(color_str[2:4], 16)
# B = int(color_str[4:6], 16)
# self.mailbox_colors[mailbox] = f"\x1b[38;2;{R};{G};{B}m"
color_int = int(self.config[mailbox]["color16"])
self.mailbox_colors[mailbox] = f"\x1b[38;5;{color_int}m"
return self.mailbox_colors[mailbox]
@staticmethod
def is_uid(uid: typing.Any) -> bool:
"""
@ -273,7 +255,6 @@ class MelEngine:
*args: typing.Any, show_progress: bool = False,
write: bool = False, close_db: bool = True,
**kwargs: typing.Any) -> int:
# TODO Detail the typing.Callable
"""
Run a function on the messages selected by the given query.
"""
@ -286,11 +267,12 @@ class MelEngine:
elements = query.search_messages()
nb_msgs = query.count_messages()
iterator = progressbar.progressbar(
elements, max_value=nb_msgs) if show_progress else elements
iterator = progressbar.progressbar(elements, max_value=nb_msgs) \
if show_progress and nb_msgs else elements
self.log.info("Executing %s", action)
for msg in iterator:
self.log.debug("On mail %s", msg)
if write:
msg.freeze()
@ -328,12 +310,18 @@ class MelOutput:
return (None, None)
def __init__(self, engine: MelEngine) -> None:
colorama.init()
self.log = logging.getLogger("MelOutput")
self.engine = engine
self.light_background = True
self.is_tty = sys.stdout.isatty()
self.dest_width, self.subject_width = self.compute_line_format()
self.mailbox_colors: typing.Dict[str, str] = dict()
# TODO Allow custom path
self.caps = mailcap.getcaps()
@staticmethod
def format_date(date: datetime.datetime) -> str:
@ -345,6 +333,10 @@ class MelOutput:
now = datetime.datetime.now()
if now - date < datetime.timedelta(days=1):
return date.strftime('%H:%M:%S')
if now - date < datetime.timedelta(days=28):
return date.strftime('%d %H:%M')
if now - date < datetime.timedelta(days=365):
return date.strftime('%m-%d %H')
return date.strftime('%y-%m-%d')
@staticmethod
@ -383,6 +375,26 @@ class MelOutput:
remainder /= 1024.0
return "%.1f %s%s" % (remainder, 'Yi', suffix)
def get_mailbox_color(self, mailbox: str) -> str:
"""
Return the color of the given mailbox in a ready to print
string with ASCII escape codes.
"""
if not self.is_tty:
return ''
if mailbox not in self.mailbox_colors:
# RGB colors (not supported everywhere)
# color_str = self.config[mailbox]["color"]
# color_str = color_str[1:] if color_str[0] == '#' else color_str
# R = int(color_str[0:2], 16)
# G = int(color_str[2:4], 16)
# B = int(color_str[4:6], 16)
# self.mailbox_colors[mailbox] = f"\x1b[38;2;{R};{G};{B}m"
color_int = int(self.engine.config[mailbox]["color16"])
self.mailbox_colors[mailbox] = f"\x1b[38;5;{color_int}m"
return self.mailbox_colors[mailbox]
def print_msg(self, msg: notmuch.Message) -> None:
"""
Print the given message header on one line.
@ -394,25 +406,33 @@ class MelOutput:
line = ""
tags = set(msg.get_tags())
mailbox, _, _ = self.engine.get_location(msg)
if self.is_tty:
line += self.engine.get_mailbox_color(mailbox)
if 'unread' in tags or 'flagged' in tags:
line += colorama.Style.BRIGHT
# if 'flagged' in tags:
# line += colorama.Style.BRIGHT
# if 'unread' not in tags:
# line += colorama.Style.DIM
line += colorama.Back.LIGHTBLACK_EX if self.light_background \
else colorama.Back.BLACK
self.light_background = not self.light_background
line += self.get_mailbox_color(mailbox)
# UID
uid = None
for tag in tags:
if tag.startswith('tuid'):
uid = tag[4:]
assert uid and MelEngine.is_uid(
uid), "{uid} ({type(UID)}) is not a valid UID."
assert uid, f"No UID for message: {msg}."
assert MelEngine.is_uid(uid), f"{uid} {type(uid)} is not a valid UID."
line += uid
# Date
line += sep
line += sep + colorama.Fore.MAGENTA
date = datetime.datetime.fromtimestamp(msg.get_date())
line += self.format_date(date)
# Icons
line += sep
line += sep + colorama.Fore.RED
def tags2col1(tag1: str, tag2: str,
characters: typing.Tuple[str, str, str, str]) -> None:
@ -437,15 +457,16 @@ class MelOutput:
tags2col1('unread', 'flagged', ('!', 'U', 'F', ' '))
tags2col1('sent', 'replied', ('?', '↑', '↪', ' '))
# Opposed
line += sep + colorama.Fore.BLUE
if 'sent' in tags:
dest = msg.get_header("to")
else:
dest = msg.get_header("from")
line += sep
line += MelOutput.clip_text(self.dest_width, dest)
# Subject
line += sep
line += sep + colorama.Fore.WHITE
subject = msg.get_header("subject")
line += MelOutput.clip_text(self.subject_width, subject)
@ -560,11 +581,41 @@ class MelOutput:
print()
# Show text/plain
# TODO Consider alternative
for part in mail.walk():
if part.is_multipart():
continue
payl = part.get_payload(decode=True)
assert isinstance(payl, bytes)
if part.get_content_type() == "text/plain":
payl = part.get_payload(decode=True)
assert isinstance(payl, bytes)
print(payl.decode())
else:
# TODO Use nametemplate from mailcap
temp_file = '/tmp/melcap.html' # TODO Real temporary file
# TODO FIFO if possible
with open(temp_file, 'wb') as temp_filedesc:
temp_filedesc.write(payl)
command, _ = mailcap.findmatch(
self.caps, part.get_content_type(), key='view', filename=temp_file)
if command:
os.system(command)
def print_dir_list(self) -> None:
"""
Print a colored directory list.
Every line is easilly copiable.
"""
for arb in self.engine.list_folders():
line = colorama.Fore.LIGHTBLACK_EX + "'"
line += self.get_mailbox_color(arb[0])
line += arb[0].replace("'", "\\'")
line += colorama.Fore.LIGHTBLACK_EX
for inter in arb[1:-1]:
line += '/' + inter.replace("'", "\\'")
line += '/' + colorama.Fore.WHITE + arb[-1].replace("'", "\\'")
line += colorama.Fore.LIGHTBLACK_EX + "'"
line += colorama.Style.RESET_ALL
print(line)
class MelCLI():
@ -606,11 +657,12 @@ class MelCLI():
for message in messages:
query_str = f'tag:tuid{message}'
nb_msgs = self.engine.apply_msgs(
query_str, action, write=write, close_db=False)
nb_msgs = self.engine.apply_msgs(query_str, action,
write=write, close_db=False)
if nb_msgs < 1:
self.log.error(
"Couldn't execute function for message %s", message)
self.engine.close_database()
def operation_default(self) -> None:
"""
@ -663,7 +715,7 @@ class MelCLI():
mbsync_config_file = os.path.expanduser(
"~/.config/mbsyncrc") # TODO Better
cmd = ["mbsync", "--config", mbsync_config_file, "--all"]
subprocess.run(cmd, check=True)
subprocess.run(cmd, check=False)
# Index new mails
self.engine.notmuch_new()
@ -675,12 +727,17 @@ class MelCLI():
self.engine.apply_msgs('tag:unprocessed', self.engine.retag_msg,
show_progress=True, write=True)
def operation_list(self) -> None:
"""
List operation: Print all folders.
"""
self.output.print_dir_list()
def operation_debug(self) -> None:
"""
DEBUG
"""
from pprint import pprint
pprint(self.engine.list_folders())
print("UwU")
def operation_retag(self) -> None:
"""
@ -717,6 +774,10 @@ class MelCLI():
# list folder [--recurse]
# List actions
parser_list = subparsers.add_parser(
"list", help="List all folders")
# parser_list.add_argument('message', nargs='*', help="Messages")
parser_list.set_defaults(operation=self.operation_list)
# flag msg...
parser_flag = subparsers.add_parser(
@ -811,10 +872,12 @@ class MelCLI():
if __name__ == "__main__":
colorama.init()
try:
if not os.environ.get("MEL_DEBUG"):
CLI = MelCLI()
except:
EXTYPE, VALUE, TB = sys.exc_info()
traceback.print_exc()
pdb.post_mortem(TB)
else:
try:
CLI = MelCLI()
except:
EXTYPE, VALUE, TB = sys.exc_info()
traceback.print_exc()
pdb.post_mortem(TB)

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""
Small script to convert music files in the form:
$(tracknumber) - $(title).$(ext)
to the form
$(tracknumber) $(title).$(ext)
(note the absence of dash)
"""
import os
import re
def main() -> None:
"""
Function that executes the script.
"""
for root, _, files in os.walk('.'):
for filename in files:
match = re.match(r'^(\d+) - (.+)$', filename)
if not match:
continue
new_filename = f"{match[1]} {match[2]}"
old_path = os.path.join(root, filename)
new_path = os.path.join(root, new_filename)
print(old_path, '->', new_path)
os.rename(old_path, new_path)
if __name__ == '__main__':
main()

View file

@ -146,6 +146,23 @@ do
done <<< "$(find "$dir/" -type f -iname "*.png")"
# FLAC (requires reflac)
while read music
do
if [ -z "$music" ]; then continue; fi
echo Processing $music
temp_dir=$(mktemp --directory)
temp="$temp_dir/to_optimize.flac"
cp "$music" "$temp"
reflac --best "$temp_dir"
echo "→ Optimize done"
replace "$temp" "$music"
rm -rf "$temp_dir"
done <<< "$(find "$dir/" -type f -iname "*.flac")"
# # SVG (requires scour)
# while read image
# do

View file

@ -3,6 +3,9 @@
# Normalisation is done at the default of each program,
# which is usually -89.0 dB
# TODO The simplifications/fixes I've done makes it consider
# multi-discs albums as multiple albums
import logging
import os
import sys

View file

@ -152,6 +152,7 @@ then
i notmuch # Index mail
i neomutt || i mutt # CLI mail client
i lynx # CLI web browser (for HTML mail)
i tiv # CLI image viewer
if $INSTALL_GUI
then
i thunderbird # GUI mail client (just in case)
@ -175,6 +176,7 @@ then
i duperemove # Find and merge dupplicates on BTRFS partitions
i optipng # Optimize PNG files
i jpegtran libjpeg-turbo # Compress JPEG files
i reflac # Recompress FLAC files
fi