Переглянути джерело

Convert duration flags, add docs

binwiederhier 1 рік тому
батько
коміт
243123fd7e
5 змінених файлів з 62 додано та 50 видалено
  1. 21 21
      cmd/serve.go
  2. 27 15
      docs/config.md
  3. 2 3
      docs/publish.md
  4. 1 0
      server/config.go
  5. 11 11
      util/time.go

+ 21 - 21
cmd/serve.go

@@ -45,7 +45,7 @@ var flagsServe = append(
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"cache_file", "C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-duration", Aliases: []string{"cache_duration", "b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: util.FormatDuration(server.DefaultCacheDuration), Usage: "buffer messages for this time to allow `since` requests"}),
 	altsrc.NewIntFlag(&cli.IntFlag{Name: "cache-batch-size", Aliases: []string{"cache_batch_size"}, EnvVars: []string{"NTFY_BATCH_SIZE"}, Usage: "max size of messages to batch together when writing to message cache (if zero, writes are synchronous)"}),
-	altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-batch-timeout", Aliases: []string{"cache_batch_timeout"}, EnvVars: []string{"NTFY_CACHE_BATCH_TIMEOUT"}, Usage: "timeout for batched async writes to the message cache (if zero, writes are synchronous)"}),
+	altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-batch-timeout", Aliases: []string{"cache_batch_timeout"}, EnvVars: []string{"NTFY_CACHE_BATCH_TIMEOUT"}, Value: util.FormatDuration(server.DefaultCacheBatchTimeout), Usage: "timeout for batched async writes to the message cache (if zero, writes are synchronous)"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-startup-queries", Aliases: []string{"cache_startup_queries"}, EnvVars: []string{"NTFY_CACHE_STARTUP_QUERIES"}, Usage: "queries run when the cache database is initialized"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}),
@@ -195,57 +195,57 @@ func execServe(c *cli.Context) error {
 	// Convert durations
 	cacheDuration, err := util.ParseDuration(cacheDurationStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid cache duration: %s", cacheDurationStr)
 	}
 	cacheBatchTimeout, err := util.ParseDuration(cacheBatchTimeoutStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid cache batch timeout: %s", cacheBatchTimeoutStr)
 	}
 	attachmentExpiryDuration, err := util.ParseDuration(attachmentExpiryDurationStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid attachment expiry duration: %s", attachmentExpiryDurationStr)
 	}
 	keepaliveInterval, err := util.ParseDuration(keepaliveIntervalStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid keepalive interval: %s", keepaliveIntervalStr)
 	}
 	managerInterval, err := util.ParseDuration(managerIntervalStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid manager interval: %s", managerIntervalStr)
 	}
 	messageDelayLimit, err := util.ParseDuration(messageDelayLimitStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid message delay limit: %s", messageDelayLimitStr)
 	}
 	visitorRequestLimitReplenish, err := util.ParseDuration(visitorRequestLimitReplenishStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid visitor request limit replenish: %s", visitorRequestLimitReplenishStr)
 	}
 	visitorEmailLimitReplenish, err := util.ParseDuration(visitorEmailLimitReplenishStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid visitor email limit replenish: %s", visitorEmailLimitReplenishStr)
 	}
 
 	// Convert sizes to bytes
-	messageSizeLimit, err := parseSize(messageSizeLimitStr, server.DefaultMessageSizeLimit)
+	messageSizeLimit, err := util.ParseSize(messageSizeLimitStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid message size limit: %s", messageSizeLimitStr)
 	}
-	attachmentTotalSizeLimit, err := parseSize(attachmentTotalSizeLimitStr, server.DefaultAttachmentTotalSizeLimit)
+	attachmentTotalSizeLimit, err := util.ParseSize(attachmentTotalSizeLimitStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid attachment total size limit: %s", attachmentTotalSizeLimitStr)
 	}
-	attachmentFileSizeLimit, err := parseSize(attachmentFileSizeLimitStr, server.DefaultAttachmentFileSizeLimit)
+	attachmentFileSizeLimit, err := util.ParseSize(attachmentFileSizeLimitStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid attachment file size limit: %s", attachmentFileSizeLimitStr)
 	}
-	visitorAttachmentTotalSizeLimit, err := parseSize(visitorAttachmentTotalSizeLimitStr, server.DefaultVisitorAttachmentTotalSizeLimit)
+	visitorAttachmentTotalSizeLimit, err := util.ParseSize(visitorAttachmentTotalSizeLimitStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid visitor attachment total size limit: %s", visitorAttachmentTotalSizeLimitStr)
 	}
-	visitorAttachmentDailyBandwidthLimit, err := parseSize(visitorAttachmentDailyBandwidthLimitStr, server.DefaultVisitorAttachmentDailyBandwidthLimit)
+	visitorAttachmentDailyBandwidthLimit, err := util.ParseSize(visitorAttachmentDailyBandwidthLimitStr)
 	if err != nil {
-		return err
+		return fmt.Errorf("invalid visitor attachment daily bandwidth limit: %s", visitorAttachmentDailyBandwidthLimitStr)
 	} else if visitorAttachmentDailyBandwidthLimit > math.MaxInt {
 		return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt)
 	}
@@ -293,8 +293,8 @@ func execServe(c *cli.Context) error {
 		return errors.New("if stripe-secret-key is set, stripe-webhook-key and base-url must also be set")
 	} else if twilioAccount != "" && (twilioAuthToken == "" || twilioPhoneNumber == "" || twilioVerifyService == "" || baseURL == "" || authFile == "") {
 		return errors.New("if twilio-account is set, twilio-auth-token, twilio-phone-number, twilio-verify-service, base-url, and auth-file must also be set")
-	} else if messageSizeLimit > 4096 {
-		log.Warn("message-size-limit is >4K, this is not recommended and largely untested, and may lead to issues with some clients")
+	} else if messageSizeLimit > server.DefaultMessageSizeLimit {
+		log.Warn("message-size-limit is greater than 4K, this is not recommended and largely untested, and may lead to issues with some clients")
 		if messageSizeLimit > 5*1024*1024 {
 			return errors.New("message-size-limit cannot be higher than 5M")
 		}

+ 27 - 15
docs/config.md

@@ -995,6 +995,15 @@ are the easiest), and then configure the following options:
 After you have configured phone calls, create a [tier](#tiers) with a call limit (e.g. `ntfy tier create --call-limit=10 ...`),
 and then assign it to a user. Users may then use the `X-Call` header to receive a phone call when publishing a message.
 
+## Message limits
+There are a few message limits that you can configure:
+
+* `message-size-limit` defines the max size of a message body. Please note message sizes >4K are **not recommended,
+   and largely untested**. The Android/iOS and other clients may not work, or work properly. If FCM and/or APNS is used,
+   the limit should stay 4K, because their limits are around that size. If you increase this size limit regardless, 
+   FCM and APNS will NOT work for large messages.
+* `message-delay-limit` defines the max delay of a message when using the "Delay" header and [scheduled delivery](publish.md#scheduled-delivery).
+
 ## Rate limiting
 !!! info
     Be aware that if you are running ntfy behind a proxy, you must set the `behind-proxy` flag. 
@@ -1092,8 +1101,8 @@ response if no "rate visitor" has been previously registered. This is to avoid b
 To enable subscriber-based rate limiting, set `visitor-subscriber-rate-limiting: true`.
 
 !!! info
-    Due to a denial-of-service issue, support for the `Rate-Topics` header was removed entirely. This is unfortunate,
-    but subscriber-based rate limiting will still work for `up*` topics.
+    Due to a [denial-of-service issue](https://github.com/binwiederhier/ntfy/issues/1048), support for the `Rate-Topics`
+    header was removed entirely. This is unfortunate, but subscriber-based rate limiting will still work for `up*` topics.
 
 ## Tuning for scale
 If you're running ntfy for your home server, you probably don't need to worry about scale at all. In its default config,
@@ -1391,6 +1400,8 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
 | `twilio-verify-service`                    | `NTFY_TWILIO_VERIFY_SERVICE`                    | *string*                                            | -                 | Twilio Verify service SID, e.g. VA12345beefbeef67890beefbeef122586                                                                                                                                                              |
 | `keepalive-interval`                       | `NTFY_KEEPALIVE_INTERVAL`                       | *duration*                                          | 45s               | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
 | `manager-interval`                         | `NTFY_MANAGER_INTERVAL`                         | *duration*                                          | 1m                | Interval in which the manager prunes old messages, deletes topics and prints the stats.                                                                                                                                         |
+| `message-size-limit`                       | `NTFY_MESSAGE_SIZE_LIMIT`                       | *size*                                              | 4K                | The size limit for the message body. Please note that this is largely untested, and that FCM/APNS have limits around 4KB. If you increase this size limit, FCM and APNS will NOT work for large messages.                       |
+| `message-delay-limit`                      | `NTFY_MESSAGE_DELAY_LIMIT`                      | *duration*                                          | 3d                | Amount of time a message can be [scheduled](publish.md#scheduled-delivery) into the future when using the `Delay` header                                                                                                        |
 | `global-topic-limit`                       | `NTFY_GLOBAL_TOPIC_LIMIT`                       | *number*                                            | 15,000            | Rate limiting: Total number of topics before the server rejects new topics.                                                                                                                                                     |
 | `upstream-base-url`                        | `NTFY_UPSTREAM_BASE_URL`                        | *URL*                                               | `https://ntfy.sh` | Forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers                                                                                                                   |
 | `upstream-access-token`                    | `NTFY_UPSTREAM_ACCESS_TOKEN`                    | *string*                                            | `tk_zyYLYj...`    | Access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth                                                                                                  |
@@ -1416,9 +1427,8 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
 | `web-push-file`                            | `NTFY_WEB_PUSH_FILE`                            | *string*                                            | -                 | Web Push: Database file that stores subscriptions                                                                                                                                                                               |
 | `web-push-email-address`                   | `NTFY_WEB_PUSH_EMAIL_ADDRESS`                   | *string*                                            | -                 | Web Push: Sender email address                                                                                                                                                                                                  |
 | `web-push-startup-queries`                 | `NTFY_WEB_PUSH_STARTUP_QUERIES`                 | *string*                                            | -                 | Web Push: SQL queries to run against subscription database at startup                                                                                                                                                           |
-| `message-limit`                            | `NTFY_MESSAGE_LIMIT`                            | *number*                                            | -                 | The size limit (in bytes) for a ntfy message. NOTE: FCM has size limit at 4000 bytes. APNS has size limit at 4KB. If you increase this size limit, FCM and APNS will NOT work for large messages.                                                      |
 
-The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h.   
+The format for a *duration* is: `<number>(smhd)`, e.g. 30s, 20m, 1h or 3d.   
 The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k.
 
 ## Command line options
@@ -1450,7 +1460,7 @@ OPTIONS:
    --log-level-overrides value, --log_level_overrides value [ --log-level-overrides value, --log_level_overrides value ]  set log level overrides [$NTFY_LOG_LEVEL_OVERRIDES]
    --log-format value, --log_format value                                                                                 set log format (default: "text") [$NTFY_LOG_FORMAT]
    --log-file value, --log_file value                                                                                     set log file, default is STDOUT [$NTFY_LOG_FILE]
-   --config value, -c value                                                                                               config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE]
+   --config value, -c value                                                                                               config file (default: "/etc/ntfy/server.yml") [$NTFY_CONFIG_FILE]
    --base-url value, --base_url value, -B value                                                                           externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL]
    --listen-http value, --listen_http value, -l value                                                                     ip:port used as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP]
    --listen-https value, --listen_https value, -L value                                                                   ip:port used as HTTPS listen address [$NTFY_LISTEN_HTTPS]
@@ -1460,19 +1470,19 @@ OPTIONS:
    --cert-file value, --cert_file value, -E value                                                                         certificate file, if listen-https is set [$NTFY_CERT_FILE]
    --firebase-key-file value, --firebase_key_file value, -F value                                                         Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE]
    --cache-file value, --cache_file value, -C value                                                                       cache file used for message caching [$NTFY_CACHE_FILE]
-   --cache-duration since, --cache_duration since, -b since                                                               buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION]
+   --cache-duration since, --cache_duration since, -b since                                                               buffer messages for this time to allow since requests (default: "12h") [$NTFY_CACHE_DURATION]
    --cache-batch-size value, --cache_batch_size value                                                                     max size of messages to batch together when writing to message cache (if zero, writes are synchronous) (default: 0) [$NTFY_BATCH_SIZE]
-   --cache-batch-timeout value, --cache_batch_timeout value                                                               timeout for batched async writes to the message cache (if zero, writes are synchronous) (default: 0s) [$NTFY_CACHE_BATCH_TIMEOUT]
+   --cache-batch-timeout value, --cache_batch_timeout value                                                               timeout for batched async writes to the message cache (if zero, writes are synchronous) (default: "0s") [$NTFY_CACHE_BATCH_TIMEOUT]
    --cache-startup-queries value, --cache_startup_queries value                                                           queries run when the cache database is initialized [$NTFY_CACHE_STARTUP_QUERIES]
    --auth-file value, --auth_file value, -H value                                                                         auth database file used for access control [$NTFY_AUTH_FILE]
    --auth-startup-queries value, --auth_startup_queries value                                                             queries run when the auth database is initialized [$NTFY_AUTH_STARTUP_QUERIES]
    --auth-default-access value, --auth_default_access value, -p value                                                     default permissions if no matching entries in the auth database are found (default: "read-write") [$NTFY_AUTH_DEFAULT_ACCESS]
    --attachment-cache-dir value, --attachment_cache_dir value                                                             cache directory for attached files [$NTFY_ATTACHMENT_CACHE_DIR]
-   --attachment-total-size-limit value, --attachment_total_size_limit value, -A value                                     limit of the on-disk attachment cache (default: 5G) [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT]
-   --attachment-file-size-limit value, --attachment_file_size_limit value, -Y value                                       per-file attachment size limit (e.g. 300k, 2M, 100M) (default: 15M) [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT]
-   --attachment-expiry-duration value, --attachment_expiry_duration value, -X value                                       duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: 3h) [$NTFY_ATTACHMENT_EXPIRY_DURATION]
-   --keepalive-interval value, --keepalive_interval value, -k value                                                       interval of keepalive messages (default: 45s) [$NTFY_KEEPALIVE_INTERVAL]
-   --manager-interval value, --manager_interval value, -m value                                                           interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL]
+   --attachment-total-size-limit value, --attachment_total_size_limit value, -A value                                     limit of the on-disk attachment cache (default: "5G") [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT]
+   --attachment-file-size-limit value, --attachment_file_size_limit value, -Y value                                       per-file attachment size limit (e.g. 300k, 2M, 100M) (default: "15M") [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT]
+   --attachment-expiry-duration value, --attachment_expiry_duration value, -X value                                       duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: "3h") [$NTFY_ATTACHMENT_EXPIRY_DURATION]
+   --keepalive-interval value, --keepalive_interval value, -k value                                                       interval of keepalive messages (default: "45s") [$NTFY_KEEPALIVE_INTERVAL]
+   --manager-interval value, --manager_interval value, -m value                                                           interval of for message pruning and stats printing (default: "1m") [$NTFY_MANAGER_INTERVAL]
    --disallowed-topics value, --disallowed_topics value [ --disallowed-topics value, --disallowed_topics value ]          topics that are not allowed to be used [$NTFY_DISALLOWED_TOPICS]
    --web-root value, --web_root value                                                                                     sets root of the web app (e.g. /, or /app), or disables it (disable) (default: "/") [$NTFY_WEB_ROOT]
    --enable-signup, --enable_signup                                                                                       allows users to sign up via the web app, or API (default: false) [$NTFY_ENABLE_SIGNUP]
@@ -1491,16 +1501,18 @@ OPTIONS:
    --twilio-auth-token value, --twilio_auth_token value                                                                   Twilio auth token [$NTFY_TWILIO_AUTH_TOKEN]
    --twilio-phone-number value, --twilio_phone_number value                                                               Twilio number to use for outgoing calls [$NTFY_TWILIO_PHONE_NUMBER]
    --twilio-verify-service value, --twilio_verify_service value                                                           Twilio Verify service ID, used for phone number verification [$NTFY_TWILIO_VERIFY_SERVICE]
+   --message-size-limit value, --message_size_limit value                                                                 size limit for the message (see docs for limitations) (default: "4K") [$NTFY_MESSAGE_SIZE_LIMIT]
+   --message-delay-limit value, --message_delay_limit value                                                               max duration a message can be scheduled into the future (default: "3d") [$NTFY_MESSAGE_DELAY_LIMIT]
    --global-topic-limit value, --global_topic_limit value, -T value                                                       total number of topics allowed (default: 15000) [$NTFY_GLOBAL_TOPIC_LIMIT]
    --visitor-subscription-limit value, --visitor_subscription_limit value                                                 number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT]
    --visitor-attachment-total-size-limit value, --visitor_attachment_total_size_limit value                               total storage limit used for attachments per visitor (default: "100M") [$NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT]
    --visitor-attachment-daily-bandwidth-limit value, --visitor_attachment_daily_bandwidth_limit value                     total daily attachment download/upload bandwidth limit per visitor (default: "500M") [$NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT]
    --visitor-request-limit-burst value, --visitor_request_limit_burst value                                               initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST]
-   --visitor-request-limit-replenish value, --visitor_request_limit_replenish value                                       interval at which burst limit is replenished (one per x) (default: 5s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
+   --visitor-request-limit-replenish value, --visitor_request_limit_replenish value                                       interval at which burst limit is replenished (one per x) (default: "5s") [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
    --visitor-request-limit-exempt-hosts value, --visitor_request_limit_exempt_hosts value                                 hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit [$NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS]
    --visitor-message-daily-limit value, --visitor_message_daily_limit value                                               max messages per visitor per day, derived from request limit if unset (default: 0) [$NTFY_VISITOR_MESSAGE_DAILY_LIMIT]
    --visitor-email-limit-burst value, --visitor_email_limit_burst value                                                   initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST]
-   --visitor-email-limit-replenish value, --visitor_email_limit_replenish value                                           interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH]
+   --visitor-email-limit-replenish value, --visitor_email_limit_replenish value                                           interval at which burst limit is replenished (one per x) (default: "1h") [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH]
    --visitor-subscriber-rate-limiting, --visitor_subscriber_rate_limiting                                                 enables subscriber-based rate limiting (default: false) [$NTFY_VISITOR_SUBSCRIBER_RATE_LIMITING]
    --behind-proxy, --behind_proxy, -P                                                                                     if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY]
    --stripe-secret-key value, --stripe_secret_key value                                                                   key used for the Stripe API communication, this enables payments [$NTFY_STRIPE_SECRET_KEY]
@@ -1513,6 +1525,6 @@ OPTIONS:
    --web-push-private-key value, --web_push_private_key value                                                             private key used for web push notifications [$NTFY_WEB_PUSH_PRIVATE_KEY]
    --web-push-file value, --web_push_file value                                                                           file used to store web push subscriptions [$NTFY_WEB_PUSH_FILE]
    --web-push-email-address value, --web_push_email_address value                                                         e-mail address of sender, required to use browser push services [$NTFY_WEB_PUSH_EMAIL_ADDRESS]
-   --web-push-startup-queries value, --web_push_startup-queries value                                                     queries run when the web push database is initialized [$NTFY_WEB_PUSH_STARTUP_QUERIES]   
+   --web-push-startup-queries value, --web_push_startup_queries value                                                     queries run when the web push database is initialized [$NTFY_WEB_PUSH_STARTUP_QUERIES]
    --help, -h                                                                                                             show help
 ```

+ 2 - 3
docs/publish.md

@@ -738,9 +738,8 @@ Usage is pretty straight forward. You can set the delivery time using the `X-Del
 `3h`, `2 days`), or a natural language time string (e.g. `10am`, `8:30pm`, `tomorrow, 3pm`, `Tuesday, 7am`, 
 [and more](https://github.com/olebedev/when)). 
 
-As of today, the minimum delay you can set is **10 seconds** and the maximum delay is **3 days**. This can currently
-not be configured otherwise ([let me know](https://github.com/binwiederhier/ntfy/issues) if you'd like to change 
-these limits).
+As of today, the minimum delay you can set is **10 seconds** and the maximum delay is **3 days**. This can be configured
+with the `message-delay-limit` option).
 
 For the purposes of [message caching](config.md#message-cache), scheduled messages are kept in the cache until 12 hours 
 after they were delivered (or whatever the server-side cache duration is set to). For instance, if a message is scheduled

+ 1 - 0
server/config.go

@@ -12,6 +12,7 @@ import (
 const (
 	DefaultListenHTTP                           = ":80"
 	DefaultCacheDuration                        = 12 * time.Hour
+	DefaultCacheBatchTimeout                    = time.Duration(0)
 	DefaultKeepaliveInterval                    = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!)
 	DefaultManagerInterval                      = time.Minute
 	DefaultDelayedSenderInterval                = 10 * time.Second

+ 11 - 11
util/time.go

@@ -10,8 +10,8 @@ import (
 )
 
 var (
-	errUnparsableTime = errors.New("unable to parse time")
-	durationStrRegex  = regexp.MustCompile(`(?i)^(\d+)\s*(d|days?|h|hours?|m|mins?|minutes?|s|secs?|seconds?)$`)
+	errInvalidDuration = errors.New("unable to parse duration")
+	durationStrRegex   = regexp.MustCompile(`(?i)^(\d+)\s*(d|days?|h|hours?|m|mins?|minutes?|s|secs?|seconds?)$`)
 )
 
 const (
@@ -51,7 +51,7 @@ func ParseFutureTime(s string, now time.Time) (time.Time, error) {
 	if err == nil {
 		return t, nil
 	}
-	return time.Time{}, errUnparsableTime
+	return time.Time{}, errInvalidDuration
 }
 
 // ParseDuration is like time.ParseDuration, except that it also understands days (d), which
@@ -65,7 +65,7 @@ func ParseDuration(s string) (time.Duration, error) {
 	if matches != nil {
 		number, err := strconv.Atoi(matches[1])
 		if err != nil {
-			return 0, errUnparsableTime
+			return 0, errInvalidDuration
 		}
 		switch unit := matches[2][0:1]; unit {
 		case "d":
@@ -77,10 +77,10 @@ func ParseDuration(s string) (time.Duration, error) {
 		case "s":
 			return time.Duration(number) * time.Second, nil
 		default:
-			return 0, errUnparsableTime
+			return 0, errInvalidDuration
 		}
 	}
-	return 0, errUnparsableTime
+	return 0, errInvalidDuration
 }
 
 func FormatDuration(d time.Duration) string {
@@ -104,7 +104,7 @@ func parseFromDuration(s string, now time.Time) (time.Time, error) {
 	if err == nil {
 		return now.Add(d), nil
 	}
-	return time.Time{}, errUnparsableTime
+	return time.Time{}, errInvalidDuration
 }
 
 func parseUnixTime(s string, now time.Time) (time.Time, error) {
@@ -112,7 +112,7 @@ func parseUnixTime(s string, now time.Time) (time.Time, error) {
 	if err != nil {
 		return time.Time{}, err
 	} else if int64(t) < now.Unix() {
-		return time.Time{}, errUnparsableTime
+		return time.Time{}, errInvalidDuration
 	}
 	return time.Unix(int64(t), 0).UTC(), nil
 }
@@ -120,7 +120,7 @@ func parseUnixTime(s string, now time.Time) (time.Time, error) {
 func parseNaturalTime(s string, now time.Time) (time.Time, error) {
 	r, err := when.EN.Parse(s, now) // returns "nil, nil" if no matches!
 	if err != nil || r == nil {
-		return time.Time{}, errUnparsableTime
+		return time.Time{}, errInvalidDuration
 	} else if r.Time.After(now) {
 		return r.Time, nil
 	}
@@ -128,9 +128,9 @@ func parseNaturalTime(s string, now time.Time) (time.Time, error) {
 	// simply append "tomorrow, " to it.
 	r, err = when.EN.Parse("tomorrow, "+s, now) // returns "nil, nil" if no matches!
 	if err != nil || r == nil {
-		return time.Time{}, errUnparsableTime
+		return time.Time{}, errInvalidDuration
 	} else if r.Time.After(now) {
 		return r.Time, nil
 	}
-	return time.Time{}, errUnparsableTime
+	return time.Time{}, errInvalidDuration
 }