Exemple #1
0
    def donation_delete(self, msg, donation_id: str) -> str:
        """
        As an admin, delete a donation either because its spam or needs to be re-submitted
        """
        with synchronized(CONFIRMATION_LOCK):
            try:
                to_be_confirmed = self["to_be_confirmed"]
                to_be_confirmed.pop(donation_id)
                self["to_be_confirmed"] = to_be_confirmed
                return f"Removed pending donation {donation_id}"
            except KeyError:
                pass

        with synchronized(RECORDED_LOCK):
            try:
                to_be_recorded = self["to_be_recorded"]
                to_be_recorded.pop(donation_id)
                self["to_be_recorded"] = to_be_recorded
                return f"Removed recorded donation {donation_id}"
            except KeyError:
                pass

        with synchronized(DONOR_LOCK):
            try:
                donations = self["donations"]
                donations.pop(donation_id)
                self["donations"] = donations
                return (
                    f"Removed pr'd donation {donation_id}. This won't remove the donation from the page until a pr"
                    f"is redone with new donations list. You can do this with ./rebuild donations list"
                )
            except KeyError:
                return f"Donation {donation_id} is not found."
        return f"Donation {donation_id} is not in our donations lists"
Exemple #2
0
    def donation_change(self, msg, donation_id: str, amount: str) -> str:
        """
        As an admin, change a donation amount
        """
        if "$" not in amount:
            return (
                "Error: Please include your amount as a $##.##. i.e. $20.99. You can also use whole numbers like "
                "$20")

        amount_float = float(amount.replace("$", ""))
        if amount_float <= 0:
            return "Error: Donation amount has to be a positive number."

        with synchronized(CONFIRMATION_LOCK):
            to_be_confirmed = self["to_be_confirmed"]
            donation = to_be_confirmed.pop(donation_id, None)
            if donation is None:
                return f"Error: {donation_id} is not in our donation database."
            self["to_be_confirmed"] = to_be_confirmed

        self._add_donation_for_confirmation(
            donation_id=donation_id,
            amount=amount_float,
            file_url=donation["file_url"],
            user=donation["user"],
            make_public=donation["user"] is not None,
        )
        return (
            f"Donation {donation_id} has been updated. You can now confirm it with "
            f"`./donation confirm {donation_id}`")
Exemple #3
0
    def donation_confirm(self, msg, donation_id: str) -> str:
        """
        As an admin, confirm a donation
        """
        with synchronized(CONFIRMATION_LOCK):
            to_be_confirmed = self["to_be_confirmed"]
            donation = to_be_confirmed.pop(donation_id, None)
            if donation is None:
                return f"Error: {donation_id} is not in our donation database."
            self["to_be_confirmed"] = to_be_confirmed

        with synchronized(RECORDED_LOCK):
            to_be_recorded = self["to_be_recorded"]
            to_be_recorded[donation_id] = donation
            self["to_be_recorded"] = to_be_recorded

        return f"Donation {donation_id} confirmed. Be on the look out for a PR updating the website"
Exemple #4
0
    def _record_donations(self, force: bool = False) -> None:
        """
        Poller that records a donation and turns it into a PR
        """
        if len(self["to_be_recorded"].keys()) == 0 and not force:
            return
        timestamp = int(datetime.now().timestamp())

        with synchronized(RECORDED_LOCK):
            to_be_recorded = self["to_be_recorded"]
            self["to_be_recorded"] = dict()

        with synchronized(DONOR_LOCK):
            new_donations = {**self["donations"], **to_be_recorded}
            self["donations"] = new_donations

        self["donation_total"] = self._total_donations()
        branch_name = f"new-donations-{timestamp}"
        with self.website_plugin.temp_website_clone(
                checkout_branch=branch_name) as website_clone:
            file_list = self._update_blog_post(website_clone, new_donations,
                                               self["donation_total"])
            pr = self.website_plugin.open_website_pr(
                website_clone,
                file_list,
                f"updating with new donations {timestamp}",
                f"Donation Manager: New Donations {timestamp}",
                "New donations",
            )

        self.send(self.config["DM_CHANNEL_IDENTIFIER"],
                  text=f"New donation PR:\n"
                  f"{pr}")

        self.log.debug(self.config["DM_REPORT_CHANNEL_ID"])
        self._bot.api_call(
            "conversations.setTopic",
            {
                "channel":
                self.config["DM_REPORT_CHANNEL_ID"],
                "topic":
                f"Total Donations in SA Dev's Season of Giving: ${self['donation_total']:.2f}",
            },
        )
Exemple #5
0
 def activate(self):
     super().activate()
     with synchronized(CONFIRMATION_LOCK):
         try:
             self["to_be_confirmed"]
         except KeyError:
             self["to_be_confirmed"] = dict()
     with synchronized(RECORDED_LOCK):
         try:
             self["to_be_recorded"]
         except KeyError:
             self["to_be_recorded"] = dict()
     with synchronized(DONOR_LOCK):
         try:
             self["donations"]
         except KeyError:
             self["donations"] = dict()
     self["donation_total"] = self._total_donations()
     self.website_plugin = self.get_plugin("SADevsWebsite")
     self.start_poller(self.config["DM_RECORD_POLLER_INTERVAL"],
                       self._record_donations)
Exemple #6
0
    def list_donations(self, msg, _) -> str:
        """Lists all the donations we have"""

        yield "*Donations still needing confirmation*:"
        with synchronized(CONFIRMATION_LOCK):
            for id, donation in self["to_be_confirmed"].items():
                yield f"{id}: {donation['user']} - {donation['amount']} - {donation['file_url']}"

        yield "*Donations waiting to be recorded:*"
        with synchronized(RECORDED_LOCK):
            yield "\n".join([
                f"{id}: {donation['user']} - {donation['amount']}"
                for id, donation in self["to_be_recorded"].items()
            ])

        yield "*Confirmed Donations*:"
        with synchronized(DONOR_LOCK):
            yield "\n".join([
                f"{id}: {donation['user']} - {donation['amount']}"
                for id, donation in self["donations"].items()
            ])
Exemple #7
0
    def activate(self):
        super().activate()
        # setup our on disk log
        with synchronized(CAL_LOCK):
            try:
                self["channel_action_log"]
            except KeyError:
                self["channel_action_log"] = {
                    datetime.now().strftime("%Y-%m-%d"): list()
                }

        self.start_poller(
            self.config["CHANMON_LOG_JANITOR_INTERVAL"],
            self._log_janitor,
            args=(self.config["CHANMON_LOG_DAYS"]),
        )
Exemple #8
0
    def _log_janitor(self, days_to_keep: int) -> None:
        """Prunes our on-disk logs"""
        first_key = next(iter(self["channel_action_log"]))
        if UTC.localize(datetime.utcnow()) - parse(first_key) > timedelta(
                days=days_to_keep):
            with synchronized(CAL_LOCK):
                cal_log = self["channel_action_log"]
                cal_log.pop(first_key, None)
                self["channel_action_log"] = cal_log

        cal_log = self["channel_action_log"]
        today = datetime.now().strftime("%Y-%m-%d")
        for key in cal_log.keys():
            if len(cal_log[key]) == 0 and key != today:
                cal_log.pop(key)
        self["channel_action_log"] = cal_log
Exemple #9
0
    def _log_channel_change(self, channel_name: str, user_name: str,
                            action: str, timestamp: str) -> None:
        """Logs a channel change event"""
        log = self._build_log(channel_name, user_name, action, timestamp)
        if self.config["CHANMON_CHANNEL_ID"] is not None:
            self._send_log_to_slack(log)

        with synchronized(CAL_LOCK):
            chan_log = self["channel_action_log"]
            today = datetime.now().strftime("%Y-%m-%d")
            try:
                chan_log[today].append(log)
            except KeyError:
                chan_log[today] = list()
                chan_log[today].append(log)
            self["channel_action_log"] = chan_log
Exemple #10
0
    def _write_router_status(self):
        self._modelChanged.wait()
        with synchronized(self):

            def neq(i, v):
                s = self.read_state
                return len(s) > i and v != s[i]

            diff = [(i, v) for i, v in enumerate(self.goal_state) if neq(i, v)]
            sends = diff
            try:
                for i, v in sends:
                    self.read_state[i] = v
                self.send_set_items(sends)
            except OSError:
                self._send_disconnect()
            except Exception as e:
                print(e)
            self._modelChanged.clear()
Exemple #11
0
    def _add_donation_for_confirmation(
        self,
        donation_id: str,
        amount: float,
        file_url: str,
        user: str,
        make_public: bool,
    ) -> None:
        """
        Adds a donation to be confirmed
        """
        if not make_public:
            user = None
        else:
            if type(user) != str:
                user = self._get_user_real_name(user)

        with synchronized(CONFIRMATION_LOCK):
            try:
                to_be_confirmed = self["to_be_confirmed"]
            except KeyError:
                to_be_confirmed = dict()
            if donation_id in to_be_confirmed:
                raise KeyError(
                    "Donation is not unique. Did you already add this donation? If this is in error, "
                    "reach out to the admins")

            to_be_confirmed[donation_id] = {
                "amount": amount,
                "file_url": file_url,
                "user": user,
            }
            self["to_be_confirmed"] = to_be_confirmed

        self.send(
            self.config["DM_CHANNEL_IDENTIFIER"],
            text=f"New donation:\n"
            f"Amount: ${amount:.2f}\n"
            f"File URL: {file_url}\n"
            f"User: {user}\n\n"
            f"To approve this donation run `./donation confirm {donation_id}`\n"
            f"To change this donation run `./donation change {donation_id} [new amount]`",
        )
def report_metrics():
    """Report the current batch of metrics to InfluxDB.

    """

    global data_points

    # Set aside the current batch of metrics and initialize the list
    # used to collect metrics to empty again.

    with wrapt.synchronized(lock):
        pending_data_points = data_points
        data_points = []

    # Report the complete batch of metrics to InfluxDB in one go.

    if pending_data_points:
        try:
            client.write_points(pending_data_points)
        except Exception:
            traceback.print_exc()
Exemple #13
0
            def wrapper(*args, **kwargs):
                use_cached = kwargs.pop(optional,
                                        default) if optional else True
                if method:
                    self = args[0]
                    c = cache(self)
                    if single_lock:
                        cache_lock = lock
                    else:
                        cache_lock = lock(
                            self) if lock is not True else synchronized(self)
                else:
                    c = cache
                    cache_lock = lock

                if c is None:
                    return func(*args, **kwargs)

                kargs, kkwargs = split_arg_vals_with_defaults(
                    sig, *args, **kwargs)
                k = key(*kargs, **kkwargs)
                if use_cached:
                    with cache_lock:
                        try:
                            val = c[k]
                        except KeyError:
                            pass  # key not found
                        else:
                            if isinstance(val, Exception) and exc:
                                raise val
                            return val

                    if key_lock:
                        do_exec = False
                        with cache_lock:
                            klock = key_locks.get(k, None)
                            if klock is None:  # The func is not already being executed with these args
                                do_exec = True
                                klock = key_locks[k] = lock_type(
                                ) if lock is not True else RLock()
                                # Acquire before releasing cache_lock to prevent the wrong thread from getting it first
                                klock.acquire()

                        if do_exec:
                            try:
                                try:
                                    val = func(*args, **kwargs)
                                except Exception as e:
                                    if exc:
                                        val = e
                                    else:
                                        raise e

                                with suppress(
                                        ValueError
                                ):  # raised if the value is too large
                                    with cache_lock:
                                        c[k] = val
                            finally:
                                with cache_lock:
                                    klock.release()
                                    del key_locks[
                                        k]  # Allow future runs if key disappears from cache (e.g., TTLCache)

                            if isinstance(val, Exception) and exc:
                                raise val
                            return val
                        else:
                            with klock:  # Block until the executing thread completes
                                with cache_lock:
                                    try:
                                        val = c[k]
                                    except KeyError:
                                        pass  # Just in case something goes wrong; falls thru to execute/store/return
                                    else:
                                        if isinstance(val, Exception) and exc:
                                            raise val
                                        return val
                try:
                    val = func(*args, **kwargs)
                except Exception as e:
                    if exc:
                        val = e
                    else:
                        raise e

                with suppress(ValueError):  # raised if the value is too large
                    with cache_lock:
                        c[k] = val

                if isinstance(val, Exception) and exc:
                    raise val
                return val