summaryrefslogtreecommitdiffstats
path: root/release-notes/trac.py
diff options
context:
space:
mode:
Diffstat (limited to 'release-notes/trac.py')
-rw-r--r--release-notes/trac.py136
1 files changed, 136 insertions, 0 deletions
diff --git a/release-notes/trac.py b/release-notes/trac.py
new file mode 100644
index 0000000..91e781a
--- /dev/null
+++ b/release-notes/trac.py
@@ -0,0 +1,136 @@
+#
+# RTEMS Tools Project (http://www.rtems.org/)
+# Copyright 2022 Chris Johns (chris@contemporary.software)
+# All rights reserved.
+#
+# This file is part of the RTEMS Tools package in 'rtems-tools'.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+import pickle
+import os
+import urllib.request
+
+
+class cache(object):
+
+ def __init__(self, milestone, path, force):
+ self.milestone = milestone
+ self.path = path
+ self.force = force
+ self.checked = False
+ self.cache_valid = False
+
+ @staticmethod
+ def _milestone(url):
+ path, options = url.split('?', 1)
+ opts = options.split('&')
+ for o in opts:
+ if 'milestone' in o:
+ label, milestone = o.split('=', 1)
+ return milestone
+ raise RuntimeError('milestone not found: ' + url)
+
+ def _tickets_path(self):
+ return os.path.join(self.path,
+ 'tickets-%s' % (self.milestone) + '.ppk')
+
+ def _ticket_path(self, url):
+ path, options = url.split('?', 1)
+ opts = options.split('&')
+ fmt = None
+ for o in opts:
+ if 'format' in o:
+ label, fmt = o.split('=', 1)
+ if not fmt:
+ raise RuntimeError('ticket format not found: ' + url)
+ if '/' in path:
+ ticket_id = path[path.rfind('/') + 1:]
+ return os.path.join(self.path, '%s.%s' % (ticket_id, fmt))
+ raise RuntimeError('ticket id not found: ' + url)
+
+ def _query_path(self):
+ return os.path.join(self.path, 'query-%s' % (self.milestone) + '.csv')
+
+ def check(self):
+ if not self.checked:
+ self.checked = True
+ if self.path:
+ if os.path.exists(self.path):
+ if not os.path.isdir(self.path):
+ raise RuntimeError('cache is not a directory:' +
+ self.path)
+ else:
+ os.mkdir(self.path)
+ self.cache_valid = True
+ return self.cache_valid
+
+ def open_page(self, url):
+ url_path = None
+ if self.check():
+ if 'query' in url:
+ url_path = self._query_path()
+ else:
+ url_path = self._ticket_path(url)
+ if not self.force and os.path.exists(url_path):
+ return open(url_path, 'rb')
+ # Open the URL
+ delay = 1
+ tries = 6
+ backoff = 2
+ while tries > 0:
+ try:
+ page = urllib.request.urlopen(url)
+ if url_path:
+ with open(url_path, 'wb') as f:
+ f.write(page.read())
+ return open(url_path, 'rb')
+ return page
+ except OSError:
+ tries -= 1
+ time.sleep(delay)
+ delay *= backoff
+ raise RuntimeError('cannot open url:' + url)
+
+ def load(self):
+ if self.check():
+ ticket_cache = self._tickets_path()
+ if os.path.exists(ticket_cache):
+ if not self.force:
+ try:
+ with open(ticket_cache, 'rb') as f:
+ tickets = pickle.load(f)
+ print('%d tickets loaded from cache: %s' %
+ (len(tickets['tickets']), ticket_cache))
+ return tickets
+ except:
+ print('cache corrupted: ' + ticket_cache)
+ os.remove(ticket_cache)
+ return None
+
+ def unload(self, tickets):
+ if self.check():
+ ticket_cache = self._tickets_path()
+ with open(ticket_cache, 'wb') as f:
+ pickle.dump(tickets, f)