command: Fix opmlexport crash due to orphaned feed data
[rss2email.git] / rss2email / command.py
1 # Copyright (C) 2012-2013 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of rss2email.
4 #
5 # rss2email is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 2 of the License, or (at your option) version 3 of
8 # the License.
9 #
10 # rss2email is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # rss2email.  If not, see <http://www.gnu.org/licenses/>.
16
17 """rss2email commands
18 """
19
20 import sys as _sys
21 import xml.dom.minidom as _minidom
22 import xml.sax.saxutils as _saxutils
23
24 from . import LOG as _LOG
25 from . import error as _error
26
27
28 def new(feeds, args):
29     "Create a new feed database."
30     if args.email:
31         _LOG.info('set the default target email to {}'.format(args.email))
32         feeds.config['DEFAULT']['to'] = args.email
33     feeds.save()
34
35 def email(feeds, args):
36     "Update the default target email address"
37     if not args.email:
38         _LOG.info('unset the default target email')
39     else:
40         _LOG.info('set the default target email to {}'.format(args.email))
41     feeds.config['DEFAULT']['to'] = args.email
42     feeds.save()
43
44 def add(feeds, args):
45     "Add a new feed to the database"
46     feed = feeds.new_feed(name=args.name, url=args.url, to=args.email)
47     _LOG.info('add new feed {}'.format(feed))
48     if not feed.to:
49         raise _error.NoToEmailAddress(feeds=feeds)
50     feeds.save()
51
52 def run(feeds, args):
53     "Fetch feeds and send entry emails."
54     if not args.index:
55         args.index = range(len(feeds))
56     try:
57         for index in args.index:
58             feed = feeds.index(index)
59             if feed.active:
60                 try:
61                     feed.run(send=args.send)
62                 except _error.RSS2EmailError as e:
63                     e.log()
64     finally:
65         feeds.save()
66
67 def list(feeds, args):
68     "List all the feeds in the database"
69     for i,feed in enumerate(feeds):
70         if feed.active:
71             active_char = '*'
72         else:
73             active_char = ' '
74         print('{}: [{}] {}'.format(i, active_char, feed))
75
76 def _set_active(feeds, args, active=True):
77     "Shared by `pause` and `unpause`."
78     if active:
79         action = 'unpause'
80     else:
81         action = 'pause'
82     if not args.index:
83         args.index = range(len(feeds))
84     for index in args.index:
85         feed = feeds.index(index)
86         _LOG.info('{} feed {}'.format(action, feed))
87         feed.active = active
88     feeds.save()
89
90 def pause(feeds, args):
91     "Pause a feed (disable fetching)"
92     _set_active(feeds=feeds, args=args, active=False)
93
94 def unpause(feeds, args):
95     "Unpause a feed (enable fetching)"
96     _set_active(feeds=feeds, args=args, active=True)
97
98 def delete(feeds, args):
99     "Remove a feed from the database"
100     to_remove = []
101     for index in args.index:
102         feed = feeds.index(index)
103         to_remove.append(feed)
104     for feed in to_remove:
105         _LOG.info('deleting feed {}'.format(feed))
106         feeds.remove(feed)
107     feeds.save()
108
109 def reset(feeds, args):
110     "Forget dynamic feed data (e.g. to re-send old entries)"
111     if not args.index:
112         args.index = range(len(feeds))
113     for index in args.index:
114         feed = feeds.index(index)
115         _LOG.info('resetting feed {}'.format(feed))
116         feed.reset()
117     feeds.save()
118
119 def opmlimport(feeds, args):
120     "Import configuration from OPML."
121     if args.file:
122         _LOG.info('importing feeds from {}'.format(args.file))
123         f = open(args.file, 'rb')
124     else:
125         _LOG.info('importing feeds from stdin')
126         f = _sys.stdin
127     try:
128         dom = _minidom.parse(f)
129         new_feeds = dom.getElementsByTagName('outline')
130     except Exception as e:
131         raise _error.OPMLReadError() from e
132     if args.file:
133         f.close()
134     for feed in new_feeds:
135         if feed.hasAttribute('xmlUrl'):
136             url = _saxutils.unescape(feed.getAttribute('xmlUrl'))
137             feed = feeds.new_feed(url=url)
138             _LOG.info('add new feed {}'.format(feed))
139     feeds.save()
140
141 def opmlexport(feeds, args):
142     "Export configuration to OPML."
143     if args.file:
144         _LOG.info('exporting feeds to {}'.format(args.file))
145         f = open(args.file, 'rb')
146     else:
147         _LOG.info('exporting feeds to stdout')
148         f = _sys.stdout
149     f.write(
150         '<?xml version="1.0" encoding="UTF-8"?>\n'
151         '<opml version="1.0">\n'
152         '<head>\n'
153         '<title>rss2email OPML export</title>\n'
154         '</head>\n'
155         '<body>\n')
156     for feed in feeds:
157         if not feed.url:
158             _LOG.debug('dropping {}'.format(feed))
159             continue
160         url = _saxutils.escape(feed.url)
161         f.write('<outline type="rss" text="{0}" xmlUrl="{0}"/>'.format(url))
162     f.write(
163         '</body>\n'
164         '</opml>\n')
165     if args.file:
166         f.close()