commit 910e029c42f64fb39174d1f3a87557d36a8dee18
parent f5950c1cb6fd7e60e7b192a89a7069cf21d2c83c
Author: lash <dev@holbrook.no>
Date: Mon, 2 Jan 2023 09:22:34 +0000
Add separate add attachment feature
Diffstat:
7 files changed, 253 insertions(+), 16 deletions(-)
diff --git a/VERSION b/VERSION
@@ -1 +0,0 @@
-0.5.1
diff --git a/feedwarrior/adapters/fileadapter.py b/feedwarrior/adapters/fileadapter.py
@@ -34,34 +34,67 @@ class fileadapter:
self.feeds_uuid = uu
- def get(self, uu, **kwargs):
+ def get_raw_fp(self, fp):
+ return open(fp, 'r')
+
+
+ def get_gz_fp(self, fp):
+ fp += '.gz'
+ logg.debug('uncompressing {}'.format(fp))
+ return gzip.open(fp, 'rb')
+ #return open(fp, 'r')
+
+
+ def get_with_type(self, uu, **kwargs):
entry_path = os.path.join(self.src, 'entries', str(uu))
f = None
- if entry_path[len(entry_path)-3:] == '.gz':
- logg.debug('uncompressing {}'.format(entry_path))
- f = gzip.open(entry_path, 'rb')
- else:
- f = open(entry_path, 'r')
+ typ = 'plain'
+ if entry_path[-3:] == '.gz':
+ entry_path = entry_path[:-3]
+ logg.debug('etnry {}'.format(entry_path))
+ try:
+ f = self.get_raw_fp(entry_path)
+ except FileNotFoundError:
+ f = self.get_gz_fp(entry_path)
+ typ = 'gzip'
c = f.read()
f.close()
- return c
+ return (c, typ,)
+
+
+ def get(self, uu, **kwargs):
+ r = self.get_with_type(uu, **kwargs)
+ return r[0]
+
+# entry_path = os.path.join(self.src, 'entries', str(uu))
+# f = None
+# try:
+# f = self.get_raw_fp(entry_path)
+# except FileNotFoundError:
+# f = self.get_gz_fp(entry_path)
+# c = f.read()
+# f.close()
+# return c
- def put(self, uu, entry, **kwargs):
+ def put(self, uu, entry, replace=False, **kwargs):
+ fm = 'xb'
+ if replace:
+ fm = 'wb'
entry_serialized = entry.serialize()
entry_json = json.dumps(entry_serialized)
contents_bytes = entry_json.encode('utf-8')
entry_path = os.path.join(self.src, 'entries', str(uu))
- if os.path.exists(entry_path) or os.path.exists(entry_path + '.gz'):
+ if not replace and (os.path.exists(entry_path) or os.path.exists(entry_path + '.gz')):
raise FileExistsError('record {} already exists'.format(str(uu)))
f = None
if kwargs.get('compress') != None:
entry_path += '.gz'
- f = gzip.open(entry_path, 'xb')
+ f = gzip.open(entry_path, fm)
else:
- f = open(entry_path, 'xb')
+ f = open(entry_path, fm)
f.write(contents_bytes)
f.close()
diff --git a/feedwarrior/cmd/__init__.py b/feedwarrior/cmd/__init__.py
diff --git a/feedwarrior/cmd/attach.py b/feedwarrior/cmd/attach.py
@@ -0,0 +1,72 @@
+# standard imports
+import sys
+import os
+from email.message import EmailMessage
+from email.mime.multipart import MIMEMultipart
+import email
+import logging
+import uuid
+import json
+import gzip
+import tempfile
+import base64
+import uuid
+
+# external imports
+import magic
+from mime_parser import parse_mime
+
+# local imports
+import feedwarrior
+from feedwarrior import entry as feedentry
+from feedwarrior.adapters import fileadapter
+from feedwarrior.entry import from_multipart
+#from feedwarrior.common import task_ids_to_uuids, check_task_uuids
+
+logg = logging.getLogger()
+
+
+def parse_args(argparser):
+ argparser.add_argument('-e', '--entry', type=str, help='entry uuid to modify')
+ argparser.add_argument('file', type=str, help='file to attach')
+ return True
+
+
+def check_args(args):
+ pass
+
+
+def from_file(m, fp):
+ mg = magic.Magic(flags=magic.MAGIC_MIME_TYPE)
+ mime_type_str = mg.id_filename(fp)
+ mime_type = parse_mime(mime_type_str)
+ logg.info('detected {} type for {}'.format(mime_type, fp))
+
+ part = EmailMessage()
+ part.add_header('Content-Type', mime_type_str)
+ part.add_header('Content-Transfer-Encoding', 'BASE64')
+ f = open(fp, 'rb')
+ v = f.read()
+ f.close()
+ vb = base64.b64encode(v)
+ part.set_payload(vb.decode('utf-8'))
+
+ fn = os.path.basename(fp)
+ part.add_header('Content-Disposition', 'attachment', filename=fn)
+
+ m.attach(part)
+ return m
+
+
+def execute(config, feed, args):
+ fa = fileadapter(config.data_dir, None)
+ (v, fm) = fa.get_with_type(args.entry) #, compress=args.z)
+ j = json.loads(v)
+ m = email.message_from_string(j['payload'])
+
+ fp = os.path.realpath(args.file)
+ attachment = from_file(m, fp)
+
+ entry = from_multipart(attachment)
+ compress = fm == 'gzip'
+ fa.put(args.entry, entry, replace=True, compress=compress)
diff --git a/feedwarrior/runnable/main.py b/feedwarrior/runnable/main.py
@@ -0,0 +1,124 @@
+#!/usr/bin/python
+
+# Author: Louis Holbrook <dev@holbrook.no> (https://holbrook.no)
+# License: GPLv3
+# Description: Work log tool
+
+# standard imports
+import os
+import sys
+import argparse
+import configparser
+import json
+import logging
+
+# local imports
+import feedwarrior
+from feedwarrior.cmd import create as cmd_create
+from feedwarrior.cmd import entry as cmd_entry
+from feedwarrior.cmd import show as cmd_show
+from feedwarrior.cmd import ls as cmd_list
+from feedwarrior.cmd import add as cmd_add
+from feedwarrior.cmd import attach as cmd_attach
+
+logging.basicConfig(level=logging.ERROR)
+logg = logging.getLogger()
+
+
+def matches_part(full, part):
+ if len(part) > len(full):
+ return False
+ return full[:len(part)] == part
+
+
+
+argparser = argparse.ArgumentParser(description='create and manipulate feedwarrior feeds')
+argparser.add_argument('-l', help='feed log to operate on')
+argparser.add_argument('-c', type=str, default='config.ini', help='configuration file')
+argparser.add_argument('-v', action='store_true', help='be verbose')
+argparser.add_argument('--headers', action='store_true', help='add headers in output')
+sub = argparser.add_subparsers()
+# TODO: add subparser to same level flags as main parser
+sub.dest = 'command'
+sub_entry = sub.add_parser('entry', help='add entry to feed')
+cmd_entry.parse_args(sub_entry)
+sub_show = sub.add_parser('show', help='view feed log')
+cmd_show.parse_args(sub_show)
+sub_create = sub.add_parser('create', help='create new feed')
+cmd_create.parse_args(sub_create)
+sub_list = sub.add_parser('list', help='list feeds')
+cmd_list.parse_args(sub_list)
+sub_add = sub.add_parser('add', help='add new entry with editor')
+cmd_add.parse_args(sub_add)
+sub_attach = sub.add_parser('attach', help='attach file to existing entry')
+cmd_attach.parse_args(sub_attach)
+
+
+args = argparser.parse_known_args()
+args = argparser.parse_args(args[1], args[0])
+if args.v:
+ logging.getLogger().setLevel(logging.DEBUG)
+
+logg.debug('attempting to load config {}'.format(args.c))
+config = feedwarrior.load_config(args.c)
+
+
+def get_feed_by_name(s):
+ index_path = os.path.join(config.feeds_dir, 'names', s)
+ resolved_path = os.path.realpath(index_path)
+ os.stat(resolved_path)
+ logg.debug('feed path {} resolves to {}'.format(index_path, resolved_path))
+ return os.path.basename(resolved_path)
+
+
+
+def main():
+ feed_current = None
+ if args.l != None:
+ try:
+ uu = feedwarrior.common.parse_uuid(args.l)
+ feed_current = feedwarrior.feed(uu)
+ except ValueError:
+ try:
+ uu = get_feed_by_name(args.l)
+ feed_current = feedwarrior.feed(uu)
+ except FileNotFoundError as e:
+ sys.stderr.write('cannot resolve feed {}\n'.format(args.l))
+ sys.exit(1)
+
+ cmd_mod = None
+ if args.command == None or matches_part('show', args.command):
+ if feed_current == None:
+ sys.stderr.write('plesae speficy a feed for showing\n')
+ sys.exit(1)
+ feed_current = feedwarrior.load_feed(config.data_dir, feed_current.uuid)
+ cmd_mod = cmd_show
+ elif matches_part('create', args.command):
+ feed_current = feedwarrior.feed(parent=feed_current)
+ cmd_mod = cmd_create
+ elif matches_part('entry', args.command):
+ cmd_mod = cmd_entry
+ elif matches_part('list', args.command) or args.command == 'ls':
+ cmd_mod = cmd_list
+ elif matches_part('add', args.command):
+ cmd_mod = cmd_add
+ elif matches_part('attach', args.command):
+ cmd_mod = cmd_attach
+ else:
+ sys.stderr.write('invalid command {}\n'.format(args.command))
+ sys.exit(1)
+
+ try:
+ os.makedirs(config.entries_dir, mode=0o777, exist_ok=False)
+ os.makedirs(config.feeds_dir, mode=0o777, exist_ok=False)
+ os.makedirs(os.path.join(config.feeds_dir, 'names'), mode=0o777, exist_ok=False)
+ logg.debug('creating datadir {}'.format(config.data_dir))
+ except FileExistsError as e:
+ logg.debug('found existing datadir {}'.format(config.data_dir))
+
+ cmd_mod.check_args(args)
+ cmd_mod.execute(config, feed_current, args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements.txt b/requirements.txt
@@ -1,2 +1,4 @@
pyxdg~=0.26
tasklib>=1.3.0,<2.0.0
+filemagic~=1.6
+mime_parser~=1.2.0
diff --git a/setup.py b/setup.py
@@ -20,12 +20,19 @@ setup(
packages=[
'feedwarrior',
'feedwarrior.cmd',
- 'feedwarrior.adapters'
+ 'feedwarrior.adapters',
+ 'feedwarrior.runnable',
],
install_requires=[
requirements,
],
- scripts = [
- 'scripts/feedwarrior',
- ]
+ #scripts = [
+ # 'scripts/feedwarrior',
+ # ],
+ entry_points = {
+ 'console_scripts': [
+ 'feedwarrior = feedwarrior.runnable.main:main',
+ ],
+ },
+ license_files = ('LICENSE',),
)