comparison feed-push @ 32:5348758c622d draft

don't keep file descriptors open Signed-off-by: Changaco <changaco ατ changaco δοτ net>
author Changaco <changaco ατ changaco δοτ net>
date Sun, 05 Aug 2012 14:47:19 +0200
parents f5c5e6f4edc8
children adde3e451237
comparison
equal deleted inserted replaced
31:f5c5e6f4edc8 32:5348758c622d
76 log(LOG_DEBUG, 'ignoring event '+gamin_events.get(event, str(event))+' on '+path) 76 log(LOG_DEBUG, 'ignoring event '+gamin_events.get(event, str(event))+' on '+path)
77 77
78 78
79 # Config parsing 79 # Config parsing
80 80
81 def parse_config_file(config_fd): 81 def parse_config_file(config_path):
82 feeds_paths = config_to_feed_paths_to_commands[config_fd.name] = {} 82 try:
83 config_fd = open(config_path)
84 except IOError as e:
85 return log('failed to open "'+line+'": '+str(e))
86 feeds_paths = config_to_feed_paths_to_commands[config_path] = {}
83 cmd = [] 87 cmd = []
84 log('parsing config file '+config_fd.name) 88 log('parsing config file '+config_path)
85 config_fd.seek(0) 89 config_fd.seek(0)
86 for i, line in enumerate(config_fd): 90 for i, line in enumerate(config_fd):
87 line = line.strip() 91 line = line.strip()
88 if len(line) == 0 or line[0] == '#': 92 if len(line) == 0 or line[0] == '#':
89 continue 93 continue
90 if line[0] == '%': 94 if line[0] == '%':
91 if isinstance(cmd, str): 95 if isinstance(cmd, str):
92 cmd = [] 96 cmd = []
93 cmd.append(line[1:].rstrip(';')) 97 cmd.append(line[1:].rstrip(';'))
94 elif not cmd: 98 elif not cmd:
95 log(LOG_ERR, 'missing command in file '+config_fd.name+' before line '+str(i)) 99 log(LOG_ERR, 'missing command in file '+config_path+' before line '+str(i))
96 return 100 return
97 else: 101 else:
98 cmd = '; '.join(cmd) 102 cmd = '; '.join(cmd)
99 for feed_path in glob(line): 103 for feed_path in glob(line):
100 feed_path = abspath(feed_path) 104 feed_path = abspath(feed_path)
101 dict_append(feeds_paths, feed_path, cmd) 105 dict_append(feeds_paths, feed_path, cmd)
102 if not feed_path in path_to_feed_fd: 106 if not feed_path in watched_feeds:
103 monitor.watch_file(feed_path, handler(handle_feed_change)) 107 monitor.watch_file(feed_path, handler(handle_feed_change))
108 watched_feeds.add(feed_path)
104 log('now watching '+feed_path) 109 log('now watching '+feed_path)
110 config_fd.close()
105 111
106 112
107 # Gamin callbacks 113 # Gamin callbacks
108 114
109 def handler(f): 115 def handler(f):
116 122
117 def handle_config_change(path, event): 123 def handle_config_change(path, event):
118 path = abspath(path) 124 path = abspath(path)
119 if os.path.isdir(path): 125 if os.path.isdir(path):
120 ignore_event(path, event) 126 ignore_event(path, event)
121 elif not path in path_to_config_fd: 127 elif not path in config_to_feed_paths_to_commands:
122 open_config(path, event) 128 open_config(path, event)
123 elif event in [gamin.GAMChanged, gamin.GAMDeleted]: 129 elif event in [gamin.GAMChanged, gamin.GAMDeleted]:
124 update_config(path, event) 130 update_config(path, event)
125 else: 131 else:
126 ignore_event(path, event) 132 ignore_event(path, event)
127 133
128 def open_config(path, event): 134 def open_config(path, event):
129 if event in [gamin.GAMCreated, gamin.GAMExists]: 135 if event in [gamin.GAMCreated, gamin.GAMExists]:
130 if (not path.endswith('.conf') or path[0] == '.') and not hasattr(global_args.config, 'read'): 136 if (not path.endswith('.conf') or path[0] == '.') and not hasattr(global_args.config, 'read'):
131 return log('ignoring '+path+' (not a valid config file name)') 137 return log('ignoring '+path+' (not a valid config file name)')
132 try: 138 parse_config_file(path)
133 config_fd = path_to_config_fd[path] = open(path)
134 except IOError as e:
135 return log('failed to open "'+line+'" '+str(e))
136 parse_config_file(config_fd)
137 else: 139 else:
138 ignore_event(path, event) 140 ignore_event(path, event)
139 141
140 def update_config(path, event): 142 def update_config(path, event):
141 feeds_paths = set(concat(d.keys() for d in config_to_feed_paths_to_commands.values())) 143 feeds_paths = set(concat(d.keys() for d in config_to_feed_paths_to_commands.values()))
142 if event == gamin.GAMChanged: 144 if event == gamin.GAMChanged:
143 parse_config_file(path_to_config_fd[path]) 145 parse_config_file(path)
144 elif event == gamin.GAMDeleted: 146 elif event == gamin.GAMDeleted:
145 log('removing actions from deleted config file '+path) 147 log('removing actions from deleted config file '+path)
146 config_to_feed_paths_to_commands.pop(path) 148 config_to_feed_paths_to_commands.pop(path)
147 path_to_config_fd.pop(path).close()
148 new_feeds_paths = set(concat(d.keys() for d in config_to_feed_paths_to_commands.values())) 149 new_feeds_paths = set(concat(d.keys() for d in config_to_feed_paths_to_commands.values()))
149 for feed_path in feeds_paths.difference(new_feeds_paths): 150 for feed_path in feeds_paths.difference(new_feeds_paths):
150 monitor.stop_watch(feed_path) 151 monitor.stop_watch(feed_path)
152 watched_feeds.discard(feed_path)
151 log('stopped watching '+feed_path) 153 log('stopped watching '+feed_path)
152 if feed_path in path_to_feed_fd:
153 path_to_feed_fd.pop(feed_path).close()
154 154
155 def handle_feed_change(path, event): 155 def handle_feed_change(path, event):
156 if path not in path_to_feed_fd: 156 if event in [gamin.GAMCreated, gamin.GAMExists, gamin.GAMChanged]:
157 if event in [gamin.GAMCreated, gamin.GAMExists, gamin.GAMChanged]: 157 try:
158 try: 158 feed_fd = open(path)
159 path_to_feed_fd[path] = open(path) 159 except IOError as e:
160 except IOError as e: 160 return log('failed to open "'+path+'": '+str(e))
161 return log('failed to open "'+path+'": '+str(e))
162 handle_feed_change(path, gamin.GAMChanged)
163 else:
164 ignore_event(path, event)
165 elif event == gamin.GAMCreated:
166 path_to_feed_fd.pop(path).close()
167 handle_feed_change(path, gamin.GAMCreated)
168 elif event == gamin.GAMChanged:
169 feed_fd = path_to_feed_fd[path]
170 feed_fd.seek(0)
171 feed = feedparser.parse(feed_fd.read()) 161 feed = feedparser.parse(feed_fd.read())
162 feed_fd.close()
172 i = 0 163 i = 0
173 for entry in reversed(feed.entries): 164 for entry in reversed(feed.entries):
174 if entry.id in state['id_cache'].get(feed_fd.name, []) or \ 165 if entry.id in state['id_cache'].get(path, []) or \
175 not global_args.flood and calendar.timegm(entry.published_parsed) < time.time() - 86400: 166 not global_args.flood and calendar.timegm(entry.published_parsed) < time.time() - 86400:
176 continue 167 continue
177 i += 1 168 i += 1
178 for feed_path_to_commands in config_to_feed_paths_to_commands.values(): 169 for feed_path_to_commands in config_to_feed_paths_to_commands.values():
179 for cmd in feed_path_to_commands.get(path, []): 170 for cmd in feed_path_to_commands.get(path, []):
180 run_command(format_cmd(cmd, feed=feed.feed, entry=entry), entry.content[0].value) 171 run_command(format_cmd(cmd, feed=feed.feed, entry=entry), entry.content[0].value)
181 state['id_cache'][feed_fd.name] = [entry.id for entry in feed.entries] 172 state['id_cache'][path] = [entry.id for entry in feed.entries]
182 save_state() 173 save_state()
183 if i == 0: 174 if i == 0:
184 log('GAMChanged: no new entry in %s' % path) 175 log('no new entry in %s' % path)
185 elif event == gamin.GAMDeleted:
186 path_to_feed_fd.pop(path).close()
187 else: 176 else:
188 ignore_event(path, event) 177 ignore_event(path, event)
189 178
190 def save_state(): 179 def save_state():
191 global_args.state_file.truncate(0) 180 global_args.state_file.truncate(0)
313 state.update(json.loads(saved_state)) 302 state.update(json.loads(saved_state))
314 del saved_state 303 del saved_state
315 304
316 import gamin 305 import gamin
317 monitor = gamin.WatchMonitor() 306 monitor = gamin.WatchMonitor()
318 path_to_feed_fd = {} 307 watched_feeds = set()
319 path_to_config_fd = {}
320 config_to_feed_paths_to_commands = {} 308 config_to_feed_paths_to_commands = {}
321 if hasattr(global_args.config, 'read'): 309 if hasattr(global_args.config, 'read'):
322 os.chdir(os.path.dirname(global_args.config.name)) 310 os.chdir(os.path.dirname(global_args.config.name))
323 monitor.watch_file(global_args.config.name, handler(handle_config_change)) 311 monitor.watch_file(global_args.config.name, handler(handle_config_change))
324 else: 312 else: