[Git][clojure-team/trapperkeeper-scheduler-clojure][upstream] New upstream version 1.4.1
Jérôme Charaoui (@lavamind)
gitlab at salsa.debian.org
Thu Sep 10 15:08:11 BST 2026
Jérôme Charaoui pushed to branch upstream at Debian Clojure Maintainers / trapperkeeper-scheduler-clojure
Commits:
f174db96 by Jérôme Charaoui at 2026-09-09T22:34:16-04:00
New upstream version 1.4.1
- - - - -
17 changed files:
- + .github/dependabot.yml
- + .github/release.yml
- + .github/workflows/pr-testing.yaml
- + .github/workflows/release.yaml
- − .travis.yml
- CHANGELOG.md
- − CODEOWNERS
- − CONTRIBUTING.md
- README.md
- − ext/travisci/test.sh
- locales/trapperkeeper-scheduler.pot
- project.clj
- + renovate.json
- src/puppetlabs/trapperkeeper/services/protocols/scheduler.clj
- src/puppetlabs/trapperkeeper/services/scheduler/scheduler_core.clj
- src/puppetlabs/trapperkeeper/services/scheduler/scheduler_service.clj
- test/integration/puppetlabs/trapperkeeper/services/scheduler/scheduler_service_test.clj
Changes:
=====================================
.github/dependabot.yml
=====================================
@@ -0,0 +1,17 @@
+version: 2
+updates:
+ # raise PRs for gem updates
+ - package-ecosystem: bundler
+ directory: "/"
+ schedule:
+ interval: daily
+ time: "13:00"
+ open-pull-requests-limit: 10
+
+ # Maintain dependencies for GitHub Actions
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: daily
+ time: "13:00"
+ open-pull-requests-limit: 10
=====================================
.github/release.yml
=====================================
@@ -0,0 +1,39 @@
+---
+# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
+
+changelog:
+ exclude:
+ labels:
+ - duplicate
+ - invalid
+ - modulesync
+ - question
+ - skip-changelog
+ - wont-fix
+ - wontfix
+
+ categories:
+ - title: Breaking Changes 🛠
+ labels:
+ - backwards-incompatible
+
+ - title: New Features 🎉
+ labels:
+ - enhancement
+
+ - title: Bug Fixes 🐛
+ labels:
+ - bug
+
+ - title: Documentation Updates 📚
+ labels:
+ - documentation
+ - docs
+
+ - title: Dependency Updates ⬆️
+ labels:
+ - dependencies
+
+ - title: Other Changes
+ labels:
+ - "*"
=====================================
.github/workflows/pr-testing.yaml
=====================================
@@ -0,0 +1,55 @@
+---
+name: PR Testing
+
+on:
+ pull_request: {}
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: read
+
+jobs:
+ pr-testing:
+ name: PR Testing
+ strategy:
+ fail-fast: false
+ matrix:
+ version: ['17', '21', '25']
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout repo
+ uses: actions/checkout at 3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ submodules: recursive
+ - name: setup java
+ uses: actions/setup-java at b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.version }}
+ - name: Install Clojure tools
+ uses: DeLaGuardo/setup-clojure at 4c7a6f613e5089821bb3bb2a33a3ee115578580d # 13.6.1
+ with:
+ cli: latest # Clojure CLI based on tools.deps
+ lein: latest # Leiningen
+ boot: latest # Boot.clj
+ bb: latest # Babashka
+ clj-kondo: latest # Clj-kondo
+ cljstyle: latest # cljstyle
+ zprint: latest # zprint
+ - name: clojure tests
+ run: lein test
+ timeout-minutes: 30
+
+ tests:
+ if: always()
+ needs:
+ - pr-testing
+ runs-on: ubuntu-24.04
+ name: Test suite
+ steps:
+ - name: Decide whether the needed jobs succeeded or failed
+ uses: re-actors/alls-green at release/v1
+ with:
+ jobs: ${{ toJSON(needs) }}
=====================================
.github/workflows/release.yaml
=====================================
@@ -0,0 +1,18 @@
+---
+name: Release and publish package to clojars.org
+
+permissions:
+ contents: write
+ packages: write
+
+on:
+ workflow_dispatch:
+
+jobs:
+ release:
+ uses: 'openvoxproject/shared-actions/.github/workflows/clojure_release.yml at main'
+ secrets:
+ github_pat: ${{ secrets.OPENVOXBOT_COMMIT_AND_PRS }}
+ ssh_private_key: ${{ secrets.OPENVOXBOT_SSH_PRIVATE_KEY }}
+ clojars_username: ${{ secrets.CLOJARS_USERNAME }}
+ clojars_password: ${{ secrets.CLOJARS_PASSWORD }}
=====================================
.travis.yml deleted
=====================================
@@ -1,15 +0,0 @@
-language: clojure
-lein: 2.9.1
-jdk:
-- openjdk8
-- openjdk11
-script: ./ext/travisci/test.sh
-notifications:
- email: false
- hipchat:
- rooms:
- secure: CfGS3yYsocLruSP0lRr9HFwzdZ1HFw4rFEVdJkP/i0i8aIPY3XB3vTQNxw/lIBVRDwWHaUfA+xnyK3itCxt0M0UtPDp1BkU6abLr8Apjet/nJJ2icBvQfnpx1hb6xBpoE69vpRiIVewoU69UPFjXdZd2D1BWX58tNbdV9CA8Ezw=
- template:
- - ! '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}'
- - ! 'Change view: %{compare_url}'
- - ! 'Build details: %{build_url}'
=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,16 @@
+## 1.2.3
+ * add interface for `cron` to the protocol and implementation to
+allow for jobs on a cadence specified by a cron string
+ * add interface for `cron-next-valid-time` to the potocol and
+implementation to query the next occurance of a given cron
+specification.
+
+## 1.2.1
+ * updates clj-parent and drops support for java 8
+
+## 1.2.0
+ * unreleased
+
## 1.1.3
* ensure a non-nil function is passed to scheduling routines
=====================================
CODEOWNERS deleted
=====================================
@@ -1,4 +0,0 @@
-# This will cause the puppetserver-maintainers group to be assigned
-# review of any opened PRs against the branches containing this file.
-
-* @puppetlabs/puppetserver-maintainers
=====================================
CONTRIBUTING.md deleted
=====================================
@@ -1,9 +0,0 @@
-# How to contribute
-
-Third-party patches are essential for keeping Puppet Labs open-source projects
-great. We want to keep it as easy as possible to contribute changes that
-allow you to get the most out of our projects. There are a few guidelines
-that we need contributors to follow so that we can have a chance of keeping on
-top of things. For more info, see our canonical guide to contributing:
-
-[https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md)
=====================================
README.md
=====================================
@@ -1,8 +1,6 @@
# Trapperkeeper Scheduler Service
-[](https://travis-ci.org/puppetlabs/trapperkeeper-scheduler)
-
-[](http://clojars.org/puppetlabs/trapperkeeper-scheduler)
+[](http://clojars.org/org.openvoxproject/trapperkeeper-scheduler)
A Trapperkeeper service that provides a simple API for scheduling background tasks.
@@ -45,6 +43,17 @@ The functions that are currently available are as follows:
identifier that can be used to reference this scheduled job (e.g.,
for cancellation) later. A group identifier `group-id` can be provided that
allows jobs in the same group to be stopped at the same time.
+* `cron [cron-string f]`: schedules a job that will call `f` in accordance with the
+ cron schedule indicated by the 'cron-string'. Returns an identifier that can
+ be used to reference this scheduled job (e.g. for cancellation) later. More information
+ on a valid cron string can be found [here](https://www.quartz-scheduler.org/api/2.3.0/org/quartz/CronExpression.html).
+* `cron [cron-string f group-id]`: schedules a job that will call `f` in accordance
+ with the cron schedule indicated by the 'cron-string'. Returns an identifier that can
+ be used to reference this scheduled job (e.g. for cancellation) later. A group identifier
+ `group-id` can be provided that allows jobs in the same group to be stopped at the same time.
+ More information on a valid cron string can be found [here](https://www.quartz-scheduler.org/api/2.3.0/org/quartz/CronExpression.html).
+* `cron-next-valid-time [cron-string date]`: Given a cron specification and a date, returns a
+ date that corresponds to the next execution of the timer based on that cron value.
* `after [interval-ms f]`: schedules a job that will call `f` a single time, after
a delay of `interval-ms` milliseconds. Returns an identifier that can be used
to reference this scheduled job (e.g. for cancellation) later.
@@ -62,7 +71,7 @@ The functions that are currently available are as follows:
cancels the job. If the job is currently executing it will be allowed to complete,
but will not be invoked again afterward. Returns `true` if the job was successfully
stopped, `false` otherwise.
-* `stop-grouped-jobs [group-id]`: Given a `group-id` identifier, cancel all the jobs
+* `stop-jobs [group-id]`: Given a `group-id` identifier, cancel all the jobs
associated with that `group-id`. If any of the jobs are currently executing they
will be allowed to complete, but will not be invoked again afterward. Returns a
sequence of maps, one for each job in the group, with each map containing the
@@ -92,7 +101,6 @@ the [`org.quartz-scheduler/quartz`](http://www.quartz-scheduler.org/) library.
* Add additional scheduling API functions with more complicated recurring models.`.
* Add API for introspecting the state of currently scheduled jobs
-#Support
-
-Please log tickets and issues at our [Jira Tracker](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20Trapperkeeper).
+## Support
+GitHub issues and PRs are welcome! Additionally, drop us a line in [the Vox Pupuli Slack](https://voxpupuli.org/connect/).
=====================================
ext/travisci/test.sh deleted
=====================================
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-lein test
=====================================
locales/trapperkeeper-scheduler.pot
=====================================
@@ -7,7 +7,7 @@
msgid ""
msgstr ""
"Project-Id-Version: puppetlabs.trapperkeeper_scheduler \n"
-"X-Git-Ref: ccf78ef0d6b688ce8fc24988277ddf04de9f8945\n"
+"X-Git-Ref: c183da5db1824d02eaf3d3c04339dfa1abdc3655\n"
"Report-Msgid-Bugs-To: docs at puppet.com\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@@ -35,6 +35,10 @@ msgstr ""
msgid "Failed to schedule job"
msgstr ""
+#: src/puppetlabs/trapperkeeper/services/scheduler/scheduler_core.clj
+msgid "Invalid cron expression"
+msgstr ""
+
#. this can occur if the interface is being used while the scheduler is shutdown
#: src/puppetlabs/trapperkeeper/services/scheduler/scheduler_core.clj
msgid "Failure stopping job"
=====================================
project.clj
=====================================
@@ -1,34 +1,49 @@
-(defproject puppetlabs/trapperkeeper-scheduler "1.1.3"
+(def i18n-version "1.0.5")
+
+(defproject org.openvoxproject/trapperkeeper-scheduler "1.4.1"
:description "Trapperkeeper Scheduler Service"
+ ;; Generally, try to keep version pins in :managed-dependencies and the libraries
+ ;; this project actually uses in :dependencies, inheriting the version from
+ ;; :managed-dependencies. This prevents endless version conflicts due to deps of deps.
+ ;; Renovate should keep the versions largely in sync between projects.
+ :managed-dependencies [[org.clojure/clojure "1.12.5"]
+ [org.clojure/tools.logging "1.3.1"]
+ [clj-time "0.15.2"]
+ [org.openvoxproject/i18n ~i18n-version]
+ [org.openvoxproject/kitchensink "3.5.8"]
+ [org.openvoxproject/kitchensink "3.5.8" :classifier "test"]
+ [org.openvoxproject/trapperkeeper "5.0.5"]
+ [org.openvoxproject/trapperkeeper "5.0.5" :classifier "test"]
+ [org.quartz-scheduler/quartz "2.5.2"]]
+
:dependencies [[org.clojure/clojure]
- [puppetlabs/trapperkeeper]
- [puppetlabs/i18n]
- [puppetlabs/kitchensink]
- [org.quartz-scheduler/quartz "2.3.2" :exclusions [c3p0]]]
+ [org.openvoxproject/i18n]
+ [org.openvoxproject/kitchensink]
+ [org.openvoxproject/trapperkeeper]
+ [org.quartz-scheduler/quartz :exclusions [c3p0]]]
:min-lein-version "2.9.1"
- :parent-project {:coords [puppetlabs/clj-parent "4.6.7"]
- :inherit [:managed-dependencies]}
-
:pedantic? :abort
+ :license {:name "Apache License, Version 2.0"
+ :url "http://www.apache.org/licenses/LICENSE-2.0.html"}
+
:test-paths ["test/unit" "test/integration"]
:test-selectors {:integration :integration
:unit (complement :integration)}
:deploy-repositories [["releases" {:url "https://clojars.org/repo"
- :username :env/clojars_jenkins_username
- :password :env/clojars_jenkins_password
+ :username :env/CLOJARS_USERNAME
+ :password :env/CLOJARS_PASSWORD
:sign-releases false}]]
:profiles {:dev {:source-paths ["dev"]
- :dependencies [[puppetlabs/trapperkeeper :classifier "test" :scope "test"]
- [puppetlabs/kitchensink :classifier "test" :scope "test"]]}}
+ :dependencies [[org.openvoxproject/trapperkeeper :classifier "test" :scope "test"]
+ [org.openvoxproject/kitchensink :classifier "test" :scope "test"]]}}
- :plugins [[lein-parent "0.3.7"]
- [puppetlabs/i18n "0.8.0"]]
+ :plugins [[org.openvoxproject/i18n ~i18n-version]]
:aot [puppetlabs.trapperkeeper.services.scheduler.job]
:repl-options {:init-ns user})
=====================================
renovate.json
=====================================
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "addLabels": ["renovate", "dependencies"],
+ "assigneesFromCodeOwners": true,
+ "automerge": true,
+ "automergeType": "pr",
+ "dependencyDashboard": true,
+ "enabledManagers": ["leiningen"]
+}
=====================================
src/puppetlabs/trapperkeeper/services/protocols/scheduler.clj
=====================================
@@ -11,6 +11,14 @@
group-id can be provided to collect a set of jobs into one group to allow
them to be stopped together.")
+ (cron
+ [this cron-string f]
+ [this cron-string f group-id]
+ "Calls 'f' in accordance with the cron schedule indicated by the 'cron-string'.
+ Returns an identifier for the scheduled job. An optional
+ group can be provided to associated jobs with each other to allow
+ them to be stopped together.")
+
(after
[this n f]
[this n f group-id]
@@ -38,6 +46,11 @@
group-id can be provided to collect a set of jobs into one group to allow
them to be stopped together.")
+ (cron-next-valid-time
+ [this cron-string date]
+ "Given a cron specification and a date, returns a date that corresponds
+ to the next execution of the timer based on that cron value")
+
(stop-job [this job]
"Given an identifier of a scheduled job, stop its execution. If an
=====================================
src/puppetlabs/trapperkeeper/services/scheduler/scheduler_core.clj
=====================================
@@ -5,7 +5,8 @@
(:import (org.quartz.impl.matchers GroupMatcher)
(org.quartz.impl StdSchedulerFactory SchedulerRepository)
(org.quartz JobBuilder SimpleScheduleBuilder TriggerBuilder
- Scheduler JobKey SchedulerException JobDataMap)
+ Scheduler JobKey SchedulerException JobDataMap
+ CronExpression CronScheduleBuilder)
(org.quartz.utils Key)
(java.util Date UUID Properties)))
@@ -55,6 +56,27 @@
; this can occur if the interface is being used while the scheduler is shutdown
(log/error e (i18n/trs "Failed to schedule job")))))
+(defn cron
+ [cron-string f ^Scheduler scheduler group-name]
+ (try
+ ;; throws parse error if cron-string is invalid
+ (CronExpression/validateExpression cron-string)
+ (let [cron-schedule (CronScheduleBuilder/cronSchedule cron-string)
+ job-name (Key/createUniqueName group-name)
+ job (build-executable-job f job-name group-name)
+ trigger (-> (TriggerBuilder/newTrigger)
+ (.withSchedule cron-schedule)
+ (.build))]
+ (.scheduleJob scheduler job trigger)
+ (.getJobKey trigger))
+ (catch SchedulerException e
+ ; this can occur if the interface is being used while the scheduler is shutdown
+ (log/error e (i18n/trs "Failed to schedule job")))
+ (catch java.text.ParseException e
+ ;; this occurs when the cron-string is invalid
+ (log/debug e)
+ (throw (IllegalArgumentException. ^String (i18n/trs "Invalid cron expression") e)))))
+
(defn after
[n f ^Scheduler scheduler group-name]
(try
@@ -114,6 +136,20 @@
; this can occur if the interface is being used while the scheduler is shutdown
(log/error e (i18n/trs "Failed to schedule job")))))
+(defn cron-next-valid-time
+ "Returns the next occurance of the cron specification after the given date"
+ [cron-string ^Date date]
+ (try
+ ;; throws parse error if cron-string is invalid
+ (CronExpression/validateExpression cron-string)
+ (let [cron-expression (CronExpression. cron-string)
+ next-valid-time (.getNextValidTimeAfter cron-expression date)]
+ next-valid-time)
+ (catch java.text.ParseException e
+ ;; this occurs when the cron-string is invalid
+ (log/debug e)
+ (throw (IllegalArgumentException. ^String (i18n/trs "Invalid cron expression") e)))))
+
(defn stop-job
"Returns true, if the job was deleted, and false if the job wasn't found."
[^JobKey id ^Scheduler scheduler]
=====================================
src/puppetlabs/trapperkeeper/services/scheduler/scheduler_service.clj
=====================================
@@ -54,6 +54,15 @@
(interspaced [this n f group-id]
(core/interspaced n f (get-scheduler this) (safe-group-id group-id)))
+
+ (cron [this cron-string f]
+ (cron this cron-string f default-group-name))
+
+ (cron [this cron-string f group-id]
+ (core/cron cron-string f (get-scheduler this) (safe-group-id group-id)))
+
+ (cron-next-valid-time [this cron-string date]
+ (core/cron-next-valid-time cron-string date))
(after [this n f]
(after this n f default-group-name))
=====================================
test/integration/puppetlabs/trapperkeeper/services/scheduler/scheduler_service_test.clj
=====================================
@@ -6,7 +6,9 @@
[puppetlabs.trapperkeeper.services.protocols.scheduler :refer :all]
[puppetlabs.trapperkeeper.services.scheduler.scheduler-core :as sc]
[puppetlabs.trapperkeeper.app :as tk])
- (:import [java.util.concurrent TimeUnit CountDownLatch]))
+ (:import [java.util.concurrent TimeUnit CountDownLatch]
+ [java.util Date Calendar]
+ [java.time LocalDateTime]))
(deftest ^:integration test-interspaced
(testing "without group-id"
@@ -114,6 +116,97 @@
actual-delay (- execution-time schedule-time)]
(is (>= actual-delay delay)))))))))))
+(deftest ^:integration test-cron
+ (testing "without group-id"
+ (with-app-with-empty-config app [scheduler-service]
+ (testing "cron"
+ (let [service (tk/get-service app :SchedulerService)
+ num-runs 5 ; let it run a few times, but not too many
+ cron-string "0/10 * * * * ?" ; every 10 seconds
+ p (promise)
+ counter (atom 0)
+ runtimes (atom [])
+ last-completion-time (atom nil)
+ job (fn []
+ (when @last-completion-time
+ (let [runtime (LocalDateTime/now)]
+ (swap! runtimes conj runtime)))
+ (swap! counter inc)
+ (when (= @counter num-runs)
+ (deliver p nil))
+
+ (reset! last-completion-time (LocalDateTime/now)))]
+
+ ; Schedule the job, and wait for it run num-runs times, then stop it.
+ (let [job-id (cron service cron-string job)]
+ (deref p)
+ (stop-job service job-id))
+
+ (testing "each runtime should be on a multiple of 10 seconds"
+ (is (every? (fn [^LocalDateTime runtime] (= (rem (.getSecond runtime) 10)
+ 0)) @runtimes)))
+ (testing "throws Illegal Argument exception for invalid cron string"
+ (is (thrown? IllegalArgumentException
+ (cron service "**invalid**" job))))))))
+ (testing "with group-id"
+ (with-app-with-empty-config app [scheduler-service]
+ (testing "cron"
+ (let [service (tk/get-service app :SchedulerService)
+ num-runs 5 ; let it run a few times, but not too many
+ cron-string "0/10 * * * * ?" ; every 10 seconds
+ p (promise)
+ counter (atom 0)
+ runtimes (atom [])
+ last-completion-time (atom nil)
+ job (fn []
+ (when @last-completion-time
+ (let [runtime (LocalDateTime/now)]
+ (swap! runtimes conj runtime)))
+ (swap! counter inc)
+ (when (= @counter num-runs)
+ (deliver p nil))
+
+ (reset! last-completion-time (LocalDateTime/now)))]
+
+ ; Schedule the job, and wait for it run num-runs times, then stop it.
+ (let [job-id (cron service cron-string job :some-group-identifier)]
+ (deref p)
+ (stop-job service job-id))
+
+ (testing "each runtime should be on a multiple of 10 seconds"
+ (is (every? (fn [^LocalDateTime runtime] (= (rem (.getSecond runtime) 10)
+ 0)) @runtimes)))
+ (testing "throws Illegal Argument exception for invalid cron string"
+ (is (thrown? IllegalArgumentException
+ (cron service "**invalid**" job :some-group-identifier)))))))))
+
+(defn create-custom-date
+ "helper fn to create custom future date"
+ [year month day hour minute second]
+ (let [cal (java.util.Calendar/getInstance)]
+ (.clear cal)
+ (.set cal java.util.Calendar/YEAR year)
+ (.set cal java.util.Calendar/MONTH (dec month)) ;; month in Calendar is zero-indexed
+ (.set cal java.util.Calendar/DAY_OF_MONTH day)
+ (.set cal java.util.Calendar/HOUR_OF_DAY hour) ;; 24-hour format
+ (.set cal java.util.Calendar/MINUTE minute)
+ (.set cal java.util.Calendar/SECOND second)
+ (.set cal java.util.Calendar/MILLISECOND 0)
+ (.getTime cal)))
+
+(deftest ^:integration test-cron-next-valid-time
+ (testing "cron-next-valid-time returns the next runtime of a cron specification from a given date"
+ (with-app-with-empty-config app [scheduler-service]
+ (let [cron-string "0/10 * * * * ?" ; every 10 seconds
+ current-time (create-custom-date 2010 9 17 9 41 0) ;; year-month-day-hours-mins-seconds
+ expected-execution-time (create-custom-date 2010 9 17 9 41 10)
+ service (tk/get-service app :SchedulerService)
+ next-execution-time (cron-next-valid-time service cron-string current-time)]
+ (is (= expected-execution-time next-execution-time))
+ (testing "throws Illegal Argument exception for invalid cron string"
+ (is (thrown? IllegalArgumentException
+ (cron-next-valid-time service "**invalid**" current-time))))))))
+
(deftest ^:integration test-stop-job
(testing "without group-id"
(testing "stop-job lets a job complete but does not run it again"
View it on GitLab: https://salsa.debian.org/clojure-team/trapperkeeper-scheduler-clojure/-/commit/f174db964d8111040f57e52e138fef5a334720db
--
View it on GitLab: https://salsa.debian.org/clojure-team/trapperkeeper-scheduler-clojure/-/commit/f174db964d8111040f57e52e138fef5a334720db
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20260910/c434ac66/attachment.htm>
More information about the pkg-java-commits
mailing list