From 4a81665e18878f4ba5182b34d314333bf1ae9c80 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Jun 23 2023 03:49:02 +0000 Subject: Update patch release/upstream to d7ce2f0b (2023-04-22) Add radicale-3.1.8-fix-main-component-PR-1252.patch Partially align spec file with Fedora variant --- diff --git a/radicale-3.1.8-20230422-d7ce2f0b.patch b/radicale-3.1.8-20230422-d7ce2f0b.patch new file mode 100644 index 0000000..991b71a --- /dev/null +++ b/radicale-3.1.8-20230422-d7ce2f0b.patch @@ -0,0 +1,632 @@ +diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml +index 4b82fa5..4eebaa3 100644 +--- a/.github/workflows/test.yml ++++ b/.github/workflows/test.yml +@@ -6,7 +6,7 @@ jobs: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] +- python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', pypy-3.7, pypy-3.8, pypy-3.9] ++ python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', pypy-3.7, pypy-3.8, pypy-3.9] + exclude: + - os: windows-latest + python-version: pypy-3.7 +diff --git a/CHANGELOG.md b/CHANGELOG.md +index eed6458..0a1577c 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -1,5 +1,7 @@ + # Changelog + ++## master ++ + ## 3.1.8 + + * Fix setuptools requirement if installing wheel +diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md +index f82e77d..663ffef 100644 +--- a/DOCUMENTATION.md ++++ b/DOCUMENTATION.md +@@ -23,7 +23,7 @@ Radicale is a small but powerful CalDAV (calendars, to-do lists) and CardDAV + Radicale is really easy to install and works out-of-the-box. + + ```bash +-python3 -m pip install --upgrade radicale ++python3 -m pip install --upgrade https://github.com/Kozea/Radicale/archive/master.tar.gz + python3 -m radicale --storage-filesystem-folder=~/.var/lib/radicale/collections + ``` + +@@ -36,7 +36,7 @@ Want more? Check the [tutorials](#tutorials) and the + #### What's New? + + Read the +-[changelog on GitHub.](https://github.com/Kozea/Radicale/blob/v3/CHANGELOG.md) ++[changelog on GitHub.](https://github.com/Kozea/Radicale/blob/master/CHANGELOG.md) + + ## Tutorials + +@@ -64,7 +64,7 @@ Then open a console and type: + ```bash + # Run the following command as root or + # add the --user argument to only install for the current user +-$ python3 -m pip install --upgrade radicale ++$ python3 -m pip install --upgrade https://github.com/Kozea/Radicale/archive/master.tar.gz + $ python3 -m radicale --storage-filesystem-folder=~/.var/lib/radicale/collections + ``` + +@@ -82,7 +82,7 @@ click on "Install now". Wait a couple of minutes, it's done! + Launch a command prompt and type: + + ```powershell +-python -m pip install --upgrade radicale ++python -m pip install --upgrade https://github.com/Kozea/Radicale/archive/master.tar.gz + python -m radicale --storage-filesystem-folder=~/radicale/collections + ``` + +@@ -1348,7 +1348,7 @@ add new features, fix bugs or update the documentation. + #### Documentation + + To change or complement the documentation create a pull request to +-[DOCUMENTATION.md](https://github.com/Kozea/Radicale/blob/v3/DOCUMENTATION.md). ++[DOCUMENTATION.md](https://github.com/Kozea/Radicale/blob/master/DOCUMENTATION.md). + + ## Download + +diff --git a/Dockerfile b/Dockerfile +index cb14a58..1bfc82a 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -2,8 +2,8 @@ + + FROM python:3-alpine + +-# Version of Radicale +-ARG VERSION=v3 ++# Version of Radicale (e.g. v3) ++ARG VERSION=master + # Persistent storage for data + VOLUME /var/lib/radicale + # TCP port of Radicale +diff --git a/README.md b/README.md +index 7ee078f..aed74fc 100644 +--- a/README.md ++++ b/README.md +@@ -1,7 +1,7 @@ + # Radicale + +-[![Test](https://github.com/Kozea/Radicale/actions/workflows/test.yml/badge.svg?branch=v3)](https://github.com/Kozea/Radicale/actions/workflows/test.yml) +-[![Coverage Status](https://coveralls.io/repos/github/Kozea/Radicale/badge.svg?branch=v3)](https://coveralls.io/github/Kozea/Radicale?branch=v3) ++[![Test](https://github.com/Kozea/Radicale/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/Kozea/Radicale/actions/workflows/test.yml) ++[![Coverage Status](https://coveralls.io/repos/github/Kozea/Radicale/badge.svg?branch=master)](https://coveralls.io/github/Kozea/Radicale?branch=master) + + Radicale is a small but powerful CalDAV (calendars, to-do lists) and CardDAV + (contacts) server, that: +@@ -17,4 +17,4 @@ Radicale is a small but powerful CalDAV (calendars, to-do lists) and CardDAV + * Is GPLv3-licensed free software. + + For the complete documentation, please visit +-[Radicale v3 Documentation](https://radicale.org/v3.html). ++[Radicale master Documentation](https://radicale.org/master.html). +diff --git a/radicale/app/get.py b/radicale/app/get.py +index 7e5feeb..d8b0152 100644 +--- a/radicale/app/get.py ++++ b/radicale/app/get.py +@@ -45,8 +45,8 @@ def propose_filename(collection: storage.BaseCollection) -> str: + + class ApplicationPartGet(ApplicationBase): + +- def _content_disposition_attachement(self, filename: str) -> str: +- value = "attachement" ++ def _content_disposition_attachment(self, filename: str) -> str: ++ value = "attachment" + try: + encoded_filename = quote(filename, encoding=self._encoding) + except UnicodeEncodeError: +@@ -91,7 +91,7 @@ class ApplicationPartGet(ApplicationBase): + return (httputils.NOT_ALLOWED if limited_access else + httputils.DIRECTORY_LISTING) + content_type = xmlutils.MIMETYPES[item.tag] +- content_disposition = self._content_disposition_attachement( ++ content_disposition = self._content_disposition_attachment( + propose_filename(item)) + elif limited_access: + return httputils.NOT_ALLOWED +diff --git a/radicale/app/move.py b/radicale/app/move.py +index fda8525..0c38eed 100644 +--- a/radicale/app/move.py ++++ b/radicale/app/move.py +@@ -18,6 +18,7 @@ + # along with Radicale. If not, see . + + import posixpath ++import re + from http import client + from urllib.parse import urlparse + +@@ -26,6 +27,21 @@ from radicale.app.base import Access, ApplicationBase + from radicale.log import logger + + ++def get_server_netloc(environ: types.WSGIEnviron, force_port: bool = False): ++ if environ.get("HTTP_X_FORWARDED_HOST"): ++ host = environ["HTTP_X_FORWARDED_HOST"] ++ proto = environ.get("HTTP_X_FORWARDED_PROTO") or "http" ++ port = "443" if proto == "https" else "80" ++ else: ++ host = environ.get("HTTP_HOST") or environ["SERVER_NAME"] ++ proto = environ["wsgi.url_scheme"] ++ port = environ["SERVER_PORT"] ++ if (not force_port and port == ("443" if proto == "https" else "80") or ++ re.search(r":\d+$", host)): ++ return host ++ return host + ":" + port ++ ++ + class ApplicationPartMove(ApplicationBase): + + def do_MOVE(self, environ: types.WSGIEnviron, base_prefix: str, +@@ -33,7 +49,11 @@ class ApplicationPartMove(ApplicationBase): + """Manage MOVE request.""" + raw_dest = environ.get("HTTP_DESTINATION", "") + to_url = urlparse(raw_dest) +- if to_url.netloc != environ["HTTP_HOST"]: ++ to_netloc_with_port = to_url.netloc ++ if to_url.port is None: ++ to_netloc_with_port += (":443" if to_url.scheme == "https" ++ else ":80") ++ if to_netloc_with_port != get_server_netloc(environ, force_port=True): + logger.info("Unsupported destination address: %r", raw_dest) + # Remote destination server, not supported + return httputils.REMOTE_DESTINATION +diff --git a/radicale/item/filter.py b/radicale/item/filter.py +index 587dc36..6a89ffa 100644 +--- a/radicale/item/filter.py ++++ b/radicale/item/filter.py +@@ -468,7 +468,15 @@ def text_match(vobject_item: vobject.base.Component, + match(attrib) for child in children + for attrib in child.params.get(attrib_name, [])) + else: +- condition = any(match(child.value) for child in children) ++ res = [] ++ for child in children: ++ # Some filters such as CATEGORIES provide a list in child.value ++ if type(child.value) is list: ++ for value in child.value: ++ res.append(match(value)) ++ else: ++ res.append(match(child.value)) ++ condition = any(res) + if filter_.get("negate-condition") == "yes": + return not condition + return condition +diff --git a/radicale/log.py b/radicale/log.py +index eaa842b..8d54a1b 100644 +--- a/radicale/log.py ++++ b/radicale/log.py +@@ -25,16 +25,25 @@ Log messages are sent to the first available target of: + + """ + ++import contextlib ++import io + import logging + import os ++import socket ++import struct + import sys + import threading +-from typing import Any, Callable, ClassVar, Dict, Iterator, Union ++import time ++from typing import (Any, Callable, ClassVar, Dict, Iterator, Mapping, Optional, ++ Tuple, Union, cast) + + from radicale import types + + LOGGER_NAME: str = "radicale" +-LOGGER_FORMAT: str = "[%(asctime)s] [%(ident)s] [%(levelname)s] %(message)s" ++LOGGER_FORMATS: Mapping[str, str] = { ++ "verbose": "[%(asctime)s] [%(ident)s] [%(levelname)s] %(message)s", ++ "journal": "[%(ident)s] [%(levelname)s] %(message)s", ++} + DATE_FORMAT: str = "%Y-%m-%d %H:%M:%S %z" + + logger: logging.Logger = logging.getLogger(LOGGER_NAME) +@@ -59,12 +68,17 @@ class IdentLogRecordFactory: + + def __call__(self, *args: Any, **kwargs: Any) -> logging.LogRecord: + record = self._upstream_factory(*args, **kwargs) +- ident = "%d" % os.getpid() +- main_thread = threading.main_thread() +- current_thread = threading.current_thread() +- if current_thread.name and main_thread != current_thread: +- ident += "/%s" % current_thread.name ++ ident = ("%d" % record.process if record.process is not None ++ else record.processName or "unknown") ++ tid = None ++ if record.thread is not None: ++ if record.thread != threading.main_thread().ident: ++ ident += "/%s" % (record.threadName or "unknown") ++ if (sys.version_info >= (3, 8) and ++ record.thread == threading.get_ident()): ++ tid = threading.get_native_id() + record.ident = ident # type:ignore[attr-defined] ++ record.tid = tid # type:ignore[attr-defined] + return record + + +@@ -75,19 +89,102 @@ class ThreadedStreamHandler(logging.Handler): + terminator: ClassVar[str] = "\n" + + _streams: Dict[int, types.ErrorStream] ++ _journal_stream_id: Optional[Tuple[int, int]] ++ _journal_socket: Optional[socket.socket] ++ _journal_socket_failed: bool ++ _formatters: Mapping[str, logging.Formatter] ++ _formatter: Optional[logging.Formatter] + +- def __init__(self) -> None: ++ def __init__(self, format_name: Optional[str] = None) -> None: + super().__init__() + self._streams = {} ++ self._journal_stream_id = None ++ with contextlib.suppress(TypeError, ValueError): ++ dev, inode = os.environ.get("JOURNAL_STREAM", "").split(":", 1) ++ self._journal_stream_id = (int(dev), int(inode)) ++ self._journal_socket = None ++ self._journal_socket_failed = False ++ self._formatters = {name: logging.Formatter(fmt, DATE_FORMAT) ++ for name, fmt in LOGGER_FORMATS.items()} ++ self._formatter = (self._formatters[format_name] ++ if format_name is not None else None) ++ ++ def _get_formatter(self, default_format_name: str) -> logging.Formatter: ++ return self._formatter or self._formatters[default_format_name] ++ ++ def _detect_journal(self, stream: types.ErrorStream) -> bool: ++ if not self._journal_stream_id or not isinstance(stream, io.IOBase): ++ return False ++ try: ++ stat = os.fstat(stream.fileno()) ++ except OSError: ++ return False ++ return self._journal_stream_id == (stat.st_dev, stat.st_ino) ++ ++ @staticmethod ++ def _encode_journal(data: Mapping[str, Optional[Union[str, int]]] ++ ) -> bytes: ++ msg = b"" ++ for key, value in data.items(): ++ if value is None: ++ continue ++ keyb = key.encode() ++ valueb = str(value).encode() ++ if b"\n" in valueb: ++ msg += (keyb + b"\n" + ++ struct.pack(" bool: ++ if not self._journal_socket: ++ # Try to connect to systemd journal socket ++ if self._journal_socket_failed or not hasattr(socket, "AF_UNIX"): ++ return False ++ journal_socket = None ++ try: ++ journal_socket = socket.socket( ++ socket.AF_UNIX, socket.SOCK_DGRAM) ++ journal_socket.connect("/run/systemd/journal/socket") ++ except OSError as e: ++ self._journal_socket_failed = True ++ if journal_socket: ++ journal_socket.close() ++ # Log after setting `_journal_socket_failed` to prevent loop! ++ logger.error("Failed to connect to systemd journal: %s", ++ e, exc_info=True) ++ return False ++ self._journal_socket = journal_socket ++ ++ priority = {"DEBUG": 7, ++ "INFO": 6, ++ "WARNING": 4, ++ "ERROR": 3, ++ "CRITICAL": 2}.get(record.levelname, 4) ++ timestamp = time.strftime("%Y-%m-%dT%H:%M:%S.%%03dZ", ++ time.gmtime(record.created)) % record.msecs ++ data = {"PRIORITY": priority, ++ "TID": cast(Optional[int], getattr(record, "tid", None)), ++ "SYSLOG_IDENTIFIER": record.name, ++ "SYSLOG_FACILITY": 1, ++ "SYSLOG_PID": record.process, ++ "SYSLOG_TIMESTAMP": timestamp, ++ "CODE_FILE": record.pathname, ++ "CODE_LINE": record.lineno, ++ "CODE_FUNC": record.funcName, ++ "MESSAGE": self._get_formatter("journal").format(record)} ++ self._journal_socket.sendall(self._encode_journal(data)) ++ return True + + def emit(self, record: logging.LogRecord) -> None: + try: + stream = self._streams.get(threading.get_ident(), sys.stderr) +- msg = self.format(record) +- stream.write(msg) +- stream.write(self.terminator) +- if hasattr(stream, "flush"): +- stream.flush() ++ if self._detect_journal(stream) and self._try_emit_journal(record): ++ return ++ msg = self._get_formatter("verbose").format(record) ++ stream.write(msg + self.terminator) ++ stream.flush() + except Exception: + self.handleError(record) + +@@ -111,13 +208,16 @@ def register_stream(stream: types.ErrorStream) -> Iterator[None]: + def setup() -> None: + """Set global logging up.""" + global register_stream +- handler = ThreadedStreamHandler() +- logging.basicConfig(format=LOGGER_FORMAT, datefmt=DATE_FORMAT, +- handlers=[handler]) ++ format_name = os.environ.get("RADICALE_LOG_FORMAT") or None ++ sane_format_name = format_name if format_name in LOGGER_FORMATS else None ++ handler = ThreadedStreamHandler(sane_format_name) ++ logging.basicConfig(handlers=[handler]) + register_stream = handler.register_stream + log_record_factory = IdentLogRecordFactory(logging.getLogRecordFactory()) + logging.setLogRecordFactory(log_record_factory) + set_level(logging.WARNING) ++ if format_name != sane_format_name: ++ logger.error("Invalid RADICALE_LOG_FORMAT: %r", format_name) + + + def set_level(level: Union[int, str]) -> None: +diff --git a/radicale/server.py b/radicale/server.py +index 6cb4c7b..62fe4ef 100644 +--- a/radicale/server.py ++++ b/radicale/server.py +@@ -58,11 +58,16 @@ elif sys.platform == "win32": + + + # IPv4 (host, port) and IPv6 (host, port, flowinfo, scopeid) +-ADDRESS_TYPE = Union[Tuple[str, int], Tuple[str, int, int, int]] ++ADDRESS_TYPE = Union[Tuple[Union[str, bytes, bytearray], int], ++ Tuple[str, int, int, int]] + + + def format_address(address: ADDRESS_TYPE) -> str: +- return "[%s]:%d" % address[:2] ++ host, port, *_ = address ++ if not isinstance(host, str): ++ raise NotImplementedError("Unsupported address format: %r" % ++ (address,)) ++ return "[%s]:%d" % (host, port) + + + class ParallelHTTPServer(socketserver.ThreadingMixIn, +diff --git a/radicale/tests/__init__.py b/radicale/tests/__init__.py +index 2e13256..942cbe8 100644 +--- a/radicale/tests/__init__.py ++++ b/radicale/tests/__init__.py +@@ -25,6 +25,7 @@ import logging + import shutil + import sys + import tempfile ++import wsgiref.util + import xml.etree.ElementTree as ET + from io import BytesIO + from typing import Any, Dict, List, Optional, Tuple, Union +@@ -83,11 +84,12 @@ class BaseTest: + login.encode(encoding)).decode() + environ["REQUEST_METHOD"] = method.upper() + environ["PATH_INFO"] = path +- if data: ++ if data is not None: + data_bytes = data.encode(encoding) + environ["wsgi.input"] = BytesIO(data_bytes) + environ["CONTENT_LENGTH"] = str(len(data_bytes)) + environ["wsgi.errors"] = sys.stderr ++ wsgiref.util.setup_testing_defaults(environ) + status = headers = None + + def start_response(status_: str, headers_: List[Tuple[str, str]] +@@ -137,8 +139,8 @@ class BaseTest: + status, _, answer = self.request("GET", path, check=check, **kwargs) + return status, answer + +- def post(self, path: str, data: str = None, check: Optional[int] = 200, +- **kwargs) -> Tuple[int, str]: ++ def post(self, path: str, data: Optional[str] = None, ++ check: Optional[int] = 200, **kwargs) -> Tuple[int, str]: + status, _, answer = self.request("POST", path, data, check=check, + **kwargs) + return status, answer +diff --git a/radicale/tests/static/event1.ics b/radicale/tests/static/event1.ics +index bc04d80..4e66917 100644 +--- a/radicale/tests/static/event1.ics ++++ b/radicale/tests/static/event1.ics +@@ -25,6 +25,7 @@ LAST-MODIFIED:20130902T150158Z + DTSTAMP:20130902T150158Z + UID:event1 + SUMMARY:Event ++CATEGORIES:some_category1,another_category2 + ORGANIZER:mailto:unclesam@example.com + ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Jane Doe:MAILTO:janedoe@example.com + ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com";PARTSTAT=ACCEPTED;CN=John Doe:MAILTO:johndoe@example.com +diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py +index 5ea37bf..a0d3d53 100644 +--- a/radicale/tests/test_base.py ++++ b/radicale/tests/test_base.py +@@ -355,7 +355,7 @@ permissions: RrWw""") + path2 = "/calendar.ics/event2.ics" + self.put(path1, event) + self.request("MOVE", path1, check=201, +- HTTP_DESTINATION=path2, HTTP_HOST="") ++ HTTP_DESTINATION="http://127.0.0.1/"+path2) + self.get(path1, check=404) + self.get(path2) + +@@ -368,7 +368,7 @@ permissions: RrWw""") + path2 = "/calendar2.ics/event2.ics" + self.put(path1, event) + self.request("MOVE", path1, check=201, +- HTTP_DESTINATION=path2, HTTP_HOST="") ++ HTTP_DESTINATION="http://127.0.0.1/"+path2) + self.get(path1, check=404) + self.get(path2) + +@@ -382,7 +382,7 @@ permissions: RrWw""") + self.put(path1, event) + self.put("/calendar2.ics/event1.ics", event) + status, _, answer = self.request( +- "MOVE", path1, HTTP_DESTINATION=path2, HTTP_HOST="") ++ "MOVE", path1, HTTP_DESTINATION="http://127.0.0.1/"+path2) + assert status in (403, 409) + xml = DefusedET.fromstring(answer) + assert xml.tag == xmlutils.make_clark("D:error") +@@ -398,9 +398,9 @@ permissions: RrWw""") + self.put(path1, event) + self.put(path2, event) + self.request("MOVE", path1, check=412, +- HTTP_DESTINATION=path2, HTTP_HOST="") +- self.request("MOVE", path1, check=204, +- HTTP_DESTINATION=path2, HTTP_HOST="", HTTP_OVERWRITE="T") ++ HTTP_DESTINATION="http://127.0.0.1/"+path2) ++ self.request("MOVE", path1, check=204, HTTP_OVERWRITE="T", ++ HTTP_DESTINATION="http://127.0.0.1/"+path2) + + def test_move_between_colections_overwrite_uid_conflict(self) -> None: + """Move a item to a collection which already contains the item with +@@ -413,8 +413,9 @@ permissions: RrWw""") + path2 = "/calendar2.ics/event2.ics" + self.put(path1, event1) + self.put(path2, event2) +- status, _, answer = self.request("MOVE", path1, HTTP_DESTINATION=path2, +- HTTP_HOST="", HTTP_OVERWRITE="T") ++ status, _, answer = self.request( ++ "MOVE", path1, HTTP_OVERWRITE="T", ++ HTTP_DESTINATION="http://127.0.0.1/"+path2) + assert status in (403, 409) + xml = DefusedET.fromstring(answer) + assert xml.tag == xmlutils.make_clark("D:error") +@@ -916,6 +917,22 @@ permissions: RrWw""") + event + + ++"""]) ++ assert "/calendar.ics/event1.ics" in self._test_filter(["""\ ++ ++ ++ ++ some_category1 ++ ++ ++"""]) ++ assert "/calendar.ics/event1.ics" in self._test_filter(["""\ ++ ++ ++ ++ some_category1 ++ ++ + """]) + assert "/calendar.ics/event1.ics" not in self._test_filter(["""\ + +@@ -1471,7 +1488,7 @@ permissions: RrWw""") + sync_token, responses = self._report_sync_token(calendar_path) + assert len(responses) == 1 and responses[event1_path] == 200 + self.request("MOVE", event1_path, check=201, +- HTTP_DESTINATION=event2_path, HTTP_HOST="") ++ HTTP_DESTINATION="http://127.0.0.1/"+event2_path) + sync_token, responses = self._report_sync_token( + calendar_path, sync_token) + if not self.full_sync_token_support and not sync_token: +@@ -1490,9 +1507,9 @@ permissions: RrWw""") + sync_token, responses = self._report_sync_token(calendar_path) + assert len(responses) == 1 and responses[event1_path] == 200 + self.request("MOVE", event1_path, check=201, +- HTTP_DESTINATION=event2_path, HTTP_HOST="") ++ HTTP_DESTINATION="http://127.0.0.1/"+event2_path) + self.request("MOVE", event2_path, check=201, +- HTTP_DESTINATION=event1_path, HTTP_HOST="") ++ HTTP_DESTINATION="http://127.0.0.1/"+event1_path) + sync_token, responses = self._report_sync_token( + calendar_path, sync_token) + if not self.full_sync_token_support and not sync_token: +diff --git a/radicale/types.py b/radicale/types.py +index 0eb3fd6..c7e1904 100644 +--- a/radicale/types.py ++++ b/radicale/types.py +@@ -50,8 +50,8 @@ if sys.version_info >= (3, 8): + + @runtime_checkable + class ErrorStream(Protocol): +- def flush(self) -> None: ... +- def write(self, s: str) -> None: ... ++ def flush(self) -> object: ... ++ def write(self, s: str) -> object: ... + else: + ErrorStream = Any + InputStream = Any +diff --git a/setup.cfg b/setup.cfg +index a77b43b..fe2038f 100644 +--- a/setup.cfg ++++ b/setup.cfg +@@ -28,8 +28,9 @@ known_third_party = defusedxml,passlib,pkg_resources,pytest,vobject + + [flake8] + # Only enable default tests (https://github.com/PyCQA/flake8/issues/790#issuecomment-812823398) +-select = E,F,W,C90,DOES-NOT-EXIST +-ignore = E121,E123,E126,E226,E24,E704,W503,W504,DOES-NOT-EXIST ++# DNE: DOES-NOT-EXIST ++select = E,F,W,C90,DNE000 ++ignore = E121,E123,E126,E226,E24,E704,W503,W504,DNE000 + extend-exclude = build + + [mypy] +diff --git a/setup.py b/setup.py +index dafea7a..144e77c 100644 +--- a/setup.py ++++ b/setup.py +@@ -19,7 +19,7 @@ from setuptools import find_packages, setup + + # When the version is updated, a new section in the CHANGELOG.md file must be + # added too. +-VERSION = "3.1.8" ++VERSION = "3.dev" + + with open("README.md", encoding="utf-8") as f: + long_description = f.read() +@@ -33,7 +33,7 @@ install_requires = ["defusedxml", "passlib", "vobject>=0.9.6", + "setuptools; python_version<'3.9'"] + bcrypt_requires = ["passlib[bcrypt]", "bcrypt"] + # typeguard requires pytest<7 +-test_requires = ["pytest<7", "typeguard", "waitress", *bcrypt_requires] ++test_requires = ["pytest<7", "typeguard<3", "waitress", *bcrypt_requires] + + setup( + name="Radicale", +@@ -53,7 +53,7 @@ setup( + install_requires=install_requires, + extras_require={"test": test_requires, "bcrypt": bcrypt_requires}, + keywords=["calendar", "addressbook", "CalDAV", "CardDAV"], +- python_requires=">=3.6.0", ++ python_requires=">=3.7.0", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", +@@ -63,11 +63,11 @@ setup( + "License :: OSI Approved :: GNU General Public License (GPL)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", +- "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ++ "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Office/Business :: Groupware"]) diff --git a/radicale-3.1.8-fix-main-component-PR-1252.patch b/radicale-3.1.8-fix-main-component-PR-1252.patch new file mode 100644 index 0000000..5016406 --- /dev/null +++ b/radicale-3.1.8-fix-main-component-PR-1252.patch @@ -0,0 +1,27 @@ +diff --git a/radicale/item/filter.py b/radicale/item/filter.py +index 6a89ffa..217f503 100644 +--- a/radicale/item/filter.py ++++ b/radicale/item/filter.py +@@ -225,6 +225,7 @@ def visit_time_ranges(vobject_item: vobject.base.Component, child_name: str, + def get_children(components: Iterable[vobject.base.Component]) -> Iterator[ + Tuple[vobject.base.Component, bool, List[date]]]: + main = None ++ rec_main = None + recurrences = [] + for comp in components: + if hasattr(comp, "recurrence_id") and comp.recurrence_id.value: +@@ -232,11 +233,14 @@ def visit_time_ranges(vobject_item: vobject.base.Component, child_name: str, + if comp.rruleset: + # Prevent possible infinite loop + raise ValueError("Overwritten recurrence with RRULESET") ++ rec_main = comp + yield comp, True, [] + else: + if main is not None: + raise ValueError("Multiple main components") + main = comp ++ if main is None and len(recurrences) == 1: ++ main = rec_main + if main is None: + raise ValueError("Main component missing") + yield main, False, recurrences diff --git a/radicale.spec b/radicale.spec index f0a9042..b5d6c32 100644 --- a/radicale.spec +++ b/radicale.spec @@ -3,8 +3,7 @@ # unfortunately, radicale major version upgrades are breakable updates, therefore # Fedora >= 31: introduce radicale3 -# EL7: introduce radicale2 & radicale3 (planned) -# EL8: radicale3 +# EL >=8: radicale3 # # Note: this is the complex spec file for Fedora+EL @@ -19,7 +18,7 @@ %define bundled_version_setuptools_scm 1.17.0 %define radicale_version 3.1.8 -%define radicale_release 52 +%define radicale_release 53 %define radicale_name radicale @@ -77,7 +76,8 @@ Source50: %{name}-test-example.ics Source51: %{name}-test-example.vcf Patch0: %{name}-config-storage-hooks-SELinux-note.patch -Patch1: %{name}-3.1.8-20230322-6ae831a3.patch +Patch1: %{name}-3.1.8-20230422-d7ce2f0b.patch +Patch2: %{name}-3.1.8-fix-main-component-PR-1252.patch %if "%{?bundled_version_dateutil}" != "" Source100: https://github.com/dateutil/dateutil/releases/download/%{bundled_version_dateutil}/python-dateutil-%{bundled_version_dateutil}.tar.gz @@ -380,9 +380,6 @@ for d in %{buildroot}%{python3_sitearch}/{dateutil,vobject,defusedxm,passlib}*; [ -d $d ] && %{__mv} $d %{buildroot}%{usersitedir}/ done -# Create folder where the calendar will be stored (and radicale's home directory) -install -d -p %{buildroot}%{_sharedstatedir}/%{name}/ - # create USER_SITE directory install -d -p %{buildroot}%{dirname:%{sharedstatesitedir}} @@ -421,6 +418,9 @@ sed -i 's|^#!/usr/bin/env python3$|#!/usr/bin/python3|' %{buildroot}%{_datadir}/ mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d/ install -p -m 644 %{SOURCE3} %{buildroot}%{_sysconfdir}/httpd/conf.d/%{name}.conf +# Create folder where the calendar will be stored (and radicale's home directory) +install -d -p %{buildroot}%{_sharedstatedir}/%{name}/ + install -D -p -m 644 %{SOURCE1} %{buildroot}%{_unitdir}/%{name}.service install -D -p -m 644 %{SOURCE7} %{buildroot}%{_tmpfilesdir}/%{name}.conf @@ -598,6 +598,11 @@ fi %changelog +* Wed Jun 21 2023 Peter Bieringer - 3.1.8-53 +- Update patch release/upstream to d7ce2f0b (2023-04-22) +- Add radicale-3.1.8-fix-main-component-PR-1252.patch +- Partially align spec file with Fedora variant + * Sat Apr 15 2023 Peter Bieringer - 3.1.8-51 - Move bundled required modules to USER_SITE directory - Disable bundled dateutil (EPEL provides now 2.8.2)