Mercurial > louis > mq > lightsd
changeset 461:b19134003e8f
Finish the mq so we can sort-out the last details of the CI setup
author | Louis Opter <kalessin@kalessin.fr> |
---|---|
date | Sat, 28 May 2016 19:00:30 -0700 |
parents | 6547b28d9983 |
children | cd963f484771 |
files | add_make_release.patch docs_setup_alabaster_and_ga.patch dont_use_ev_assign.patch fix_freebsd_build.patch network_discovery.patch optional_jsonrpc_args.patch series white_colors_clarifications.patch |
diffstat | 8 files changed, 0 insertions(+), 4030 deletions(-) [+] |
line wrap: on
line diff
--- a/add_make_release.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1622 +0,0 @@ -# HG changeset patch -# Parent 4b74a5b40d1d3fdeab6a639975ae941544839e33 -Monorepo the packages and setup continuous integration - -diff --git a/CMakeLists.txt b/CMakeLists.txt ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -3,10 +3,8 @@ - - PROJECT(LIGHTSD C) - --SET(CPACK_PACKAGE_VERSION_MAJOR "1") --SET(CPACK_PACKAGE_VERSION_MINOR "1") --SET(CPACK_PACKAGE_VERSION_PATCH "2") --SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") -+SET(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${LIGHTSD_SOURCE_DIR}/CMakeScripts") -+INCLUDE(LightsdVersion) - - MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") - MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") -@@ -15,8 +13,6 @@ - MESSAGE(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") - MESSAGE(STATUS "Source directory: ${LIGHTSD_SOURCE_DIR}") - --SET(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${LIGHTSD_SOURCE_DIR}/CMakeScripts") -- - ENABLE_TESTING() - - ### Platform checks ############################################################ -@@ -26,6 +22,14 @@ - FIND_PACKAGE(Endian REQUIRED) - FIND_PACKAGE(Sphinx) - -+# Release tools: -+FIND_PACKAGE(Gzip) -+FIND_PACKAGE(Hg) -+FIND_PACKAGE(PythonInterp 3.3) # >= 3.3 for os.replace -+FIND_PACKAGE(BsdTar) -+FIND_PACKAGE(Virtualenv) -+FIND_PACKAGE(Xz) -+ - INCLUDE(CheckFunctionExists) - INCLUDE(CheckVariableExists) - INCLUDE(TestBigEndian) -@@ -92,9 +96,7 @@ - - # 2.8.11 is the first version with TARGET_INCLUDE_DIRECTORIES: - IF (CMAKE_VERSION VERSION_GREATER 2.8.10) -- CONFIGURE_FILE( -- CTestCustom.cmake.in "${LIGHTSD_BINARY_DIR}/CTestCustom.cmake" @ONLY -- ) -+ CONFIGURE_FILE(CTestCustom.cmake.in "${LIGHTSD_BINARY_DIR}/CTestCustom.cmake" @ONLY) - ADD_SUBDIRECTORY(tests) - ELSE () - MESSAGE( -@@ -105,7 +107,34 @@ - ENDIF () - - IF (SPHINX_FOUND) -+ MESSAGE(STATUS "Sphinx found, docs generation enabled") - ADD_SUBDIRECTORY(docs) -+ELSE () -+ MESSAGE(STATUS "Shpinx wasn't found, docs generation disabled") -+ENDIF () -+ -+IF ( -+ PYTHONINTERP_FOUND -+ AND PYTHON_VERSION_MAJOR EQUAL 3 -+ AND PYTHON_VERSION_MINOR GREATER 2 -+ AND VIRTUALENV_FOUND -+ AND HG_FOUND -+ AND BSDTAR_FOUND -+ AND GZIP_FOUND -+ AND XZ_FOUND -+) -+ MESSAGE( -+ STATUS -+ "Python >= 3.3, virtualenv, bsdtar, gzip, xz, and mercurial (hg) " -+ "found, release commands enabled" -+ ) -+ ADD_SUBDIRECTORY(dist) -+ELSE () -+ MESSAGE( -+ STATUS -+ "Python >= 3.3 and/or virtualenv, bsdtar, gzip, xz, mercurial (hg) " -+ "weren't found, release commands disabled" -+ ) - ENDIF () - - INSTALL( -diff --git a/CMakeScripts/FindBsdTar.cmake b/CMakeScripts/FindBsdTar.cmake -new file mode 100644 ---- /dev/null -+++ b/CMakeScripts/FindBsdTar.cmake -@@ -0,0 +1,6 @@ -+FIND_PROGRAM( -+ BSDTAR_EXECUTABLE NAMES bsdtar -+ DOC "Path to the bsdtar executable" -+) -+ -+FIND_PACKAGE_HANDLE_STANDARD_ARGS(BsdTar DEFAULT_MSG BSDTAR_EXECUTABLE) -diff --git a/CMakeScripts/FindGzip.cmake b/CMakeScripts/FindGzip.cmake -new file mode 100644 ---- /dev/null -+++ b/CMakeScripts/FindGzip.cmake -@@ -0,0 +1,6 @@ -+FIND_PROGRAM( -+ GZIP_EXECUTABLE NAMES gzip -+ DOC "Path to the gzip executable" -+) -+ -+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Gzip DEFAULT_MSG GZIP_EXECUTABLE) -diff --git a/CMakeScripts/FindSphinx.cmake b/CMakeScripts/FindSphinx.cmake ---- a/CMakeScripts/FindSphinx.cmake -+++ b/CMakeScripts/FindSphinx.cmake -@@ -1,7 +1,7 @@ - FIND_PROGRAM( - SPHINX_EXECUTABLE - NAMES sphinx-build sphinx-build2 -- DOC "Path to sphinx-build executable" -+ DOC "Path to the sphinx-build executable" - ) - - FIND_PACKAGE_HANDLE_STANDARD_ARGS(Sphinx DEFAULT_MSG SPHINX_EXECUTABLE) -diff --git a/CMakeScripts/FindVirtualenv.cmake b/CMakeScripts/FindVirtualenv.cmake -new file mode 100644 ---- /dev/null -+++ b/CMakeScripts/FindVirtualenv.cmake -@@ -0,0 +1,7 @@ -+FIND_PROGRAM( -+ VIRTUALENV_EXECUTABLE -+ NAMES virtualenv virtualenv2 -+ DOC "Path to the virtualenv executable" -+) -+ -+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Virtualenv DEFAULT_MSG VIRTUALENV_EXECUTABLE) -diff --git a/CMakeScripts/FindXz.cmake b/CMakeScripts/FindXz.cmake -new file mode 100644 ---- /dev/null -+++ b/CMakeScripts/FindXz.cmake -@@ -0,0 +1,6 @@ -+FIND_PROGRAM( -+ XZ_EXECUTABLE NAMES xz -+ DOC "Path to the xz executable" -+) -+ -+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Xz DEFAULT_MSG XZ_EXECUTABLE) -diff --git a/CMakeScripts/LightsdVersion.cmake b/CMakeScripts/LightsdVersion.cmake -new file mode 100644 ---- /dev/null -+++ b/CMakeScripts/LightsdVersion.cmake -@@ -0,0 +1,7 @@ -+# NOTE: auto-generated by the release target -+SET(CPACK_PACKAGE_VERSION_MAJOR "1") -+SET(CPACK_PACKAGE_VERSION_MINOR "2") -+SET(CPACK_PACKAGE_VERSION_PATCH "0") -+SET(LIGHTSD_PRERELEASE "rc.1") -+SET(LIGHTSD_BUILD "") -+SET(LIGHTSD_VERSION "1.2.0-rc.1") -diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt -new file mode 100644 ---- /dev/null -+++ b/dist/CMakeLists.txt -@@ -0,0 +1,47 @@ -+SET(VENV_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/py-env") -+SET(VENV_PYTHON "${VENV_DIRECTORY}/bin/python") -+SET(VENV_STAMP "${VENV_DIRECTORY}/stamp") -+SET(PIP_REQUIREMENTS "${CMAKE_CURRENT_SOURCE_DIR}/requirements-release.txt") -+ -+CONFIGURE_FILE(release.py.in "${CMAKE_CURRENT_BINARY_DIR}/release.py" @ONLY) -+ -+ADD_CUSTOM_COMMAND( -+ OUTPUT "${VENV_STAMP}" -+ COMMAND "${VIRTUALENV_EXECUTABLE}" -q -p "${PYTHON_EXECUTABLE}" "${VENV_DIRECTORY}" -+ COMMAND "${VENV_DIRECTORY}/bin/pip" -q install -r "${PIP_REQUIREMENTS}" -+ COMMAND "${CMAKE_COMMAND}" -E touch "${VENV_STAMP}" -+ DEPENDS "${PIP_REQUIREMENTS}" -+ COMMENT "Setting up a Python virtualenv at ${VENV_DIRECTORY} for the release script" -+ VERBATIM -+) -+ -+SET( -+ RELEASE_COMMANDS -+ release -+ pre_release -+ package_release -+ release_new_tag -+ release_docs -+ release_debuild -+) -+FOREACH (TARGET ${RELEASE_COMMANDS}) -+ ADD_CUSTOM_TARGET( -+ ${TARGET} -+ COMMAND "${VENV_PYTHON}" "${CMAKE_CURRENT_BINARY_DIR}/release.py" "${TARGET}" -+ DEPENDS "${VENV_STAMP}" -+ VERBATIM -+ ) -+ENDFOREACH () -+ -+ADD_DEPENDENCIES(release_docs docs) -+ -+SET( -+ EXTRA_OUTPUT -+ "${CMAKE_CURRENT_BINARY_DIR}/debuild" # release_debuild -+ "${CMAKE_CURRENT_BINARY_DIR}/lightsd-${LIGHTSD_VERSION}.tar" # release_new_tag -+ "${VENV_DIRECTORY}" -+ "${VENV_STAMP}" -+) -+SET_DIRECTORY_PROPERTIES( -+ PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${EXTRA_OUTPUT}" -+) -diff --git a/dist/dpkg/.hgtags b/dist/dpkg/.hgtags -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/.hgtags -@@ -0,0 +1,3 @@ -+11534a873dbc92fd9c7225cf37d67942f852a67c 1.1.0 -+60c3d81a6ff2305eca554c12c42d272d5f2187db 1.1.1 -+bf09addc0ea974fc378f1acf48a8255e485f87d1 1.1.2 -diff --git a/dist/dpkg/debian/changelog b/dist/dpkg/debian/changelog -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/changelog -@@ -0,0 +1,29 @@ -+lightsd (1.1.2-1) unstable; urgency=low -+ -+ * New upstream release: fix LIFX LAN protocol v2 handling. -+ -+ -- Louis Opter <kalessin@kalessin.fr> Mon, 30 Nov 2015 10:38:18 +0000 -+ -+lightsd (1.1.1-1) unstable; urgency=low -+ -+ * New upstream release: responsiveness improvements. -+ -+ -- Louis Opter <kalessin@kalessin.fr> Tue, 17 Nov 2015 10:19:02 +0000 -+ -+lightsd (1.1.0-2) unstable; urgency=low -+ -+ * Fix parallel builds -+ -+ -- Louis Opter <kalessin@kalessin.fr> Sat, 14 Nov 2015 06:35:43 +0000 -+ -+lightsd (1.1.0-1) unstable; urgency=low -+ -+ * First published release -+ -+ -- Louis Opter <kalessin@kalessin.fr> Sun, 08 Nov 2015 08:06:51 +0000 -+ -+lightsd (1.0.1-1) unstable; urgency=low -+ -+ * Initial release -+ -+ -- Louis Opter <kalessin@kalessin.fr> Tue, 03 Nov 2015 07:43:24 +0000 -diff --git a/dist/dpkg/debian/compat b/dist/dpkg/debian/compat -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/compat -@@ -0,0 +1,1 @@ -+9 -diff --git a/dist/dpkg/debian/control b/dist/dpkg/debian/control -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/control -@@ -0,0 +1,24 @@ -+Source: lightsd -+Section: contrib/utils -+Priority: optional -+Maintainer: Louis Opter <kalessin@kalessin.fr> -+Uploaders: Buildbot <buildbot@kalessin.fr> -+Build-Depends: debhelper (>= 9), cmake (>= 2.8.9), libevent-dev (>= 2.0.19) -+Standards-Version: 3.9.6 -+Homepage: https://github.com/lopter/lightsd/ -+Vcs-Git: https://github.com/lopter/dpkg-lightsd.git -+Vcs-Browser: https://github.com/lopter/dpkg-lightsd/ -+ -+Package: lightsd -+Architecture: any -+Depends: ${shlibs:Depends}, ${misc:Depends} -+Suggests: python3, ipython3 -+Description: Centralized daemon to control your LIFX bulbs -+ lightsd discovers LIFX bulbs on the local network and acts as a central -+ point of control for them via a JSON-RPC interface. -+ . -+ The JSON-RPC interface can run over TCP, named pipes as well as Unix -+ sockets and allows you to retrieve the state of the bulbs or set their -+ colors. Effects, grouping and de-grouping operations are also supported. -+ . -+ Visit https://docs.lightsd.io/ for more information. -diff --git a/dist/dpkg/debian/copyright b/dist/dpkg/debian/copyright -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/copyright -@@ -0,0 +1,62 @@ -+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -+Upstream-Name: lightsd -+Source: https://github.com/lopter/lightsd -+ -+Files: * -+Copyright: 2014-2015 Louis Opter <kalessin@kalessin.fr> -+License: GPL-3.0+ -+ -+Files: debian/* -+Copyright: 2015 Louis Opter <kalessin@kalessin.fr> -+License: BSD-3-clause -+ -+Files: examples/* -+Copyright: 2015 Louis Opter <kalessin@kalessin.fr> -+License: BSD-3-clause -+ -+Files: share/lightsc.sh -+Copyright: 2015 Louis Opter <kalessin@kalessin.fr> -+License: BSD-3-clause -+ -+License: GPL-3.0+ -+ This program is free software: you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation, either version 3 of the License, or -+ (at your option) any later version. -+ . -+ This package is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ . -+ You should have received a copy of the GNU General Public License -+ along with this program. If not, see <http://www.gnu.org/licenses/>. -+ . -+ On Debian systems, the complete text of the GNU General -+ Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". -+ -+License: BSD-3-clause -+ Redistribution and use in source and binary forms, with or without -+ modification, are permitted provided that the following conditions are met: -+ . -+ 1. Redistributions of source code must retain the above copyright notice, this -+ list of conditions and the following disclaimer. -+ . -+ 2. Redistributions in binary form must reproduce the above copyright notice, -+ this list of conditions and the following disclaimer in the documentation -+ and/or other materials provided with the distribution. -+ . -+ 3. Neither the name of the copyright holder nor the names of its contributors -+ may be used to endorse or promote products derived from this software without -+ specific prior written permission. -+ . -+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -diff --git a/dist/dpkg/debian/docs b/dist/dpkg/debian/docs -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/docs -@@ -0,0 +1,1 @@ -+README.rst -diff --git a/dist/dpkg/debian/init.d b/dist/dpkg/debian/init.d -new file mode 100755 ---- /dev/null -+++ b/dist/dpkg/debian/init.d -@@ -0,0 +1,129 @@ -+#!/bin/sh -+### BEGIN INIT INFO -+# Provides: lightsd -+# Required-Start: $local_fs $network $syslog $remote_fs -+# Required-Stop: $local_fs $network $syslog $remote_fs -+# Default-Start: 2 3 4 5 -+# Default-Stop: 0 1 6 -+# Short-Description: Start lightsd at boot time -+# Description: Centralized daemon to discover and control LIFX bulbs. -+### END INIT INFO -+ -+# Author: Louis Opter <kalessin@kalessin.fr> -+ -+# Do NOT "set -e" -+ -+# PATH should only include /usr/* if it runs after the mountnfs.sh script -+PATH=/sbin:/usr/sbin:/bin:/usr/bin -+DESC="lightsd" -+NAME=lightsd -+DAEMON=/usr/bin/lightsd -+PIDFILE=/var/run/$NAME.pid -+SCRIPTNAME=/etc/init.d/$NAME -+ -+# Exit if the package is not installed -+[ -x "$DAEMON" ] || exit 0 -+ -+# Read configuration variable file if it is present -+[ -r /etc/default/$NAME ] && . /etc/default/$NAME -+ -+DAEMON_ARGS=" -+ --daemonize -+ --verbosity warning -+ --user lightsd -+ --pidfile $PIDFILE -+ --socket /var/run/lightsd/socket -+ --command-pipe /var/run/lightsd/pipe -+ $DAEMON_OPTS -+" -+ -+# Load the VERBOSE setting and other rcS variables -+. /lib/init/vars.sh -+ -+# Define LSB log_* functions. -+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present -+# and status_of_proc is working. -+. /lib/lsb/init-functions -+ -+do_start() -+{ -+ # Return -+ # 0 if daemon has been started -+ # 1 if daemon was already running -+ # 2 if daemon could not be started -+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ -+ || return 1 -+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_ARGS \ -+ || return 2 -+} -+ -+do_stop() -+{ -+ # Return -+ # 0 if daemon has been stopped -+ # 1 if daemon was already stopped -+ # 2 if daemon could not be stopped -+ # other if a failure occurred -+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME -+ RETVAL="$?" -+ [ "$RETVAL" = 2 ] && return 2 -+ # Wait for children to finish too if this is a daemon that forks -+ # and if the daemon is only ever run from this initscript. -+ # If the above conditions are not satisfied then add some other code -+ # that waits for the process to drop all resources that could be -+ # needed by services started subsequently. A last resort is to -+ # sleep for some time. -+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON -+ [ "$?" = 2 ] && return 2 -+ # Many daemons don't delete their pidfiles when they exit. -+ rm -f $PIDFILE -+ return "$RETVAL" -+} -+ -+case "$1" in -+ start) -+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" -+ do_start -+ case "$?" in -+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; -+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; -+ esac -+ ;; -+ stop) -+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" -+ do_stop -+ case "$?" in -+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; -+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; -+ esac -+ ;; -+ status) -+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? -+ ;; -+ restart|force-reload) -+ log_daemon_msg "Restarting $DESC" "$NAME" -+ do_stop -+ case "$?" in -+ 0|1) -+ do_start -+ case "$?" in -+ 0) log_end_msg 0 ;; -+ 1) log_end_msg 1 ;; # Old process is still running -+ *) log_end_msg 1 ;; # Failed to start -+ esac -+ ;; -+ *) -+ # Failed to stop -+ log_end_msg 1 -+ ;; -+ esac -+ ;; -+ *) -+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 -+ exit 3 -+ ;; -+esac -+ -+: -+ -+# vim: set ft=sh: -diff --git a/dist/dpkg/debian/lightsd.default b/dist/dpkg/debian/lightsd.default -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/lightsd.default -@@ -0,0 +1,10 @@ -+# Defaults for lightsd initscript -+# sourced by /etc/init.d/lightsd -+# installed at /etc/default/lightsd by the maintainer scripts -+ -+# -+# This is a POSIX shell fragment -+# -+ -+# Additional options that are passed to the Daemon. -+DAEMON_OPTS="" -diff --git a/dist/dpkg/debian/postinst b/dist/dpkg/debian/postinst -new file mode 100755 ---- /dev/null -+++ b/dist/dpkg/debian/postinst -@@ -0,0 +1,55 @@ -+#!/bin/sh -+# postinst script for lightsd -+# -+# see: dh_installdeb(1) -+ -+set -e -+ -+# summary of how this script can be called: -+# * <postinst> `configure' <most-recently-configured-version> -+# * <old-postinst> `abort-upgrade' <new version> -+# * <conflictor's-postinst> `abort-remove' `in-favour' <package> -+# <new-version> -+# * <postinst> `abort-remove' -+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' -+# <failed-install-package> <version> `removing' -+# <conflicting-package> <version> -+# for details, see http://www.debian.org/doc/debian-policy/ or -+# the debian-policy package -+ -+ -+case "$1" in -+ configure) -+ getent group lightsd >/dev/null || groupadd -r lightsd -+ getent passwd lightsd >/dev/null || useradd -r -d / -g lightsd lightsd -+ -+ cat << 'EOF' -+ -+lightsd runs under the `lightsd' user and group by default; add yourself to -+this group to be able to open lightsd's socket and pipe under /var/run/lightsd: -+ -+ gpasswd -a $USER lightsd -+ -+Re-open your current desktop or ssh session for the change to take effect. -+Then use systemctl to start lightsd; you can start playing with lightsd with: -+ -+ `lightsd --prefix`/share/doc/lightsd/examples/lightsc.py -+ -+EOF -+ ;; -+ -+ abort-upgrade|abort-remove|abort-deconfigure) -+ ;; -+ -+ *) -+ echo "postinst called with unknown argument \`$1'" >&2 -+ exit 1 -+ ;; -+esac -+ -+# dh_installdeb will replace this with shell code automatically -+# generated by other debhelper scripts. -+ -+#DEBHELPER# -+ -+exit 0 -diff --git a/dist/dpkg/debian/postrm b/dist/dpkg/debian/postrm -new file mode 100755 ---- /dev/null -+++ b/dist/dpkg/debian/postrm -@@ -0,0 +1,41 @@ -+#!/bin/sh -+# postrm script for lightsd -+# -+# see: dh_installdeb(1) -+ -+set -e -+ -+# summary of how this script can be called: -+# * <postrm> `remove' -+# * <postrm> `purge' -+# * <old-postrm> `upgrade' <new-version> -+# * <new-postrm> `failed-upgrade' <old-version> -+# * <new-postrm> `abort-install' -+# * <new-postrm> `abort-install' <old-version> -+# * <new-postrm> `abort-upgrade' <old-version> -+# * <disappearer's-postrm> `disappear' <overwriter> -+# <overwriter-version> -+# for details, see http://www.debian.org/doc/debian-policy/ or -+# the debian-policy package -+ -+ -+case "$1" in -+ purge|remove|abort-install) -+ getent passwd lightsd >/dev/null && userdel lightsd -+ ;; -+ -+ upgrade|failed-upgrade|abort-upgrade|disappear) -+ ;; -+ -+ *) -+ echo "postrm called with unknown argument \`$1'" >&2 -+ exit 1 -+ ;; -+esac -+ -+# dh_installdeb will replace this with shell code automatically -+# generated by other debhelper scripts. -+ -+#DEBHELPER# -+ -+exit 0 -diff --git a/dist/dpkg/debian/rules b/dist/dpkg/debian/rules -new file mode 100755 ---- /dev/null -+++ b/dist/dpkg/debian/rules -@@ -0,0 +1,48 @@ -+#!/usr/bin/make -f -+# See debhelper(7) (uncomment to enable) -+# output every command that modifies files on the build system. -+DH_VERBOSE = 1 -+ -+# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* -+DPKG_EXPORT_BUILDFLAGS = 1 -+include /usr/share/dpkg/default.mk -+ -+# see FEATURE AREAS in dpkg-buildflags(1) -+export DEB_BUILD_MAINT_OPTIONS = hardening=+all -+ -+# see ENVIRONMENT in dpkg-buildflags(1) -+# package maintainers to append CFLAGS -+#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic -+# package maintainers to append LDFLAGS -+#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed -+ -+ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) -+ NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) -+ MAKEFLAGS += -j$(NUMJOBS) -+endif -+ -+ifeq (,$(wildcard /run)) -+ RUNTIME_DIRECTORY=/var/run -+else -+ RUNTIME_DIRECTORY=/run -+endif -+ -+# main packaging script based on dh7 syntax -+%: -+ dh $@ -+ -+# debmake generated override targets -+# This is example for Cmake (See http://bugs.debian.org/641051 ) -+override_dh_auto_configure: -+ dh_auto_configure -- \ -+ -DCMAKE_BUILD_TYPE=RELEASE \ -+ -DCMAKE_INSTALL_PREFIX=/usr \ -+ -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) \ -+ -DLGTD_RUNTIME_DIRECTORY=$(RUNTIME_DIRECTORY)/lightsd -+ -+override_dh_auto_build: -+ dh_auto_build --parallel -+ -+override_dh_auto_install: -+ dh_auto_install -+ rm -f debian/lightsd/usr/share/doc/lightsd/COPYING -diff --git a/dist/dpkg/debian/source/format b/dist/dpkg/debian/source/format -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/source/format -@@ -0,0 +1,1 @@ -+3.0 (quilt) -diff --git a/dist/dpkg/debian/watch b/dist/dpkg/debian/watch -new file mode 100644 ---- /dev/null -+++ b/dist/dpkg/debian/watch -@@ -0,0 +1,7 @@ -+# See uscan(1) for format -+ -+# Compulsory line, this is a version 3 file -+version=3 -+ -+opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/lightsd-$1\.tar\.gz/ \ -+ https://github.com/lopter/lightsd/tags .*/?(\d+\.\d+.\d+)\.tar\.gz -diff --git a/dist/homebrew/LICENSE b/dist/homebrew/LICENSE -new file mode 100644 ---- /dev/null -+++ b/dist/homebrew/LICENSE -@@ -0,0 +1,28 @@ -+Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+All rights reserved. -+ -+Redistribution and use in source and binary forms, with or without -+modification, are permitted provided that the following conditions are met: -+ -+* Redistributions of source code must retain the above copyright notice, this -+ list of conditions and the following disclaimer. -+ -+* Redistributions in binary form must reproduce the above copyright notice, -+ this list of conditions and the following disclaimer in the documentation -+ and/or other materials provided with the distribution. -+ -+* Neither the name of homebrew-lightsd nor the names of its -+ contributors may be used to endorse or promote products derived from -+ this software without specific prior written permission. -+ -+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -+ -diff --git a/dist/homebrew/README.rst b/dist/homebrew/README.rst -new file mode 100644 ---- /dev/null -+++ b/dist/homebrew/README.rst -@@ -0,0 +1,31 @@ -+Homebrew lightsd -+================ -+ -+This `brew`_ tap let you install `lightsd`_, a daemon written C to control your -+LIFX wifi smart bulbs, on Mac OS X. -+ -+.. _brew: http://brew.sh -+.. _lightsd: https://github.com/lopter/lightsd/ -+ -+Instructions -+------------ -+ -+Make sure you have brew installed: http://brew.sh. -+ -+:: -+ -+ brew install lopter/lightsd/lightsd -+ -+Or: -+ -+:: -+ -+ brew tap lopter/lightsd -+ brew install lightsd -+ -+Documentation -+------------- -+ -+Please checkout lightsd's repository: https://github.com/lopter/lightsd/. -+ -+.. vim: set tw=80 spelllang=en spell: -diff --git a/dist/homebrew/lightsd.rb b/dist/homebrew/lightsd.rb -new file mode 100644 ---- /dev/null -+++ b/dist/homebrew/lightsd.rb -@@ -0,0 +1,95 @@ -+require "formula" -+ -+class Lightsd < Formula -+ desc "Daemon to control your LIFX wifi smart bulbs" -+ homepage "https://github.com/lopter/lightsd/" -+ url "{{ archive_url }}" -+ sha256 "{{ archive_sha256 }}" -+ revision {{ build_number }} -+ -+ depends_on "cmake" => :build -+ depends_on "libevent" => :build -+ depends_on "python3" => :optional -+ -+ def install -+ # XXX, wtf? https://github.com/Homebrew/homebrew/issues/46061 -+ ENV["PATH"] = "/usr/bin:#{ENV["PATH"]}" -+ -+ args = std_cmake_args -+ args << "-DLGTD_RUNTIME_DIRECTORY=#{var}/run/lightsd" -+ -+ # idk what std_cmake_args is supposed to do but it appears to be missing -+ # proper release flags: -+ cflags = %W[ -+ -fstack-protector-strong -+ --param=ssp-buffer-size=4 -+ -O3 -+ -DNDEBUG -+ ] -+ args << "-DCMAKE_BUILD_TYPE=RELEASE" -+ args << "-DCMAKE_C_FLAGS_RELEASE='#{cflags * " "}'" -+ -+ system "cmake", *args -+ system "make", "install" -+ end -+ -+ def plist; <<-EOS.undent -+ <?xml version="1.0" encoding="UTF-8"?> -+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -+ <plist version="1.0"> -+ <dict> -+ <key>KeepAlive</key> -+ <dict> -+ <key>SuccessfulExit</key> -+ <false/> -+ </dict> -+ <key>Label</key> -+ <string>#{plist_name}</string> -+ <key>ProgramArguments</key> -+ <array> -+ <string>#{opt_bin}/lightsd</string> -+ <string>-v</string> -+ <string>warning</string> -+ <string>-s</string> -+ <string>#{var}/run/lightsd/socket</string> -+ <string>-c</string> -+ <string>#{var}/run/lightsd/pipe</string> -+ </array> -+ <key>RunAtLoad</key> -+ <true/> -+ <key>WorkingDirectory</key> -+ <string>#{var}</string> -+ <key>StandardErrorPath</key> -+ <string>#{var}/log/lightsd.log</string> -+ <key>StandardOutPath</key> -+ <string>#{var}/log/lightsd.log</string> -+ </dict> -+ </plist> -+ EOS -+ end -+ -+ def caveats; <<-EOS.undent -+ Once you've started lightsd with launchctl load (see below), you can start -+ poking around with lightsc.py: -+ -+ `lightsd --prefix`/share/lightsd/examples/lightsc.py -+ EOS -+ end -+ -+ head do -+ url "https://github.com/lopter/lightsd.git" -+ end -+ -+ test do -+ Dir.mktmpdir("lightsd-test") do |dir| -+ args = %W[ -+ -l ::1:0 -+ -l 127.0.0.1:0 -+ -c #{dir}/lightsd.cmd -+ -h -+ ] -+ system "#{bin}/lightsd", *args -+ end -+ end -+end -+ -diff --git a/dist/openwrt/utils/lightsd/Makefile b/dist/openwrt/utils/lightsd/Makefile -new file mode 100644 ---- /dev/null -+++ b/dist/openwrt/utils/lightsd/Makefile -@@ -0,0 +1,62 @@ -+# -+# Copyright (C) 2015 Louis Opter <kalessin@kalessin.fr> -+# -+# This is free software, licensed under the GNU General Public License v2. -+# See /LICENSE for more information. -+# -+ -+include $(TOPDIR)/rules.mk -+ -+PKG_NAME:=lightsd -+PKG_VERSION:={{ version }} -+PKG_RELEASE:={{ build_number }} -+PKG_MAINTAINER:=Louis Opter <kalessin@kalessin.fr> -+PKG_LICENSE:=GPL-3.0+ -+PKG_SOURCE_URL:=https://downloads.lightsd.io/releases/ -+PKG_SOURCE:={{ archive_name }} -+PKG_MD5SUM:={{ archive_md5 }} -+PKG_BUILD_DIR:=$(BUILD_DIR)/lightsd-$(PKG_VERSION) -+ -+include $(INCLUDE_DIR)/package.mk -+include $(INCLUDE_DIR)/cmake.mk -+ -+CMAKE_OPTIONS += \ -+ -DCMAKE_BUILD_TYPE=RELEASE \ -+ -DLGTD_RUNTIME_DIRECTORY=/var/run/lightsd -+ -+define Package/lightsd -+ SECTION:=utils -+ CATEGORY:=Utilities -+ DEPENDS:=+libevent2-core -+ TITLE:=Daemon to control your LIFX Wi-Fi smart bulbs -+ MAINTAINER:=Louis Opter <kalessin@kalessin.fr> -+ URL:=https://github.com/lopter/lightsd -+ USERID:=lightsd:lightsd -+endef -+ -+define Package/lightsd/install -+ $(INSTALL_DIR) \ -+ $(1)/etc/init.d/ \ -+ $(1)/usr/bin \ -+ $(1)/usr/share/lightsd \ -+ $(1)/usr/share/doc/lightsd -+ -+ $(INSTALL_BIN) \ -+ $(PKG_INSTALL_DIR)/usr/bin/lightsd \ -+ $(1)/usr/bin/lightsd -+ -+ $(INSTALL_BIN) \ -+ ./files/lightsd.init \ -+ $(1)/etc/init.d/lightsd -+ -+ $(CP) \ -+ $(PKG_INSTALL_DIR)/usr/share/lightsd \ -+ $(1)/usr/share/lightsd -+ -+ $(CP) \ -+ $(PKG_INSTALL_DIR)/usr/share/doc/lightsd \ -+ $(1)/usr/share/doc/lightsd -+endef -+ -+$(eval $(call BuildPackage,lightsd)) -+ -diff --git a/dist/openwrt/utils/lightsd/files/lightsd.init b/dist/openwrt/utils/lightsd/files/lightsd.init -new file mode 100644 ---- /dev/null -+++ b/dist/openwrt/utils/lightsd/files/lightsd.init -@@ -0,0 +1,19 @@ -+#!/bin/sh /etc/rc.common -+# Copyright (C) 2015 Louis Opter <kalessin@kalessin.fr> -+# -+# This is free software, licensed under the GNU General Public License v2. -+# See /LICENSE for more information. -+ -+START=60 -+ -+USE_PROCD=1 -+ -+lightsd=/usr/bin/lightsd -+rundir=`$lightsd --rundir` -+ -+start_service() { -+ procd_open_instance -+ procd_set_param command $lightsd -S -v warning -t -u lightsd -s "$rundir/socket" -c "$rundir/pipe" -+ procd_set_param respawn -+ procd_close_instance -+} -diff --git a/dist/pkgbuild/.SRCINFO b/dist/pkgbuild/.SRCINFO -new file mode 100644 ---- /dev/null -+++ b/dist/pkgbuild/.SRCINFO -@@ -0,0 +1,20 @@ -+pkgbase = lightsd -+ pkgdesc = Daemon to control your LIFX smart bulbs via a JSON-RPC API -+ pkgver = {{ version }} -+ pkgrel = {{ build_number }} -+ epoch = 1 -+ url = https://www.github.com/lopter/lightsd/ -+ install = lightsd.install -+ arch = i686 -+ arch = x86_64 -+ license = GPL3 -+ makedepends = cmake>=2.8.11 -+ depends = libevent>=2.0.19 -+ optdepends = python: to run the interactive lightsc.py example client -+ optdepends = ipython: makes lightsc.py more user-friendly -+ source = {{ archive_url }} -+ sha256sums = {{ archive_sha256 }} -+ -+pkgname = lightsd -+ -+ -diff --git a/dist/pkgbuild/PKGBUILD b/dist/pkgbuild/PKGBUILD -new file mode 100644 ---- /dev/null -+++ b/dist/pkgbuild/PKGBUILD -@@ -0,0 +1,43 @@ -+# Maintainer: Louis Opter <kalessin@kalessin.fr> -+ -+pkgname=lightsd -+pkgver={{ version }} -+pkgrel={{ build_number }} -+epoch=1 -+pkgdesc="Daemon to control your LIFX smart bulbs via a JSON-RPC API" -+arch=("i686" "x86_64") -+url="https://www.github.com/lopter/lightsd/" -+license=("GPL3") -+depends=("libevent>=2.0.19") -+optdepends=( -+ "python: to run the interactive lightsc.py example client" -+ "ipython: makes lightsc.py more user-friendly" -+) -+makedepends=("cmake>=2.8.11") -+source=("{{ archive_url }}") -+sha256sums=("{{ archive_sha256 }}") -+install=lightsd.install -+ -+build() { -+ cd "$srcdir/$pkgname-$pkgver" -+ -+ cmake . \ -+ -DCMAKE_BUILD_TYPE=RELEASE \ -+ -DCMAKE_INSTALL_PREFIX=/usr \ -+ -DLGTD_RUNTIME_DIRECTORY=/run/lightsd -+ -+ make -+} -+ -+check() { -+ cd "$srcdir/$pkgname-$pkgver" -+ -+ make test -+} -+ -+package() { -+ cd "$srcdir/$pkgname-$pkgver" -+ -+ make DESTDIR="$pkgdir/" install -+} -+ -diff --git a/dist/pkgbuild/lightsd.install b/dist/pkgbuild/lightsd.install -new file mode 100644 ---- /dev/null -+++ b/dist/pkgbuild/lightsd.install -@@ -0,0 +1,22 @@ -+post_install() { -+ getent group lightsd >/dev/null || groupadd -r lightsd -+ getent passwd lightsd >/dev/null || useradd -r -d / -g lightsd lightsd -+ -+ cat << 'EOF' -+ -+lightsd runs under the `lightsd' user and group by default; add yourself to -+this group to be able to open lightsd's socket and pipe under /run/lightsd: -+ -+ gpasswd -a $USER lightsd -+ -+Re-open your current desktop or ssh session for the change to take effect. -+Then use systemctl to start lightsd; you can start playing with lightsd with: -+ -+ `lightsd --prefix`/share/doc/lightsd/examples/lightsc.py -+ -+EOF -+} -+ -+post_remove() { -+ getent passwd lightsd >/dev/null && userdel lightsd -+} -diff --git a/dist/release.py.in b/dist/release.py.in -new file mode 100644 ---- /dev/null -+++ b/dist/release.py.in -@@ -0,0 +1,559 @@ -+#!/usr/bin/env python3 -+ -+import click -+import datetime -+import functools -+import hashlib -+import os -+import itertools -+import jinja2 -+import locale -+import pytz -+import requests -+import semver -+import shutil -+import subprocess -+import sys -+ -+LIGHTSD_VERSION = "@LIGHTSD_VERSION@" -+# where the lightsd sources are: -+LIGHTSD_SOURCE_DIR = "@LIGHTSD_SOURCE_DIR@" -+# where the build is: -+LIGHTSD_BINARY_DIR = "@LIGHTSD_BINARY_DIR@" -+# where all the downstream repositories are, they will be pushed by by buildbot -+# (which manage the credentials to those repositories) -+LIGHTSD_PKGS_DIR = "@LIGHTSD_RELEASE_PACKAGES_DIR@" -+# where to put the generated archives served at -+# https://downloads.lightsd.io/releases/: -+LIGHTSD_ARCHIVES_DIR = "@LIGHTSD_RELEASE_ARCHIVES_DIR@" -+# where to put generated debian packages -+LIGHTSD_DEBS_DIR = "@LIGHTSD_RELEASE_DEBS_DIR@" -+# where to manage the documentation served at https://docs.lighsd.io/: -+LIGHTSD_DOCS_DIR = "@LIGHTSD_RELEASE_DOCS_DIR@" -+ -+# paths to utilities: -+HG_EXECUTABLE = "@HG_EXECUTABLE@" -+BSDTAR_EXECUTABLE = "@BSDTAR_EXECUTABLE@" -+GZIP_EXECUTABLE = "@GZIP_EXECUTABLE@" -+XZ_EXECUTABLE = "@XZ_EXECUTABLE@" -+ -+COMMIT_AUTHOR = "Buildbot <buildbot@kalessin.fr>" -+ -+FILE_READ_SIZE = 32768 -+ -+USER_ENCODING = locale.getpreferredencoding() -+ -+TARBALL_COMPRESSORS = [ -+ # suffix, path, extra_flags -+ ("gz", GZIP_EXECUTABLE, ["--best"]), -+ ("xz", XZ_EXECUTABLE, []), -+] -+ -+prereq_echo = functools.partial(click.secho, fg="magenta", bold=True) -+action_echo = functools.partial(click.secho, fg="blue", bold=True) -+result_echo = functools.partial(click.secho, fg="green", bold=True) -+error_echo = functools.partial(click.secho, bold=True) -+ -+ -+def repopath(repo): -+ return os.path.join(LIGHTSD_PKGS_DIR, repo) -+ -+ -+def next_dev_version(version): -+ parts, version = version, semver.format_version(**version) -+ if parts["prerelease"] is not None: -+ return semver.bump_prerelease(version) -+ return semver.parse(semver.bump_prerelease(semver.bump_patch(version))) -+ -+ -+def next_pre_release(version): -+ version = semver.format_version(**version) -+ return semver.parse(semver.bump_prerelease(version)) -+ -+ -+def next_build_release(version): -+ version = semver.format_version(**version) -+ return semver.parse(semver.bump_build(version)) -+ -+ -+def extract_build_number(version): -+ if version["build"] is None: -+ return "1" -+ rv = str(int(version["build"].split(".")[-1]) + 1) -+ version["build"] = None -+ return rv -+ -+ -+def readfile(fp, read_size=FILE_READ_SIZE): -+ chunk = fp.read(read_size) -+ while chunk: -+ yield chunk -+ chunk = fp.read(read_size) -+ -+ -+class PackageContext: -+ -+ __slots__ = ( -+ "version", -+ "build_number", -+ "archive_name", -+ "archive_md5", -+ "archive_sha256", -+ "archive_url", -+ ) -+ -+ def __init__(self, **kwargs): -+ for name, value in kwargs.items(): -+ if name not in self.__slots__: -+ raise TypeError("{}: invalid argument {}".format( -+ self.__class__.__name__, name -+ )) -+ setattr(self, name, value) -+ -+ def as_dict(self): -+ return {name: getattr(self, name) for name in self.__slots__} -+ -+ -+class DownstreamPackage: -+ -+ TYPE = None -+ TEMPLATES = None -+ STATIC_FILES = None -+ -+ def __init__(self, repo): -+ self._repo = repo -+ self._src_dir = os.path.join(LIGHTSD_SOURCE_DIR, "dist", self.TYPE) -+ jinja_loader = jinja2.FileSystemLoader(self._src_dir) -+ self._render_ctx = jinja2.Environment(loader=jinja_loader) -+ -+ def _render(self, msg): -+ pass -+ -+ def render(self, pkg_ctx): -+ if self.TEMPLATES is not None: -+ for filename in self.TEMPLATES: -+ template = self._render_ctx.get_template(filename) -+ with open(os.path.join(self._repo, filename), "wb") as fp: -+ template.stream(pkg_ctx.as_dict()).dump(fp, "utf-8") -+ -+ if self.STATIC_FILES is not None: -+ for filename in self.STATIC_FILES: -+ dest = os.path.join(self._src_dir, filename) -+ dirname = os.path.dirname(dest) -+ os.makedirs(dirname, exist_ok=True) -+ shutil.copyfile(dest, os.path.join(self._repo, filename)) -+ -+ self._render(pkg_ctx) -+ -+ def _pre_commit(self, msg): -+ pass -+ -+ def commit(self, msg): -+ self._pre_commit(msg) -+ -+ dirty = bool(len(subprocess.check_output([ -+ HG_EXECUTABLE, "-R", self._repo, "status" -+ ]))) -+ if dirty: -+ subprocess.check_call([ -+ HG_EXECUTABLE, "-R", self._repo, "commit", "-m", msg -+ ]) -+ return dirty -+ -+ -+class DebianPackage(DownstreamPackage): -+ -+ TYPE = "dpkg" -+ STATIC_FILES = ( -+ "debian/init.d", -+ "debian/source/format", -+ "debian/postinst", -+ "debian/control", -+ "debian/postrm", -+ "debian/copyright", -+ "debian/rules", -+ "debian/compat", -+ "debian/watch", -+ "debian/docs", -+ "debian/lightsd.default", -+ ) -+ -+ _CHANGELOG_ENTRY_FORMAT = ( -+"""lightsd ({version}-{build_number}) unstable; urgency=low -+ -+ * {msg} -+ -+ -- {author} {date} -+ -+""" # noqa -+ ) -+ _CHANGELOG_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S %z" -+ -+ def _render(self, pkg_ctx): -+ # change -rc.# into ~rc.# to keep lintian happy -+ self._version = pkg_ctx.version.replace("-", "~") -+ self._build_number = pkg_ctx.build_number -+ -+ def _pre_commit(self, msg): -+ changelog_path = os.path.join(self._repo, "debian", "changelog") -+ staging_changelog_path = "-".join([changelog_path, "next"]) -+ user_date_locale = locale.getlocale(locale.LC_TIME) -+ utcnow = datetime.datetime.now(pytz.utc) -+ -+ # prepare new changelog: -+ with open(staging_changelog_path, "wb") as ofp: -+ try: -+ locale.setlocale(locale.LC_TIME, "C") -+ new_entry = self._CHANGELOG_ENTRY_FORMAT.format( -+ version=self._version, -+ build_number=self._build_number, -+ msg=msg, -+ author=COMMIT_AUTHOR, -+ date=utcnow.strftime(self._CHANGELOG_DATE_FORMAT) -+ ).encode("utf-8") -+ ofp.write(new_entry) -+ with open(changelog_path, "rb") as ifp: -+ prev_entry = next(ifp) # get the first line -+ if new_entry.startswith(prev_entry): -+ os.unlink(staging_changelog_path) -+ return # we already released that version. -+ ofp.write(prev_entry) -+ for chunk in readfile(ifp): -+ ofp.write(chunk) -+ except Exception: -+ os.unlink(staging_changelog_path) -+ raise -+ finally: -+ user_date_locale = locale.setlocale( -+ locale.LC_TIME, user_date_locale -+ ) -+ -+ # and replace the old one: -+ os.replace(staging_changelog_path, changelog_path) -+ -+ -+class HomebrewPackage(DownstreamPackage): -+ -+ TYPE = "homebrew" -+ TEMPLATES = ("lightsd.rb",) -+ STATIC_FILES = ("LICENSE", "README.rst") -+ -+ -+class OpenWRTPackage(DownstreamPackage): -+ -+ TYPE = "openwrt" -+ TEMPLATES = ("utils/lightsd/Makefile",) -+ STATIC_FILES = ("utils/lightsd/files/lightsd.init",) -+ -+ -+class PKGBUILDPackage(DownstreamPackage): -+ -+ TYPE = "pkgbuild" -+ TEMPLATES = ("PKGBUILD", ".SRCINFO") -+ -+ -+PACKAGES = ( -+ DebianPackage(repopath("dpkg-lightsd")), -+ HomebrewPackage(repopath("homebrew-lightsd")), -+ OpenWRTPackage(repopath("openwrt-lightsd")), -+ PKGBUILDPackage(repopath("pkgbuild-lightsd")), -+) -+ -+LIGHTSD_VERSION_CMAKE_TEMPLATE = ( -+"""# NOTE: auto-generated by the release target -+SET(CPACK_PACKAGE_VERSION_MAJOR "{major}") -+SET(CPACK_PACKAGE_VERSION_MINOR "{minor}") -+SET(CPACK_PACKAGE_VERSION_PATCH "{patch}") -+SET(LIGHTSD_PRERELEASE "{prerelease}") -+SET(LIGHTSD_BUILD "{build}") -+SET(LIGHTSD_VERSION "{version}") -+""" # noqa -+) -+ -+ -+def _abort_if_release_exists(version): -+ gz_archive_url = ( -+ "https://downloads.lightsd.io/" -+ "releases/lightsd-{}.tar.gz".format(version) -+ ) -+ -+ prereq_echo("Checking for an existing release") -+ response = requests.head(gz_archive_url, allow_redirects=True) -+ click.echo("[+] {}: {} {}".format( -+ gz_archive_url, response.status_code, response.reason -+ )) -+ if response.status_code != requests.codes.not_found: -+ error_echo("Release already found at {}!".format(gz_archive_url)) -+ sys.exit(1) -+ -+ -+def _update_lightsd_version_cmake_include(version): -+ version_parts = { -+ k: v if v is not None else "" -+ for k, v in semver.parse(version).items() -+ } -+ lightsd_version_file = os.path.join( -+ LIGHTSD_SOURCE_DIR, "CMakeScripts", "LightsdVersion.cmake" -+ ) -+ with open(lightsd_version_file, "wb") as fp: -+ fp.write(LIGHTSD_VERSION_CMAKE_TEMPLATE.format( -+ version=version, **version_parts -+ ).encode("utf-8")) -+ -+ -+def _release(version, next_version): -+ revision, branch = [ -+ part.strip() for part in subprocess.check_output([ -+ HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, "id", "--id", "--branch" -+ ]).decode(USER_ENCODING).split() -+ ] -+ qapplied = bool(len(subprocess.check_output([ -+ HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, -+ "--config", "extensions.mq=", "qapplied" -+ ]).decode(USER_ENCODING))) -+ if qapplied or revision.endswith("+") or branch != "default": -+ error_echo( -+ "Can't do a release over a dirty repository! " -+ "(rev={}, branch={}, patched={})".format( -+ revision, branch, qapplied -+ ) -+ ) -+ sys.exit(1) -+ -+ # Re-gen LightsdVersion.cmake with major.minor.patch, tag, and re-gen -+ # LightsdVersion.cmake after bumping minor adding a prerelease tag: -+ version = click.prompt( -+ "Confirm the version to release", -+ default=version, -+ type=lambda arg: semver.format_version(**semver.parse(arg)) -+ ) -+ _update_lightsd_version_cmake_include(version) -+ subprocess.check_call([ -+ HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, -+ "tag", "-m", "Tagging release {}".format(version), version -+ # TODO: Use docutils to extract the changelog section -+ ]) -+ _update_lightsd_version_cmake_include(next_version) -+ subprocess.check_call([ -+ HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, -+ "commit", "-m", "Back to development, {}".format(next_version) -+ ]) -+ -+ subprocess.check_call([HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, "out"]) -+ if click.confirm("Are you ready to push those commit?"): -+ return -+ subprocess.check_call([HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, "push"]) -+ -+ -+@click.group() -+def cli(): -+ pass -+ -+ -+@cli.command() -+def pre_release(): -+ _abort_if_release_exists(LIGHTSD_VERSION) -+ version = semver.parse(LIGHTSD_VERSION) -+ if version["prerelease"] is None: -+ version = next_pre_release(version) -+ _release( -+ semver.format_version(**version), -+ semver.format_version(**next_pre_release(version)), -+ ) -+ -+ -+@cli.command() -+def release(): -+ _abort_if_release_exists(LIGHTSD_VERSION) -+ version = semver.parse(LIGHTSD_VERSION) -+ version["prerelease"] = version["build"] = None -+ _release( -+ semver.format_version(**version), -+ semver.format_version(**next_dev_version(version)), -+ ) -+ -+ -+@cli.command() -+def package_release(release): -+ version = semver.parse(LIGHTSD_VERSION) -+ # TODO: version = previous_release (including build metadata) -+ # (maybe downloads.lightsd.io can help) -+ version = next_build_release(version) -+ _release( -+ semver.format_version(**version), -+ semver.format_version(**next_build_release(version)), -+ ) -+ -+ -+@cli.command() -+def release_new_tag(): -+ if not all([LIGHTSD_PKGS_DIR, LIGHTSD_ARCHIVES_DIR]): -+ error_echo( -+ "Please configure the project with LIGHTSD_RELEASE_PACKAGES_DIR " -+ "and LIGHTSD_RELEASE_ARCHIVES_DIR to use this command." -+ ) -+ sys.exit(1) -+ -+ _abort_if_release_exists(LIGHTSD_VERSION) -+ -+ prereq_echo("Cleaning-up the source tree") -+ subprocess.check_call([ -+ HG_EXECUTABLE, "-R", LIGHTSD_SOURCE_DIR, -+ "--config", "extensions.purge=", "purge", "--abort-on-err", "--all" -+ ]) -+ -+ archive_name = "lightsd-{}.tar".format(LIGHTSD_VERSION) -+ archive = os.path.join(LIGHTSD_BINARY_DIR, "dist", archive_name) -+ gz_archive_name = ".".join([archive_name, "gz"]) -+ gz_archive = os.path.join(LIGHTSD_ARCHIVES_DIR, gz_archive_name) -+ gz_archive_url = "https://downloads.lightsd.io/releases/{}".format( -+ gz_archive_name -+ ) -+ -+ # NOTE: I wanted to use hg archive but then dpkg gave me troubles because -+ # the archive had extra files or something: -+ action_echo("Tarballing the sources into {}".format(archive)) -+ if not os.path.exists(archive): -+ subprocess.check_call([ -+ BSDTAR_EXECUTABLE, -+ "-C", LIGHTSD_SOURCE_DIR, -+ "-cf", archive, -+ # Put everything under a top-level directory ("archive_name" -+ # without the .tar extension): -+ "-s", "/^\\./{}/".format(archive_name.rsplit(".", 1)[0]), -+ "--exclude", ".hg", -+ "." -+ ]) -+ os.makedirs(LIGHTSD_ARCHIVES_DIR, exist_ok=True) -+ for suffix, bin, extra_flags in TARBALL_COMPRESSORS: -+ dest = ".".join([ -+ os.path.join(LIGHTSD_ARCHIVES_DIR, archive_name), suffix -+ ]) -+ if os.path.exists(dest): -+ click.echo("[=] compressing with {}".format(suffix)) -+ continue -+ cmd = itertools.chain([bin, "--stdout"], extra_flags, [archive]) -+ click.echo("[+] compressing with {}".format(suffix)) -+ with open(dest, "wb") as fp: -+ subprocess.check_call(cmd, stdout=fp) -+ -+ action_echo("Computing MD5 and SHA256 checksums for {}".format(gz_archive)) -+ gz_archive_md5 = hashlib.md5() -+ gz_archive_sha256 = hashlib.sha256() -+ with open(gz_archive, "rb") as fp: -+ for chunk in readfile(fp): -+ gz_archive_md5.update(chunk) -+ gz_archive_sha256.update(chunk) -+ gz_archive_md5 = gz_archive_md5.hexdigest() -+ gz_archive_sha256 = gz_archive_sha256.hexdigest() -+ click.echo("[+] MD5 {}".format(gz_archive_md5)) -+ click.echo("[+] SHA256 {}".format(gz_archive_sha256)) -+ -+ version = semver.parse(LIGHTSD_VERSION) -+ # NOTE: It would be cool to know which package really changed so we can just -+ # render and release that one. I guess the easiest way is to implement -+ # that with different schedulers in buildbot or a scheduler that's -+ # able to fill-in a property that could be passed to CMake (even -+ # better: generate a release target for each pkg). -+ build_number = extract_build_number(version) -+ pkg_ctx = PackageContext( -+ version=semver.format_version(**version), -+ build_number=build_number, -+ archive_name=gz_archive_name, -+ archive_md5=gz_archive_md5, -+ archive_sha256=gz_archive_sha256, -+ archive_url=gz_archive_url, -+ ) -+ action_echo("Updating packages") -+ release_type = "upstream" if build_number == "1" else "package" -+ release_msg = "New {} release {}".format(release_type, LIGHTSD_VERSION) -+ for pkg in PACKAGES: -+ pkg.render(pkg_ctx) -+ dirty = pkg.commit(release_msg) -+ if not dirty: -+ click.echo("[=] {}".format(pkg.TYPE)) -+ continue -+ click.echo("[+] {} package".format(pkg.TYPE)) -+ -+ result_echo(release_msg.replace("upstream", "downstream")) -+ -+ -+@cli.command() -+def release_docs(): -+ version = semver.parse(LIGHTSD_VERSION) -+ dest_dir = os.path.join(LIGHTSD_DOCS_DIR, LIGHTSD_VERSION) -+ docs_dir = os.path.join(LIGHTSD_BINARY_DIR, "docs", "_build", ".") -+ os.makedirs(dest_dir, exist_ok=True) -+ if version["prerelease"] is None and version["build"] is None: -+ alias = "current" -+ else: -+ alias = "latest" -+ action_echo("Copying files into {}".format(dest_dir)) -+ subprocess.check_call(["cp", "-av", docs_dir, dest_dir]) -+ action_echo("Updating alias {}".format(alias)) -+ subprocess.check_call([ -+ "ln", "-vrsnf", dest_dir, os.path.join(LIGHTSD_DOCS_DIR, alias) -+ ]) -+ result_echo("{} ({}) updated".format(dest_dir, alias)) -+ -+ -+@cli.command() -+def release_debuild(): -+ if not all([LIGHTSD_DEBS_DIR, LIGHTSD_ARCHIVES_DIR]): -+ error_echo( -+ "Please configure the project with LIGHTSD_RELEASE_DEBS_DIR " -+ "and LIGHTSD_RELEASE_ARCHIVES_DIR to use this command." -+ ) -+ sys.exit(1) -+ -+ # This is just too painful to do from buildbot atm (we need to parametrize -+ # the build with the version): -+ debuild_dir = os.path.join(LIGHTSD_BINARY_DIR, "dist", "debuild") -+ if os.path.exists(debuild_dir): -+ prereq_echo("Cleaning-up previous build in {}".format(debuild_dir)) -+ shutil.rmtree(debuild_dir) -+ os.makedirs(debuild_dir, exist_ok=True) -+ version = semver.parse(LIGHTSD_VERSION) -+ build_number = extract_build_number(version) -+ version = semver.format_version(**version) -+ archive_name = "lightsd-{}.tar.gz".format(version) -+ archive_path = os.path.join(LIGHTSD_ARCHIVES_DIR, archive_name) -+ deb_archive_name = archive_name.replace("-rc", "~rc") -+ deb_archive_name = deb_archive_name.replace("lightsd-", "lightsd_") -+ prereq_echo("Setting-up sources under {}".format(debuild_dir)) -+ deb_archive_path = os.path.join(debuild_dir, deb_archive_name) -+ shutil.copyfile(archive_path, deb_archive_path) -+ click.echo("[+] Archive copied to {}".format(deb_archive_path)) -+ src_dir = os.path.join(debuild_dir, "lightsd-{}".format(version)) -+ subprocess.check_call([ -+ BSDTAR_EXECUTABLE, "-C", debuild_dir, "-xzf", deb_archive_path, -+ ]) -+ click.echo("[+] Archive extracted in {}".format(src_dir)) -+ shutil.copytree( -+ os.path.join(LIGHTSD_PKGS_DIR, "dpkg-lightsd", "debian"), -+ os.path.join(src_dir, "debian") -+ ) -+ click.echo("[+] dpkg sources copied to {}".format( -+ os.path.join(src_dir, "debian") -+ )) -+ dpkg_arch = subprocess.check_output(["dpkg", "--print-architecture"]) -+ dpkg_arch = dpkg_arch.decode(USER_ENCODING) -+ deb_pkg_name = "lightsd_{version}-{build_number}_{dpkg_arch}.deb".format( -+ version=version, -+ build_number=build_number, -+ dpkg_arch=dpkg_arch, -+ ) -+ action_echo("Building {}".format(deb_pkg_name)) -+ subprocess.check_call(["debuild"], cwd=src_dir) -+ shutil.copyfile( -+ os.path.join(debuild_dir, deb_pkg_name), -+ os.path.join(LIGHTSD_DEBS_DIR, deb_pkg_name), -+ ) -+ click.echo("[+] Copied {} under {}".format( -+ deb_pkg_name, LIGHTSD_DEBS_DIR -+ )) -+ result_echo("New Debian package {}".format(deb_pkg_name)) -+ -+ -+if __name__ == "__main__": -+ cli() -diff --git a/dist/requirements-release.txt b/dist/requirements-release.txt -new file mode 100644 ---- /dev/null -+++ b/dist/requirements-release.txt -@@ -0,0 +1,6 @@ -+click>=6.6,<7.0 -+jinja2>=2.8,<3.0 -+requests>=2.9.1,<3.0 -+semver>=2.4.1,<3.0.0 -+ -+pytz
--- a/docs_setup_alabaster_and_ga.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -# HG changeset patch -# Parent 0997d02e7da2e52f7d1dba3783a6fee58fc730eb -Setup the alabaster theme and Google Analytics for the docs - -diff --git a/CMakeLists.txt b/CMakeLists.txt ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -115,6 +115,9 @@ - - IF (SPHINX_FOUND) - MESSAGE(STATUS "Sphinx found, docs generation enabled") -+ IF (DEFINED LIGHTSD_GOOGLE_ANALYTICS_ID) -+ MESSAGE(STATUS "Using Google Analytics ID ${LIGHTSD_GOOGLE_ANALYTICS_ID} for the docs") -+ ENDIF () - ADD_SUBDIRECTORY(docs) - ELSE () - MESSAGE(STATUS "Shpinx wasn't found, docs generation disabled") -diff --git a/docs/conf.py.in b/docs/conf.py.in ---- a/docs/conf.py.in -+++ b/docs/conf.py.in -@@ -13,7 +13,8 @@ - # All configuration values have a default; values that are commented out - # serve to show the default. - --import sys -+from __future__ import unicode_literals -+ - import os - - # If extensions (or modules to document with autodoc) are in another directory, -@@ -34,7 +35,7 @@ - extlinks = {'gh': ('https://github.com/lopter/lightsd/issues/%s', 'GH-')} - - # Add any paths that contain templates here, relative to this directory. --templates_path = ['_templates'] -+templates_path = [os.path.join('@CMAKE_CURRENT_SOURCE_DIR@', '_templates')] - - # The suffix of source filenames. - source_suffix = '.rst' -@@ -47,14 +48,14 @@ - - # General information about the project. - project = 'lightsd' --copyright = '2015, Louis Opter' -+copyright = '2015 - 2016, <a href="https://www.kalessin.fr/">Louis Opter</a>' - - # The version info for the project you're documenting, acts as replacement for - # |version| and |release|, also used in various other places throughout the - # built documents. - # - # The short X.Y version. --version = '@LIGHTSD_VERSION@' -+version = '@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@' - # The full version, including alpha/beta/rc tags. - release = '@LIGHTSD_VERSION@' - -@@ -101,12 +102,18 @@ - - # The theme to use for HTML and HTML Help pages. See the documentation for - # a list of builtin themes. --html_theme = 'nature' -+html_theme = 'alabaster' - - # Theme options are theme-specific and customize the look and feel of a theme - # further. For a list of options available for each theme, see the - # documentation. --#html_theme_options = {} -+html_theme_options = { -+ "github_user": "lopter", -+ "github_repo": "lightsd", -+ "github_button": True, -+ "analytics_id": "@LIGHTSD_GOOGLE_ANALYTICS_ID@", -+ "show_powered_by": False, -+} - - # Add any paths that contain custom themes here, relative to this directory. - #html_theme_path = [] -@@ -146,7 +153,15 @@ - #html_use_smartypants = True - - # Custom sidebar templates, maps document names to template names. --#html_sidebars = {} -+html_sidebars = { -+ '**': [ -+ 'about.html', -+ 'navigation.html', -+ 'relations.html', -+ 'searchbox.html', -+ 'donate.html', -+ ] -+} - - # Additional templates that should be rendered to pages, maps page names to - # template names. -@@ -162,7 +177,7 @@ - #html_split_index = False - - # If true, links to the reST sources are added to the pages. --#html_show_sourcelink = True -+html_show_sourcelink = False - - # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. - #html_show_sphinx = True
--- a/dont_use_ev_assign.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -# HG changeset patch -# Parent eaec17795512845ed7a35e486ee5e9d61aa429a2 -Avoid event_assign for better compatibility across libevent versions - -diff --git a/core/lightsd.c b/core/lightsd.c ---- a/core/lightsd.c -+++ b/core/lightsd.c -@@ -36,7 +36,6 @@ - #include <unistd.h> - - #include <event2/event.h> --#include <event2/event_struct.h> - - #include "lifx/wire_proto.h" - #include "time_monotonic.h" -@@ -72,36 +71,22 @@ - - struct event_base *lgtd_ev_base = NULL; - -+static const int lgtd_signals[] = { SIGINT, SIGTERM, SIGQUIT }; -+static struct event *lgtd_signal_evs[LGTD_ARRAY_SIZE(lgtd_signals)] = { NULL }; -+ - static int lgtd_last_signal_received = 0; - --void --lgtd_cleanup(void) --{ -- lgtd_lifx_discovery_close(); -- lgtd_listen_close_all(); -- lgtd_command_pipe_close_all(); -- lgtd_client_close_all(); -- lgtd_lifx_broadcast_close(); -- lgtd_lifx_gateway_close_all(); -- lgtd_timer_stop_all(); -- event_base_free(lgtd_ev_base); --#if LIBEVENT_VERSION_NUMBER >= 0x02010100 -- libevent_global_shutdown(); --#endif -- if (lgtd_opts.pidfile) { -- unlink(lgtd_opts.pidfile); -- } --} -- - static void - lgtd_signal_event_callback(int signum, short events, void *ctx) - { -- assert(ctx); -- -- // NOTE: syslog isn't signal safe, don't log anything in this function. -+ int i = (int)ctx; -+ assert(i >= 0); -+ assert(i < (int)LGTD_ARRAY_SIZE(lgtd_signals)); -+ assert(i < (int)LGTD_ARRAY_SIZE(lgtd_signal_evs)); -+ assert(signum == lgtd_signals[i]); - - lgtd_last_signal_received = signum; -- event_del((struct event *)ctx); // restore default behavior -+ event_del(lgtd_signal_evs[i]); // restore default behavior - event_base_loopbreak(lgtd_ev_base); - (void)events; - } -@@ -126,20 +111,21 @@ - } - - static void --lgtd_configure_signal_handling(void) -+lgtd_setup_signal_handling(void) - { -- const int signals[] = {SIGINT, SIGTERM, SIGQUIT}; -- static struct event sigevs[LGTD_ARRAY_SIZE(signals)]; -- -- for (int i = 0; i != LGTD_ARRAY_SIZE(signals); i++) { -- evsignal_assign( -- &sigevs[i], -+ for (int i = 0; i != LGTD_ARRAY_SIZE(lgtd_signals); i++) { -+ lgtd_signal_evs[i] = evsignal_new( - lgtd_ev_base, -- signals[i], -+ lgtd_signals[i], - lgtd_signal_event_callback, -- &sigevs[i] -+ // event_self_cbarg() would make things cleaner, but this was -+ // unfortunately added in libevent 2.1 which hasn't been released -+ // as of 2016: -+ (void *)(intptr_t)i // cast twice for -Wint-to-void-pointer-cast - ); -- evsignal_add(&sigevs[i], NULL); -+ if (!lgtd_signal_evs[i] || evsignal_add(lgtd_signal_evs[i], NULL)) { -+ lgtd_err(1, "can't configure signal handling"); -+ } - } - - struct sigaction act = { .sa_handler = SIG_IGN }; -@@ -149,6 +135,15 @@ - } - - static void -+lgtd_close_signal_handling(void) -+{ -+ for (int i = 0; i != LGTD_ARRAY_SIZE(lgtd_signals); i++) { -+ event_del(lgtd_signal_evs[i]); -+ event_free(lgtd_signal_evs[i]); -+ } -+} -+ -+static void - lgtd_usage(const char *progname) - { - printf( -@@ -186,6 +181,26 @@ - exit(0); - } - -+void -+lgtd_cleanup(void) -+{ -+ lgtd_lifx_discovery_close(); -+ lgtd_listen_close_all(); -+ lgtd_command_pipe_close_all(); -+ lgtd_client_close_all(); -+ lgtd_lifx_broadcast_close(); -+ lgtd_lifx_gateway_close_all(); -+ lgtd_timer_stop_all(); -+ lgtd_close_signal_handling(); -+ event_base_free(lgtd_ev_base); -+#if LIBEVENT_VERSION_NUMBER >= 0x02010100 -+ libevent_global_shutdown(); -+#endif -+ if (lgtd_opts.pidfile) { -+ unlink(lgtd_opts.pidfile); -+ } -+} -+ - int - main(int argc, char *argv[], char *envp[]) - { -@@ -195,7 +210,7 @@ - lgtd_daemon_setup_proctitle(argc, argv, envp); - - lgtd_configure_libevent(); -- lgtd_configure_signal_handling(); -+ lgtd_setup_signal_handling(); - - static const struct option long_opts[] = { - {"listen", required_argument, NULL, 'l'},
--- a/fix_freebsd_build.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -# HG changeset patch -# Parent 28701ca70422b882028b7fd0e6c52401b394041a -Fix build on FreeBSD 10.3 - -- properly use _XOPEN_SOURCE=700 instead of _POSIX_C_SOURCE; -- fix missing includes; -- add compat file for endian.h (maybe it should be the other way or - maybe I should just improve the CMake "finder"). - -diff --git a/CMakeLists.txt b/CMakeLists.txt ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -55,14 +55,20 @@ - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99") - SET(CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} "") - -+IF (CMAKE_SYSTEM_NAME MATCHES "Linux") -+ ADD_DEFINITIONS( -+ # see feature_test_macros(7) -+ "-D_XOPEN_SOURCE=700" -+ "-D_BSD_SOURCE=1" -+ "-D_DEFAULT_SOURCE=1" -+ ) -+ENDIF () -+ -+IF (APPLE) -+ ADD_DEFINITIONS("-D_DARWIN_C_SOURCE=1") -+ENDIF () -+ - ADD_DEFINITIONS( -- # Only relevant for the GNU libc: -- "-D_POSIX_C_SOURCE=200809L" -- "-D_BSD_SOURCE=1" -- "-D_DEFAULT_SOURCE=1" -- -- "-D_DARWIN_C_SOURCE=1" -- - "-DLGTD_BIG_ENDIAN_SYSTEM=${BIG_ENDIAN_SYSTEM}" - "-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}" - -diff --git a/compat/FreeBSD/endian.h b/compat/FreeBSD/endian.h -new file mode 100644 ---- /dev/null -+++ b/compat/FreeBSD/endian.h -@@ -0,0 +1,3 @@ -+#pragma once -+ -+#include <sys/endian.h> -diff --git a/core/listen.c b/core/listen.c ---- a/core/listen.c -+++ b/core/listen.c -@@ -19,6 +19,7 @@ - #include <sys/socket.h> - #include <sys/stat.h> - #include <sys/un.h> -+#include <netinet/in.h> - #include <assert.h> - #include <err.h> - #include <errno.h> -diff --git a/lifx/broadcast.c b/lifx/broadcast.c ---- a/lifx/broadcast.c -+++ b/lifx/broadcast.c -@@ -18,6 +18,7 @@ - #include <sys/queue.h> - #include <sys/tree.h> - #include <arpa/inet.h> -+#include <netinet/in.h> - #include <assert.h> - #include <endian.h> - #include <err.h> -diff --git a/lifx/gateway.c b/lifx/gateway.c ---- a/lifx/gateway.c -+++ b/lifx/gateway.c -@@ -17,6 +17,7 @@ - - #include <sys/queue.h> - #include <sys/tree.h> -+#include <netinet/in.h> - #include <assert.h> - #include <endian.h> - #include <err.h> -diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c ---- a/tests/core/pipe/test_pipe_open_fifo_already_exists.c -+++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c -@@ -1,6 +1,7 @@ - #include "pipe.c" - - #include <sys/tree.h> -+#include <sys/stat.h> - #include <endian.h> - #include <limits.h> -
--- a/network_discovery.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1322 +0,0 @@ -# HG changeset patch -# Parent 3b2d568c18f44ef51ec69c4b8d19eb15e8b7a057 -Properly broadcast LIFX discovery packets on all networks (closes GH-2) - -This is really the proper way of achieving the same semantic as -broadcasting to 255.255.255.255 and is kinda half the solution only. -What I'd like to add is: - -- bind to all local-only networks (right now lightsd binds on 0.0.0.0); -- watch for network interfaces changes so we can re-bind if necessary, - this is really the hard and annoying part as each OS has its own - mechanism for this and some (e.g: BSDs) don't even have it AFAIK. - -So that people running lightsd on a machine connected to Internet (that -can happen quickly on a laptop or a phone/tabled) don't have to firewall -56700. - -diff --git a/core/lightsd.h b/core/lightsd.h ---- a/core/lightsd.h -+++ b/core/lightsd.h -@@ -41,7 +41,9 @@ - #define LGTD_ABS(v) ((v) >= 0 ? (v) : (v) * -1) - #define LGTD_MIN(a, b) ((a) < (b) ? (a) : (b)) - #define LGTD_MAX(a, b) ((a) > (b) ? (a) : (b)) -+ - #define LGTD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) -+ - #define LGTD_MSECS_TO_TIMEVAL(v) { \ - .tv_sec = (v) / 1000, \ - .tv_usec = ((v) % 1000) * 1000 \ -@@ -49,6 +51,7 @@ - #define LGTD_NSECS_TO_USECS(v) ((v) / (unsigned int)1E6) - #define LGTD_NSECS_TO_SECS(v) ((v) / (unsigned int)1E9) - #define LGTD_SECS_TO_NSECS(v) ((v) * (unsigned int)1E9) -+ - #define LGTD_TM_TO_ISOTIME(tm, sbuf, bufsz, usec) do { \ - /* '2015-01-02T10:13:16.132222+00:00' */ \ - if ((usec)) { \ -@@ -69,11 +72,24 @@ - ); \ - } \ - } while (0) -+ - #define LGTD_SNPRINTF_APPEND(buf, i, bufsz, ...) do { \ - int n = snprintf(&(buf)[(i)], bufsz - i, __VA_ARGS__); \ - (i) = LGTD_MIN((i) + n, bufsz); \ - } while (0) - -+#if LGTD_BIG_ENDIAN_SYSTEM -+# define LGTD_STATIC_HTONS(s) (s) -+# define LGTD_STATIC_HTONL(l) (l) -+#else -+# define LGTD_STATIC_HTONS(s) ((((s) << 8) & 0xff00) | (((s) >> 8) & 0xff)) -+# define LGTD_STATIC_HTONL(l) ( \ -+ (((l) & 0xff000000) >> 24) | (((l) & 0x00ff0000) >> 8) \ -+ | (((l) & 0x0000ff00) << 8) | (((l) & 0x000000ff) << 24) \ -+) -+#endif -+ -+ - enum lgtd_verbosity { - LGTD_DEBUG = 0, - LGTD_INFO, -diff --git a/lifx/broadcast.c b/lifx/broadcast.c ---- a/lifx/broadcast.c -+++ b/lifx/broadcast.c -@@ -17,12 +17,15 @@ - - #include <sys/queue.h> - #include <sys/tree.h> -+#include <sys/socket.h> - #include <arpa/inet.h> -+#include <net/if.h> - #include <netinet/in.h> - #include <assert.h> - #include <endian.h> - #include <err.h> - #include <errno.h> -+#include <ifaddrs.h> - #include <stdarg.h> - #include <stdbool.h> - #include <stdint.h> -@@ -52,16 +55,37 @@ - }; - - static bool -+lgtd_lifx_broadcast_send_packet(const void *pkt, -+ int pkt_sz, -+ const struct sockaddr *addr, -+ ev_socklen_t addrlen) -+{ -+ char addr_str[INET6_ADDRSTRLEN]; -+ LGTD_SOCKADDRTOA(addr, addr_str); -+ lgtd_debug("broadcasting LIFX discovery packet on %s", addr_str); -+ -+ int nbytes, socket = lgtd_lifx_broadcast_endpoint.socket; -+ do { -+ nbytes = sendto(socket, pkt, pkt_sz, 0, addr, addrlen); -+ } while (nbytes == -1 && EVUTIL_SOCKET_ERROR() == EINTR); -+ -+ if (nbytes != pkt_sz) { -+ void (*warnfn)(const char *fmt, ...) = nbytes == -1 ? -+ lgtd_warn : lgtd_warnx; -+ warnfn("couldn't broadcast LIFX discovery packet on %s", addr_str); -+ return false; -+ } -+ -+ return true; -+} -+ -+static bool - lgtd_lifx_broadcast_handle_write(void) - { - assert(lgtd_lifx_broadcast_endpoint.socket != -1); - -- struct sockaddr_in lifx_addr = { -- .sin_family = AF_INET, -- .sin_addr = { INADDR_BROADCAST }, -- .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT), -- .sin_zero = { 0 } -- }; -+ const uint16_t lifx_port = htons(LGTD_LIFX_PROTOCOL_PORT); -+ - struct lgtd_lifx_packet_header get_pan_gateway; - lgtd_lifx_wire_setup_header( - &get_pan_gateway, -@@ -71,31 +95,50 @@ - LGTD_LIFX_GET_PAN_GATEWAY - ); - -- int nbytes; --retry: -- nbytes = sendto( -- lgtd_lifx_broadcast_endpoint.socket, -- (void *)&get_pan_gateway, -- sizeof(get_pan_gateway), -- 0, -- (const struct sockaddr *)&lifx_addr, -- sizeof(lifx_addr) -- ); -- if (nbytes == sizeof(get_pan_gateway)) { -- if (event_del(lgtd_lifx_broadcast_endpoint.write_ev)) { -- lgtd_err(1, "can't setup events"); -+ bool ok = false; -+ struct ifaddrs *ifaddrs = NULL; -+ if (getifaddrs(&ifaddrs)) { -+ struct sockaddr_in lifx_bcast_addr = { -+ .sin_family = AF_INET, -+ .sin_addr = { INADDR_BROADCAST }, -+ .sin_port = lifx_port, -+ .sin_zero = { 0 } -+ }; -+ char addr_str[INET6_ADDRSTRLEN]; -+ lgtd_warn( -+ "can't fetch the list of network interfaces, falling back on %s", -+ LGTD_SOCKADDRTOA((struct sockaddr *)&lifx_bcast_addr, addr_str) -+ ); -+ ok = lgtd_lifx_broadcast_send_packet( -+ &get_pan_gateway, -+ sizeof(get_pan_gateway), -+ (struct sockaddr *)&lifx_bcast_addr, -+ sizeof(lifx_bcast_addr) -+ ); -+ } else { -+ for (struct ifaddrs *ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { -+ // NOTE: IPv6 doesn't implement broadcast -+ if (ifa->ifa_broadaddr != NULL -+ && (ifa->ifa_flags & IFF_BROADCAST) -+ && ifa->ifa_broadaddr->sa_family == AF_INET -+ && ifa->ifa_netmask != NULL) { -+ struct sockaddr *addr = ifa->ifa_broadaddr; -+ ((struct sockaddr_in *)addr)->sin_port = lifx_port; -+ ev_socklen_t addrlen = sizeof(struct sockaddr_in); -+ bool sent = lgtd_lifx_broadcast_send_packet( -+ &get_pan_gateway, sizeof(get_pan_gateway), addr, addrlen -+ ); -+ ok = sent || ok; -+ } - } -- return true; -+ freeifaddrs(ifaddrs); - } -- if (nbytes == -1) { -- if (EVUTIL_SOCKET_ERROR() == EINTR) { -- goto retry; -- } -- lgtd_warn("can't broadcast discovery packet"); -- } else { -- lgtd_warnx("can't broadcast discovery packet"); -+ -+ if (ok && event_del(lgtd_lifx_broadcast_endpoint.write_ev)) { -+ lgtd_err(1, "can't setup events"); - } -- return false; -+ -+ return ok; - } - - static void -diff --git a/lifx/bulb.c b/lifx/bulb.c ---- a/lifx/bulb.c -+++ b/lifx/bulb.c -@@ -274,9 +274,9 @@ - - void - lgtd_lifx_bulb_set_ip_state(struct lgtd_lifx_bulb *bulb, -- enum lgtd_lifx_bulb_ips ip_id, -- const struct lgtd_lifx_ip_state *state, -- lgtd_time_mono_t received_at) -+ enum lgtd_lifx_bulb_ips ip_id, -+ const struct lgtd_lifx_ip_state *state, -+ lgtd_time_mono_t received_at) - { - assert(bulb); - assert(state); -diff --git a/lifx/tagging.h b/lifx/tagging.h ---- a/lifx/tagging.h -+++ b/lifx/tagging.h -@@ -17,6 +17,8 @@ - - #pragma once - -+struct lgtd_lifx_gateway; -+ - extern struct lgtd_lifx_tag_list lgtd_lifx_tags; - - struct lgtd_lifx_site { -diff --git a/tests/core/mock_event2.h b/tests/core/mock_event2.h ---- a/tests/core/mock_event2.h -+++ b/tests/core/mock_event2.h -@@ -210,3 +210,21 @@ - return 0; - } - #endif -+ -+#ifndef MOCKED_EVUTIL_CLOSESOCKET -+int -+evutil_closesocket(evutil_socket_t sock) -+{ -+ (void)sock; -+ return -1; -+} -+#endif -+ -+#ifndef MOCKED_EVUTIL_MAKE_LISTEN_SOCKET_REUSEABLE -+int -+evutil_make_listen_socket_reuseable(evutil_socket_t sock) -+{ -+ (void)sock; -+ return -1; -+} -+#endif -diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c ---- a/tests/core/tests_utils.c -+++ b/tests/core/tests_utils.c -@@ -213,3 +213,39 @@ - - free(path); - } -+ -+void -+lgtd_tests_check_sockaddr_in(const struct sockaddr *addr, -+ int addrlen, -+ int expected_family, -+ uint32_t expected_addr, -+ uint16_t expected_port) -+{ -+ if (!addr) { -+ lgtd_errx(1, "missing addr"); -+ } -+ -+ if (addr->sa_family != expected_family) { -+ lgtd_errx( -+ 1, "got address of type %d (expected %d)", -+ addr->sa_family, expected_family -+ ); -+ } -+ const struct sockaddr_in *inaddr = (const struct sockaddr_in *)addr; -+ if (inaddr->sin_addr.s_addr != expected_addr) { -+ lgtd_errx( -+ 1, "got address %#x (expected %#x)", -+ inaddr->sin_addr.s_addr, expected_addr -+ ); -+ } -+ uint16_t port = ntohs(inaddr->sin_port); -+ if (port != expected_port) { -+ lgtd_errx(1, "got port %hu (expected %u)", port, expected_port); -+ } -+ if (addrlen != -1 && addrlen != sizeof(*inaddr)) { -+ lgtd_errx( -+ 1, "got invalid addrlen %u (expected %ju)", -+ addrlen, (uintmax_t)sizeof(*inaddr) -+ ); -+ } -+} -diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h ---- a/tests/core/tests_utils.h -+++ b/tests/core/tests_utils.h -@@ -1,6 +1,7 @@ - #pragma once - - struct bufferevent; -+struct sockaddr; - - static inline bool - lgtd_tests_lifx_header_has_flags(const struct lgtd_lifx_packet_header *hdr, -@@ -32,6 +33,12 @@ - return true; - } - -+static inline void -+lgtd_tests_hr(void) -+{ -+ puts("----"); -+} -+ - char *lgtd_tests_make_temp_dir(void); - void lgtd_tests_remove_temp_dir(char *); - -@@ -44,3 +51,4 @@ - int); - struct lgtd_listen *lgtd_tests_insert_mock_listener(const char *, uint16_t); - struct lgtd_client *lgtd_tests_insert_mock_client(struct bufferevent *); -+void lgtd_tests_check_sockaddr_in(const struct sockaddr *, int, int, uint32_t, uint16_t); -diff --git a/tests/lifx/broadcast/CMakeLists.txt b/tests/lifx/broadcast/CMakeLists.txt -new file mode 100644 ---- /dev/null -+++ b/tests/lifx/broadcast/CMakeLists.txt -@@ -0,0 +1,21 @@ -+INCLUDE_DIRECTORIES( -+ ${CMAKE_CURRENT_SOURCE_DIR} -+ ${CMAKE_CURRENT_BINARY_DIR} -+) -+ -+ADD_CORE_LIBRARY( -+ test_lifx_broadcast STATIC -+ ${LIGHTSD_SOURCE_DIR}/core/stats.c -+ ${LIGHTSD_SOURCE_DIR}/core/utils.c -+ ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c -+ ${CMAKE_CURRENT_SOURCE_DIR}/../../core/tests_utils.c -+) -+ -+FUNCTION(ADD_BROADCAST_TEST TEST_SOURCE) -+ ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_broadcast) -+ENDFUNCTION() -+ -+FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") -+FOREACH(TEST ${TESTS}) -+ ADD_BROADCAST_TEST(${TEST}) -+ENDFOREACH() -diff --git a/tests/lifx/broadcast/test_broadcast_send_packet.c b/tests/lifx/broadcast/test_broadcast_send_packet.c -new file mode 100644 ---- /dev/null -+++ b/tests/lifx/broadcast/test_broadcast_send_packet.c -@@ -0,0 +1,146 @@ -+#include <sys/socket.h> -+ -+ssize_t mock_sendto(int, const void *, size_t, int, -+ const struct sockaddr *, socklen_t); -+ -+#define sendto(socket, buffer, length, flags, dest_addr, dest_len) \ -+ mock_sendto(socket, buffer, length, flags, dest_addr, dest_len) -+ -+#include "broadcast.c" -+ -+#include "mock_bulb.h" -+#include "mock_event2.h" -+#include "mock_gateway.h" -+#include "mock_log.h" -+#include "mock_tagging.h" -+#include "mock_wire_proto.h" -+ -+#include "tests_utils.h" -+ -+enum { MOCK_SOCKET_FD = 32 }; -+static const char MOCK_PKT[] = "fake packet"; -+static const struct sockaddr_in MOCK_ADDR = { -+ .sin_family = AF_INET, -+ .sin_addr = { INADDR_BROADCAST }, -+ .sin_port = LGTD_STATIC_HTONS(LGTD_LIFX_PROTOCOL_PORT), -+ .sin_zero = { 0 } -+}; -+ -+enum mock_sendto_calls { -+ TEST_SENDTO_OK = 0, -+ TEST_SENDTO_PARTIAL, -+ TEST_SENDTO_EINTR, -+ TEST_SENDTO_EINTR_OK, -+ TEST_SENDTO_ERROR, -+}; -+ -+static int mock_sendto_call_count = 0; -+ -+ssize_t -+mock_sendto(int socket, -+ const void *buffer, -+ size_t length, -+ int flags, -+ const struct sockaddr *dest_addr, -+ socklen_t dest_len) -+{ -+ if (socket != MOCK_SOCKET_FD) { -+ lgtd_errx( -+ 1, "got socket=%jd (expected %d)", (intmax_t)socket, MOCK_SOCKET_FD -+ ); -+ } -+ -+ if (length != sizeof(MOCK_PKT)) { -+ lgtd_errx( -+ 1, "got length=%ju (expected %ju)", -+ (uintmax_t)length, (uintmax_t)sizeof(MOCK_PKT) -+ ); -+ } -+ -+ if (strcmp((const char *)buffer, MOCK_PKT)) { -+ lgtd_errx( -+ 1, "got buffer [%.*s] (expected [%s])", -+ (int)length, (const char *)buffer, MOCK_PKT -+ ); -+ } -+ -+ if (flags) { -+ lgtd_errx(1, "got flags=%#x (expected 0x0)", flags); -+ } -+ -+ lgtd_tests_check_sockaddr_in( -+ dest_addr, dest_len, AF_INET, INADDR_BROADCAST, LGTD_LIFX_PROTOCOL_PORT -+ ); -+ -+ switch (mock_sendto_call_count++) { -+ case TEST_SENDTO_OK: -+ return length; -+ case TEST_SENDTO_PARTIAL: -+ return length - 1; -+ case TEST_SENDTO_EINTR: -+ errno = EINTR; -+ return -1; -+ case TEST_SENDTO_EINTR_OK: -+ return length; -+ case TEST_SENDTO_ERROR: -+ errno = EIO; -+ return -1; -+ default: -+ lgtd_errx(1, "sendto was called too many times"); -+ } -+} -+ -+int -+main(void) -+{ -+ lgtd_lifx_broadcast_endpoint.socket = MOCK_SOCKET_FD; -+ const struct sockaddr *addr = (const struct sockaddr *)&MOCK_ADDR; -+ -+ bool ok; -+ -+ // ok -+ ok = lgtd_lifx_broadcast_send_packet( -+ MOCK_PKT, sizeof(MOCK_PKT), addr, sizeof(MOCK_ADDR) -+ ); -+ if (mock_sendto_call_count != 1) { -+ lgtd_errx(1, "sendto wasn't called"); -+ } -+ if (!ok) { -+ lgtd_errx(1, "broadcast_send_packet returned false (expected true)"); -+ } -+ -+ // partial -+ ok = lgtd_lifx_broadcast_send_packet( -+ MOCK_PKT, sizeof(MOCK_PKT), addr, sizeof(MOCK_ADDR) -+ ); -+ if (mock_sendto_call_count != 2) { -+ lgtd_errx(1, "sendto wasn't called"); -+ } -+ if (ok) { -+ lgtd_errx(1, "broadcast_send_packet returned true (expected false)"); -+ } -+ -+ // eintr -+ ok = lgtd_lifx_broadcast_send_packet( -+ MOCK_PKT, sizeof(MOCK_PKT), addr, sizeof(MOCK_ADDR) -+ ); -+ if (mock_sendto_call_count != 4) { -+ lgtd_errx(1, "sendto wasn't called"); -+ } -+ if (!ok) { -+ lgtd_errx(1, "broadcast_send_packet returned false (expected true)"); -+ } -+ -+ // error -+ ok = lgtd_lifx_broadcast_send_packet( -+ MOCK_PKT, sizeof(MOCK_PKT), addr, sizeof(MOCK_ADDR) -+ ); -+ if (mock_sendto_call_count != 5) { -+ lgtd_errx(1, "sendto wasn't called"); -+ } -+ if (ok) { -+ lgtd_errx(1, "broadcast_send_packet returned true (expected false)"); -+ } -+ -+ return 0; -+} -diff --git a/tests/lifx/broadcast/test_broadcast_write_callback.c b/tests/lifx/broadcast/test_broadcast_write_callback.c -new file mode 100644 ---- /dev/null -+++ b/tests/lifx/broadcast/test_broadcast_write_callback.c -@@ -0,0 +1,368 @@ -+#include <sys/types.h> -+#include <sys/socket.h> -+#include <ifaddrs.h> -+ -+int mock_getifaddrs(struct ifaddrs **); -+void mock_freeifaddrs(struct ifaddrs *); -+ssize_t mock_sendto(int, const void *, size_t, int, -+ const struct sockaddr *, socklen_t); -+ -+#define getifaddrs(ifap) mock_getifaddrs(ifap) -+#define freeifaddrs(ifp) mock_freeifaddrs(ifp) -+#define sendto(socket, buffer, length, flags, dest_addr, dest_len) \ -+ mock_sendto(socket, buffer, length, flags, dest_addr, dest_len) -+ -+#include "broadcast.c" -+ -+#include "mock_bulb.h" -+#define MOCKED_EVENT_DEL -+#include "mock_event2.h" -+#include "mock_gateway.h" -+#include "mock_log.h" -+#include "mock_tagging.h" -+#define MOCKED_LGTD_LIFX_WIRE_SETUP_HEADER -+#include "mock_wire_proto.h" -+ -+#include "tests_utils.h" -+ -+enum { MOCK_SOCKET_FD = 32 }; -+static struct event *MOCK_WRITE_EV = (struct event *)0x7061726973; -+ -+// /24 -+#define TEST_IPV4_NETMASK_ADDR_24 LGTD_STATIC_HTONL(0xffffff00) -+static struct sockaddr_in TEST_IPV4_NETMASK_SOCKADDR_24 = { -+ .sin_family = AF_INET, .sin_addr = { TEST_IPV4_NETMASK_ADDR_24 } -+}; -+ -+// /16 -+#define TEST_IPV4_NETMASK_ADDR_16 LGTD_STATIC_HTONL(0xffff0000) -+static struct sockaddr_in TEST_IPV4_NETMASK_SOCKADDR_16 = { -+ .sin_family = AF_INET, .sin_addr = { TEST_IPV4_NETMASK_ADDR_16 } -+}; -+ -+// 192.168.42.255 -+#define TEST_IPV4_BROADCAST_ADDR_CLASS_C LGTD_STATIC_HTONL(0xc0a82aff) -+static struct sockaddr_in TEST_IPV4_BROADCAST_SOCKADDR_CLASS_C = { -+ .sin_family = AF_INET, .sin_addr = { TEST_IPV4_BROADCAST_ADDR_CLASS_C } -+}; -+ -+// 10.10.255.255 -+#define TEST_IPV4_BROADCAST_ADDR_CLASS_A LGTD_STATIC_HTONL(0x0a0affff) -+static struct sockaddr_in TEST_IPV4_BROADCAST_SOCKADDR_CLASS_A = { -+ .sin_family = AF_INET, .sin_addr = { TEST_IPV4_BROADCAST_ADDR_CLASS_A } -+}; -+ -+// 82.66.148.158 -+#define TEST_IPV4_UNICAST_ADDR_ROUTABLE LGTD_STATIC_HTONL(0x5242949e) -+static struct sockaddr_in TEST_IPV4_UNICAST_SOCKADDR_ROUTABLE = { -+ .sin_family = AF_INET, .sin_addr = { TEST_IPV4_UNICAST_ADDR_ROUTABLE } -+}; -+ -+static struct sockaddr_in6 TEST_IPV6_NETMASK_SOCKADDR_64 = { -+ .sin6_family = AF_INET6 -+}; -+static struct sockaddr_in6 TEST_IPV6_UNICAST_SOCKADDR = { -+ .sin6_family = AF_INET6 -+}; -+ -+static struct ifaddrs MOCK_IFSOCKADDR_IPV4_BROADCAST_SOCKADDR_CLASS_C = { -+ .ifa_flags = IFF_BROADCAST, -+ .ifa_broadaddr = (struct sockaddr *)&TEST_IPV4_BROADCAST_SOCKADDR_CLASS_C, -+ .ifa_netmask = (struct sockaddr *)&TEST_IPV4_NETMASK_SOCKADDR_24, -+ .ifa_next = NULL -+}; -+static struct ifaddrs MOCK_IFSOCKADDR_IPV4_BROADCAST_SOCKADDR_CLASS_A = { -+ .ifa_flags = IFF_BROADCAST, -+ .ifa_broadaddr = (struct sockaddr *)&TEST_IPV4_BROADCAST_SOCKADDR_CLASS_A, -+ .ifa_netmask = (struct sockaddr *)&TEST_IPV4_NETMASK_SOCKADDR_16, -+ .ifa_next = &MOCK_IFSOCKADDR_IPV4_BROADCAST_SOCKADDR_CLASS_C -+}; -+static struct ifaddrs MOCK_IFSOCKADDR_IPV4_UNICAST_SOCKADDR_ROUTABLE = { -+ .ifa_dstaddr = (struct sockaddr *)&TEST_IPV4_UNICAST_SOCKADDR_ROUTABLE, -+ .ifa_netmask = (struct sockaddr *)&TEST_IPV4_NETMASK_SOCKADDR_24, -+ .ifa_next = &MOCK_IFSOCKADDR_IPV4_BROADCAST_SOCKADDR_CLASS_A -+}; -+static struct ifaddrs MOCK_IFSOCKADDR_IPV6_UNICAST_SOCKADDR = { -+ .ifa_dstaddr = (struct sockaddr *)&TEST_IPV6_UNICAST_SOCKADDR, -+ .ifa_netmask = (struct sockaddr *)&TEST_IPV6_NETMASK_SOCKADDR_64, -+ .ifa_next = &MOCK_IFSOCKADDR_IPV4_UNICAST_SOCKADDR_ROUTABLE -+}; -+ -+static int event_del_call_count = 0; -+ -+int -+event_del(struct event *ev) -+{ -+ if (ev != MOCK_WRITE_EV) { -+ lgtd_errx( -+ 1, "event_del received invalid event=%p (expected %p)", -+ ev, MOCK_WRITE_EV -+ ); -+ } -+ -+ event_del_call_count++; -+ -+ return 0; -+} -+ -+static int mock_getifaddrs_call_count = 0; -+ -+int -+mock_getifaddrs(struct ifaddrs **ifap) -+{ -+ if (!ifap) { -+ lgtd_errx(1, "ifap souldn't be NULL"); -+ } -+ -+ *ifap = &MOCK_IFSOCKADDR_IPV6_UNICAST_SOCKADDR; -+ -+ mock_getifaddrs_call_count++; -+ -+ return 0; -+} -+ -+static int mock_freeifaddrs_call_count = 0; -+ -+void -+mock_freeifaddrs(struct ifaddrs *ifp) -+{ -+ if (ifp != &MOCK_IFSOCKADDR_IPV6_UNICAST_SOCKADDR) { -+ lgtd_errx( -+ 1, "got ifp = %p (expected %p)", -+ ifp, &MOCK_IFSOCKADDR_IPV6_UNICAST_SOCKADDR -+ ); -+ } -+ -+ mock_freeifaddrs_call_count++; -+} -+ -+static int mock_lifx_wire_setup_header_call_count = 0; -+ -+const struct lgtd_lifx_packet_info * -+lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, -+ enum lgtd_lifx_target_type target_type, -+ union lgtd_lifx_target target, -+ const uint8_t *site, -+ enum lgtd_lifx_packet_type packet_type) -+{ -+ if (!hdr) { -+ lgtd_errx(1, "missing header"); -+ } -+ -+ if (target_type != LGTD_LIFX_TARGET_ALL_DEVICES) { -+ lgtd_errx( -+ 1, "got target type %d (expected %d)", -+ target_type, LGTD_LIFX_TARGET_ALL_DEVICES -+ ); -+ } -+ -+ if (memcmp(&target, &LGTD_LIFX_UNSPEC_TARGET, sizeof(target))) { -+ lgtd_errx( -+ 1, "got unexpected target (expected LGTD_LIFX_UNSPEC_TARGET)" -+ ); -+ } -+ -+ if (site) { -+ lgtd_errx(1, "got unexpected site %p (expected NULL)", site); -+ } -+ -+ if (packet_type != LGTD_LIFX_GET_PAN_GATEWAY) { -+ lgtd_errx( -+ 1, "got unexpected packet type %d (expected %d)", -+ packet_type, LGTD_LIFX_GET_PAN_GATEWAY -+ ); -+ } -+ -+ mock_lifx_wire_setup_header_call_count++; -+ -+ return NULL; -+} -+ -+static int mock_sendto_call_count = 0; -+ -+enum mock_sendto_calls { -+ // 1st test case everything ok -+ TEST_OK_SENDTO_OK_ADDR_CLASS_A = 0, -+ TEST_OK_SENDTO_OK_ADDR_CLASS_C, -+ // 2nd test case one failure -+ TEST_PARTIAL_SENDTO_OK_ADDR_CLASS_A, -+ TEST_PARTIAL_SENDTO_ERROR_ADDR_CLASS_C, -+ // 3rd test case all failures -+ TEST_FAIL_SENDTO_ERROR_ADDR_CLASS_A, -+ TEST_FAIL_SENDTO_ERROR_ADDR_CLASS_C -+}; -+ -+ssize_t -+mock_sendto(int socket, -+ const void *buffer, -+ size_t length, -+ int flags, -+ const struct sockaddr *dest_addr, -+ socklen_t dest_len) -+{ -+ if (socket != MOCK_SOCKET_FD) { -+ lgtd_errx( -+ 1, "got socket=%jd (expected %d)", (intmax_t)socket, MOCK_SOCKET_FD -+ ); -+ } -+ -+ if (!buffer) { -+ lgtd_errx(1, "missing buffer"); -+ } -+ -+ if (length != sizeof(struct lgtd_lifx_packet_header)) { -+ lgtd_errx( -+ 1, "got buffer length=%ju (expected %ju)", -+ (uintmax_t)length, (uintmax_t)sizeof(struct lgtd_lifx_packet_header) -+ ); -+ } -+ -+ if (flags) { -+ lgtd_errx(1, "got flags=%#x (expected 0x0)", flags); -+ } -+ -+ switch (mock_sendto_call_count++) { -+ case TEST_PARTIAL_SENDTO_OK_ADDR_CLASS_A: -+ case TEST_OK_SENDTO_OK_ADDR_CLASS_A: -+ lgtd_tests_check_sockaddr_in( -+ dest_addr, -+ dest_len, -+ AF_INET, -+ TEST_IPV4_BROADCAST_ADDR_CLASS_A, -+ LGTD_LIFX_PROTOCOL_PORT -+ ); -+ return length; -+ case TEST_OK_SENDTO_OK_ADDR_CLASS_C: -+ lgtd_tests_check_sockaddr_in( -+ dest_addr, -+ dest_len, -+ AF_INET, -+ TEST_IPV4_BROADCAST_ADDR_CLASS_C, -+ LGTD_LIFX_PROTOCOL_PORT -+ ); -+ return length; -+ case TEST_PARTIAL_SENDTO_ERROR_ADDR_CLASS_C: -+ case TEST_FAIL_SENDTO_ERROR_ADDR_CLASS_C: -+ lgtd_tests_check_sockaddr_in( -+ dest_addr, -+ dest_len, -+ AF_INET, -+ TEST_IPV4_BROADCAST_ADDR_CLASS_C, -+ LGTD_LIFX_PROTOCOL_PORT -+ ); -+ return length - 1; -+ case TEST_FAIL_SENDTO_ERROR_ADDR_CLASS_A: -+ lgtd_tests_check_sockaddr_in( -+ dest_addr, -+ dest_len, -+ AF_INET, -+ TEST_IPV4_BROADCAST_ADDR_CLASS_A, -+ LGTD_LIFX_PROTOCOL_PORT -+ ); -+ return length - 1; -+ default: -+ lgtd_errx(1, "mock_sendto called too many times"); -+ } -+} -+ -+int -+main(void) -+{ -+ lgtd_lifx_broadcast_endpoint.socket = MOCK_SOCKET_FD; -+ lgtd_lifx_broadcast_endpoint.write_ev = MOCK_WRITE_EV; -+ -+ bool ok; -+ -+ // getifaddrs ok -+ ok = lgtd_lifx_broadcast_handle_write(); -+ if (!ok) { -+ lgtd_errx(1, "write callback returned false (expected true)"); -+ } -+ if (mock_lifx_wire_setup_header_call_count != 1) { -+ lgtd_errx( -+ 1, "mock_lifx_wire_setup_header_call_count=%d (expected 1)", -+ mock_lifx_wire_setup_header_call_count -+ ); -+ } -+ if (mock_sendto_call_count != 2) { -+ lgtd_errx( -+ 1, "mock_sendto_call_count=%d (expected 2)", -+ mock_sendto_call_count -+ ); -+ } -+ if (event_del_call_count != 1) { -+ lgtd_errx( -+ 1, "event_del_call_count=%d (expected 1)", event_del_call_count -+ ); -+ } -+ if (mock_freeifaddrs_call_count != 1) { -+ lgtd_errx( -+ 1, "freeifaddrs_call_count=%d (expected 1)", -+ mock_freeifaddrs_call_count -+ ); -+ } -+ -+ lgtd_tests_hr(); -+ -+ // getifaddrs ok, one send fails -+ ok = lgtd_lifx_broadcast_handle_write(); -+ if (!ok) { -+ lgtd_errx(1, "write callback returned false (expected true)"); -+ } -+ if (mock_lifx_wire_setup_header_call_count != 2) { -+ lgtd_errx( -+ 1, "mock_lifx_wire_setup_header_call_count=%d (expected 2)", -+ mock_lifx_wire_setup_header_call_count -+ ); -+ } -+ if (mock_sendto_call_count != 4) { -+ lgtd_errx( -+ 1, "mock_sendto_call_count=%d (expected 4)", -+ mock_sendto_call_count -+ ); -+ } -+ if (event_del_call_count != 2) { -+ lgtd_errx( -+ 1, "event_del_call_count=%d (expected 2)", event_del_call_count -+ ); -+ } -+ if (mock_freeifaddrs_call_count != 2) { -+ lgtd_errx( -+ 1, "freeifaddrs_call_count=%d (expected 2)", -+ mock_freeifaddrs_call_count -+ ); -+ } -+ -+ lgtd_tests_hr(); -+ -+ // getifaddrs ok, all sends fail -+ ok = lgtd_lifx_broadcast_handle_write(); -+ if (ok) { -+ lgtd_errx(1, "write callback returned true (expected false)"); -+ } -+ if (mock_lifx_wire_setup_header_call_count != 3) { -+ lgtd_errx( -+ 1, "mock_lifx_wire_setup_header_call_count=%d (expected 2)", -+ mock_lifx_wire_setup_header_call_count -+ ); -+ } -+ if (mock_sendto_call_count != 6) { -+ lgtd_errx( -+ 1, "mock_sendto_call_count=%d (expected 6)", -+ mock_sendto_call_count -+ ); -+ } -+ if (event_del_call_count != 2) { -+ lgtd_errx( -+ 1, "event_del_call_count=%d (expected 2)", event_del_call_count -+ ); -+ } -+ if (mock_freeifaddrs_call_count != 3) { -+ lgtd_errx( -+ 1, "freeifaddrs_call_count=%d (expected 3)", -+ mock_freeifaddrs_call_count -+ ); -+ } -+ -+ return 0; -+} -diff --git a/tests/lifx/broadcast/test_broadcast_write_callback_getifaddrs_fails.c b/tests/lifx/broadcast/test_broadcast_write_callback_getifaddrs_fails.c -new file mode 100644 ---- /dev/null -+++ b/tests/lifx/broadcast/test_broadcast_write_callback_getifaddrs_fails.c -@@ -0,0 +1,222 @@ -+#include <sys/types.h> -+#include <sys/socket.h> -+#include <ifaddrs.h> -+ -+int mock_getifaddrs(struct ifaddrs **); -+void mock_freeifaddrs(struct ifaddrs *); -+ssize_t mock_sendto(int, const void *, size_t, int, -+ const struct sockaddr *, socklen_t); -+ -+#define getifaddrs(ifap) mock_getifaddrs(ifap) -+#define freeifaddrs(ifp) mock_freeifaddrs(ifp) -+#define sendto(socket, buffer, length, flags, dest_addr, dest_len) \ -+ mock_sendto(socket, buffer, length, flags, dest_addr, dest_len) -+ -+#include "broadcast.c" -+ -+#include "mock_bulb.h" -+#define MOCKED_EVENT_DEL -+#include "mock_event2.h" -+#include "mock_gateway.h" -+#include "mock_log.h" -+#include "mock_tagging.h" -+#define MOCKED_LGTD_LIFX_WIRE_SETUP_HEADER -+#include "mock_wire_proto.h" -+ -+#include "tests_utils.h" -+ -+enum { MOCK_SOCKET_FD = 32 }; -+static struct event *MOCK_WRITE_EV = (struct event *)0x7061726973; -+static const struct sockaddr_in LIFX_BROADCAST_ADDR = { -+ .sin_family = AF_INET, -+ .sin_addr = { INADDR_BROADCAST }, -+ .sin_port = LGTD_STATIC_HTONS(LGTD_LIFX_PROTOCOL_PORT), -+ .sin_zero = { 0 } -+}; -+ -+static int event_del_call_count = 0; -+ -+int -+event_del(struct event *ev) -+{ -+ if (ev != MOCK_WRITE_EV) { -+ lgtd_errx( -+ 1, "event_del received invalid event=%p (expected %p)", -+ ev, MOCK_WRITE_EV -+ ); -+ } -+ -+ event_del_call_count++; -+ -+ return 0; -+} -+ -+int -+mock_getifaddrs(struct ifaddrs **ifap) -+{ -+ if (!ifap) { -+ lgtd_errx(1, "ifap souldn't be NULL"); -+ } -+ -+ errno = ENOSYS; -+ -+ return -1; -+} -+ -+void -+mock_freeifaddrs(struct ifaddrs *ifp) -+{ -+ (void)ifp; -+ -+ lgtd_errx(1, "freeifaddrs shouldn't have been called"); -+} -+ -+static int mock_lifx_wire_setup_header_call_count = 0; -+ -+const struct lgtd_lifx_packet_info * -+lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, -+ enum lgtd_lifx_target_type target_type, -+ union lgtd_lifx_target target, -+ const uint8_t *site, -+ enum lgtd_lifx_packet_type packet_type) -+{ -+ if (!hdr) { -+ lgtd_errx(1, "missing header"); -+ } -+ -+ if (target_type != LGTD_LIFX_TARGET_ALL_DEVICES) { -+ lgtd_errx( -+ 1, "got target type %d (expected %d)", -+ target_type, LGTD_LIFX_TARGET_ALL_DEVICES -+ ); -+ } -+ -+ if (memcmp(&target, &LGTD_LIFX_UNSPEC_TARGET, sizeof(target))) { -+ lgtd_errx( -+ 1, "got unexpected target (expected LGTD_LIFX_UNSPEC_TARGET)" -+ ); -+ } -+ -+ if (site) { -+ lgtd_errx(1, "got unexpected site %p (expected NULL)", site); -+ } -+ -+ if (packet_type != LGTD_LIFX_GET_PAN_GATEWAY) { -+ lgtd_errx( -+ 1, "got unexpected packet type %d (expected %d)", -+ packet_type, LGTD_LIFX_GET_PAN_GATEWAY -+ ); -+ } -+ -+ mock_lifx_wire_setup_header_call_count++; -+ -+ return NULL; -+} -+ -+enum mock_sendto_calls { -+ MOCK_SENDTO_OK = 0, -+ MOCK_SENDTO_ERROR, -+}; -+ -+static int mock_sendto_call_count = 0; -+ -+ssize_t -+mock_sendto(int socket, -+ const void *buffer, -+ size_t length, -+ int flags, -+ const struct sockaddr *dest_addr, -+ socklen_t dest_len) -+{ -+ if (socket != MOCK_SOCKET_FD) { -+ lgtd_errx( -+ 1, "got socket=%jd (expected %d)", (intmax_t)socket, MOCK_SOCKET_FD -+ ); -+ } -+ -+ if (!buffer) { -+ lgtd_errx(1, "missing buffer"); -+ } -+ -+ if (length != sizeof(struct lgtd_lifx_packet_header)) { -+ lgtd_errx( -+ 1, "got buffer length=%ju (expected %ju)", -+ (uintmax_t)length, (uintmax_t)sizeof(struct lgtd_lifx_packet_header) -+ ); -+ } -+ -+ if (flags) { -+ lgtd_errx(1, "got flags=%#x (expected 0x0)", flags); -+ } -+ -+ lgtd_tests_check_sockaddr_in( -+ dest_addr, dest_len, AF_INET, INADDR_BROADCAST, LGTD_LIFX_PROTOCOL_PORT -+ ); -+ -+ switch (mock_sendto_call_count++) { -+ case MOCK_SENDTO_OK: -+ return length; -+ case MOCK_SENDTO_ERROR: -+ errno = EIO; -+ return -1; -+ default: -+ lgtd_errx(1, "sendto was called too many times"); -+ } -+} -+ -+int -+main(void) -+{ -+ lgtd_lifx_broadcast_endpoint.socket = MOCK_SOCKET_FD; -+ lgtd_lifx_broadcast_endpoint.write_ev = MOCK_WRITE_EV; -+ -+ bool ok; -+ -+ // getifaddrs fails -+ ok = lgtd_lifx_broadcast_handle_write(); -+ if (!ok) { -+ lgtd_errx(1, "write callback returned false (expected true)"); -+ } -+ if (mock_lifx_wire_setup_header_call_count != 1) { -+ lgtd_errx( -+ 1, "mock_lifx_wire_setup_header_call_count=%d (expected 1)", -+ mock_lifx_wire_setup_header_call_count -+ ); -+ } -+ if (mock_sendto_call_count != 1) { -+ lgtd_errx( -+ 1, "mock_sendto_call_count=%d (expected 1)", -+ mock_sendto_call_count -+ ); -+ } -+ if (event_del_call_count != 1) { -+ lgtd_errx( -+ 1, "event_del_call_count=%d (expected 1)", event_del_call_count -+ ); -+ } -+ -+ // getifaddrs & lgtd_lifx_broadcast_send_packet fail -+ ok = lgtd_lifx_broadcast_handle_write(); -+ if (ok) { -+ lgtd_errx(1, "write callback returned true (expected false)"); -+ } -+ if (mock_lifx_wire_setup_header_call_count != 2) { -+ lgtd_errx( -+ 1, "mock_lifx_wire_setup_header_call_count=%d (expected 2)", -+ mock_lifx_wire_setup_header_call_count -+ ); -+ } -+ if (mock_sendto_call_count != 2) { -+ lgtd_errx( -+ 1, "mock_sendto_call_count=%d (expected 2)", -+ mock_sendto_call_count -+ ); -+ } -+ if (event_del_call_count != 1) { -+ lgtd_errx( -+ 1, "event_del_call_count=%d (expected 1)", event_del_call_count -+ ); -+ } -+ -+ return 0; -+} -diff --git a/tests/lifx/mock_bulb.h b/tests/lifx/mock_bulb.h -new file mode 100644 ---- /dev/null -+++ b/tests/lifx/mock_bulb.h -@@ -0,0 +1,138 @@ -+#pragma once -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_GET -+struct lgtd_lifx_bulb * -+lgtd_lifx_bulb_get(const uint8_t *addr) -+{ -+ (void)addr; -+ return NULL; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_OPEN -+struct lgtd_lifx_bulb * -+lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *gw, const uint8_t *addr) -+{ -+ (void)gw; -+ (void)addr; -+ return NULL; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_CLOSE -+void -+lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) -+{ -+ (void)bulb; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_HAS_LABEL -+bool -+lgtd_lifx_bulb_has_label(const struct lgtd_lifx_bulb *bulb, -+ const char *label) -+{ -+ (void)bulb; -+ (void)label; -+ return false; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_LIGHT_STATE -+void -+lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *bulb, -+ const struct lgtd_lifx_light_state *state, -+ lgtd_time_mono_t received_at) -+{ -+ (void)bulb; -+ (void)state; -+ (void)received_at; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_POWER_STATE -+void -+lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *bulb, uint16_t power) -+{ -+ (void)bulb; -+ (void)power; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_TAGS -+void -+lgtd_lifx_bulb_set_tags(struct lgtd_lifx_bulb *bulb, uint64_t tags) -+{ -+ (void)bulb; -+ (void)tags; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_IP_STATE -+void -+lgtd_lifx_bulb_set_ip_state(struct lgtd_lifx_bulb *bulb, -+ enum lgtd_lifx_bulb_ips ip_id, -+ const struct lgtd_lifx_ip_state *state, -+ lgtd_time_mono_t received_at) -+{ -+ (void)bulb; -+ (void)ip_id; -+ (void)state; -+ (void)received_at; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_IP_FIRMWARE_INFO -+void -+lgtd_lifx_bulb_set_ip_firmware_info(struct lgtd_lifx_bulb *bulb, -+ enum lgtd_lifx_bulb_ips ip_id, -+ const struct lgtd_lifx_ip_firmware_info *info, -+ lgtd_time_mono_t received_at) -+{ -+ (void)bulb; -+ (void)ip_id; -+ (void)info; -+ (void)received_at; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_PRODUCT_INFO -+void -+lgtd_lifx_bulb_set_product_info(struct lgtd_lifx_bulb *bulb, -+ const struct lgtd_lifx_product_info *info) -+{ -+ (void)bulb; -+ (void)info; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_RUNTIME_INFO -+void -+lgtd_lifx_bulb_set_runtime_info(struct lgtd_lifx_bulb *bulb, -+ const struct lgtd_lifx_runtime_info *info, -+ lgtd_time_mono_t received_at) -+{ -+ (void)bulb; -+ (void)info; -+ (void)received_at; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_LABEL -+void -+lgtd_lifx_bulb_set_label(struct lgtd_lifx_bulb *bulb, -+ const char label[LGTD_LIFX_LABEL_SIZE]) -+{ -+ (void)bulb; -+ (void)label; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_BULB_SET_AMBIENT_LIGHT -+void -+lgtd_lifx_bulb_set_ambient_light(struct lgtd_lifx_bulb *bulb, float illuminance) -+{ -+ (void)bulb; -+ (void)illuminance; -+} -+#endif -diff --git a/tests/lifx/mock_tagging.h b/tests/lifx/mock_tagging.h -new file mode 100644 ---- /dev/null -+++ b/tests/lifx/mock_tagging.h -@@ -0,0 +1,55 @@ -+#pragma once -+ -+#include "lifx/tagging.h" -+ -+struct lgtd_lifx_tag_list lgtd_lifx_tags = -+ LIST_HEAD_INITIALIZER(&lgtd_lifx_tags); -+ -+#ifndef MOCKED_LGTD_LIFX_TAGGING_FIND_TAG -+struct lgtd_lifx_tag * -+lgtd_lifx_tagging_find_tag(const char *tag_label) -+{ -+ (void)tag_label; -+ return NULL; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_TAGGING_ALLOCATE_TAG -+struct lgtd_lifx_tag * -+lgtd_lifx_tagging_allocate_tag(const char *tag_label) -+{ -+ (void)tag_label; -+ return NULL; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_TAGGING_DEALLOCATE_TAG -+void -+lgtd_lifx_tagging_deallocate_tag(struct lgtd_lifx_tag *tag) -+{ -+ (void)tag; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_TAGGING_INCREF -+struct lgtd_lifx_tag * -+lgtd_lifx_tagging_incref(const char *tag_label, -+ struct lgtd_lifx_gateway *gw, -+ int tag_id) -+{ -+ (void)tag_label; -+ (void)gw; -+ (void)tag_id; -+ return NULL; -+} -+#endif -+ -+#ifndef MOCKED_LGTD_LIFX_TAGGING_DECREF -+void -+lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, -+ struct lgtd_lifx_gateway *gw) -+{ -+ (void)tag; -+ (void)gw; -+} -+#endif -diff --git a/tests/lifx/wire_proto/CMakeLists.txt b/tests/lifx/wire_proto/CMakeLists.txt ---- a/tests/lifx/wire_proto/CMakeLists.txt -+++ b/tests/lifx/wire_proto/CMakeLists.txt -@@ -4,12 +4,12 @@ - ) - - ADD_CORE_LIBRARY( -- test_lifx_wire_proto_core STATIC -+ test_lifx_wire_proto STATIC - ${LIGHTSD_SOURCE_DIR}/core/utils.c - ) - - FUNCTION(ADD_WIRE_PROTO_TEST TEST_SOURCE) -- ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_wire_proto_core) -+ ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_wire_proto) - ENDFUNCTION() - - FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c")
--- a/optional_jsonrpc_args.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,644 +0,0 @@ -# HG changeset patch -# Parent f53fd0ef32c475da14c00890cf0b2d0f65bc1e9a -Correctly support optional arguments in the JSON-RPC API - -Passing too many arguments as an array also properly fails now. - -While we are at it, put the correct argument names in the documentation -so that it works for people passing argument by name. - -diff --git a/core/jsonrpc.c b/core/jsonrpc.c ---- a/core/jsonrpc.c -+++ b/core/jsonrpc.c -@@ -459,7 +459,7 @@ - } - } - -- return si == schema_size; -+ return !objsize || (si < schema_size && schema[si].optional); - } - - static bool -@@ -470,6 +470,17 @@ - int ntokens, - const char *json) - { -+ if (!ntokens) { -+ // "params" were omitted, make sure no args were required or that they -+ // are all optional: -+ while (schema_size--) { -+ if (!schema[schema_size].optional) { -+ return false; -+ } -+ } -+ return true; -+ } -+ - switch (tokens[0].type) { - case JSMN_OBJECT: - return lgtd_jsonrpc_extract_values_from_schema_and_dict( -@@ -716,7 +727,7 @@ - offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, t), - -1, - lgtd_jsonrpc_type_integer, -- false -+ true - ), - }; - -@@ -746,9 +757,12 @@ - if (k < 2500 || k > 9000 || errno == ERANGE) { - goto error_invalid_params; - } -- int t = strtol(&client->json[params.t->start], NULL, 10); -- if (t < 0 || errno == ERANGE) { -- goto error_invalid_params; -+ int t = 0; -+ if (params.t) { -+ t = strtol(&client->json[params.t->start], NULL, 10); -+ if (t < 0 || errno == ERANGE) { -+ goto error_invalid_params; -+ } - } - - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); -@@ -854,7 +868,7 @@ - offsetof(struct lgtd_jsonrpc_set_waveform_args, transient), - -1, - lgtd_jsonrpc_type_bool, -- false -+ true - ), - }; - -@@ -906,7 +920,8 @@ - 0, 1 - ); - skew_ratio -= UINT16_MAX / 2; -- bool transient = client->json[params.transient->start] == 't'; -+ bool transient = params.transient ? -+ client->json[params.transient->start] == 't' : true; - - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); - ok = lgtd_jsonrpc_build_target_list( -@@ -1102,43 +1117,24 @@ - int *batch_sent) - { - static const struct lgtd_jsonrpc_method methods[] = { -+ LGTD_JSONRPC_METHOD("power_on", lgtd_jsonrpc_check_and_call_power_on), -+ LGTD_JSONRPC_METHOD("power_off", lgtd_jsonrpc_check_and_call_power_off), - LGTD_JSONRPC_METHOD( -- "power_on", 1, // t -- lgtd_jsonrpc_check_and_call_power_on -+ "power_toggle", lgtd_jsonrpc_check_and_call_power_toggle - ), - LGTD_JSONRPC_METHOD( -- "power_off", 1, // t -- lgtd_jsonrpc_check_and_call_power_off -- ), -- LGTD_JSONRPC_METHOD( -- "power_toggle", 1, // t -- lgtd_jsonrpc_check_and_call_power_toggle -- ), -- LGTD_JSONRPC_METHOD( -- "set_light_from_hsbk", 6, // t, h, s, b, k, t -+ "set_light_from_hsbk", - lgtd_jsonrpc_check_and_call_set_light_from_hsbk - ), - LGTD_JSONRPC_METHOD( -- // t, waveform, h, s, b, k, period, cycles, skew_ratio, transient -- "set_waveform", 10, -- lgtd_jsonrpc_check_and_call_set_waveform -+ "set_waveform", lgtd_jsonrpc_check_and_call_set_waveform - ), - LGTD_JSONRPC_METHOD( -- "get_light_state", 1, // t -- lgtd_jsonrpc_check_and_call_get_light_state -+ "get_light_state", lgtd_jsonrpc_check_and_call_get_light_state - ), -- LGTD_JSONRPC_METHOD( -- "tag", 2, // t, tag -- lgtd_jsonrpc_check_and_call_tag -- ), -- LGTD_JSONRPC_METHOD( -- "untag", 2, // t, tag -- lgtd_jsonrpc_check_and_call_untag -- ), -- LGTD_JSONRPC_METHOD( -- "set_label", 2, // t, label -- lgtd_jsonrpc_check_and_call_set_label -- ) -+ LGTD_JSONRPC_METHOD("tag", lgtd_jsonrpc_check_and_call_tag), -+ LGTD_JSONRPC_METHOD("untag", lgtd_jsonrpc_check_and_call_untag), -+ LGTD_JSONRPC_METHOD("set_label", lgtd_jsonrpc_check_and_call_set_label) - }; - - if (batch_sent) { -@@ -1172,15 +1168,11 @@ - continue; - } - int diff = memcmp( -- methods[i].name, &client->json[request.method->start], methods[i].namelen -+ methods[i].name, -+ &client->json[request.method->start], -+ methods[i].namelen - ); - if (!diff) { -- int params_count = request.params ? request.params->size : 0; -- if (params_count != methods[i].params_count) { -- error_code = LGTD_JSONRPC_INVALID_PARAMS; -- error_msg = "Invalid number of parameters"; -- goto error; -- } - struct bufferevent *client_io = NULL; // keep compilers happy... - if (!request.id) { - // Ugly hack to behave correctly on jsonrpc notifications, it's -diff --git a/core/jsonrpc.h b/core/jsonrpc.h ---- a/core/jsonrpc.h -+++ b/core/jsonrpc.h -@@ -63,15 +63,13 @@ - struct lgtd_jsonrpc_method { - const char *name; - int namelen; -- int params_count; - void (*method)(struct lgtd_client *); - }; - --#define LGTD_JSONRPC_METHOD(name_, params_count_, method_) { \ -- .name = (name_), \ -- .namelen = sizeof((name_)) -1, \ -- .params_count = (params_count_), \ -- .method = (method_) \ -+#define LGTD_JSONRPC_METHOD(name_, method_) { \ -+ .name = (name_), \ -+ .namelen = sizeof((name_)) -1, \ -+ .method = (method_) \ - } - - enum lgtd_jsonrpc_error_code { -diff --git a/docs/protocol.rst b/docs/protocol.rst ---- a/docs/protocol.rst -+++ b/docs/protocol.rst -@@ -32,9 +32,7 @@ - +-----------------------------+------------------------------------------------+ - - The mac address (id) of each bulb can be found with get_light_state_ under the --``_lifx`` map, e.g: -- --:: -+``_lifx`` map, e.g:: - - "_lifx": { - "addr": "d0:73:d5:02:e5:30", -@@ -65,28 +63,30 @@ - - Power on (if they are off) or power off (if they are on) the given bulb(s). - --.. function:: set_light_from_hsbk(target, h, s, b, k, t) -+.. function:: set_light_from_hsbk(target, hue, saturation, brightness, kelvin[, transition]) - -- :param float h: Hue from 0 to 360. -- :param float s: Saturation from 0 to 1. -- :param float b: Brightness from 0 to 1. -- :param int k: Temperature in Kelvin from 2500 to 9000. -- :param int t: Transition duration to this color in ms. -+ :param float hue: From 0 to 360. -+ :param float saturation: From 0 to 1. -+ :param float brightness: From 0 to 1. -+ :param int kelvin: Temperature in Kelvin from 2500 to 9000. -+ :param int transition: Optional time in ms it will take for the bulb to turn -+ to this color. - --.. function:: set_waveform(target, waveform, h, s, b, k, period, cycles, skew_ratio, transient) -+.. function:: set_waveform(target, waveform, hue, saturation, brightness, kelvin, period, cycles, skew_ratio[, transient]) - - :param string waveform: One of ``SAW``, ``SINE``, ``HALF_SINE``, - ``TRIANGLE``, ``SQUARE``. -- :param float h: Hue from 0 to 360. -- :param float s: Saturation from 0 to 1. -- :param float b: Brightness from 0 to 1. -- :param int k: Temperature in Kelvin from 2500 to 9000. -- :param int period: milliseconds per cycle. -- :param int cycles: number of cycles. -- :param float skew_ratio: from 0 to 1. -- :param bool transient: if true the target will keep the color it has at the -+ :param float hue: From 0 to 360. -+ :param float saturation: From 0 to 1. -+ :param float brightness: From 0 to 1. -+ :param int kelvin: Temperature in Kelvin from 2500 to 9000. -+ :param int period: Milliseconds per cycle. -+ :param int cycles: Number of cycles. -+ :param float skew_ratio: From 0 to 1, see table below. -+ :param bool transient: If true the target will keep the color it has at the - end of the waveform, otherwise it will revert back to -- its original state. -+ its original state. This argument is optional and -+ defaults to true. - - The meaning of the ``skew_ratio`` argument depends on the type of waveform: - -@@ -129,9 +129,7 @@ - Tag (group) the given target bulb(s) with the given label (group name), then - label can be used as a target by prefixing it with ``#``. - -- To add a device to an existing "group" simply do: -- -- :: -+ To add a device to an existing "group" simply do:: - - tag(["#myexistingtag", "bulbtoadd"], "myexistingtag") - -@@ -143,9 +141,7 @@ - .. function:: untag(target, label) - - Remove the given tag from the given target bulb(s). To completely delete a -- tag (group), simple do: -- -- :: -+ tag (group), simply do:: - - untag("#myexistingtag", "myexistingtag") - -diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c ---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c -+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c -@@ -8,7 +8,7 @@ - - #include "test_jsonrpc_utils.h" - --static bool set_light_called = false; -+static int set_light_call_count = false; - - void - lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, -@@ -55,19 +55,38 @@ - 1, "Invalid temperature: %d, expected: 4200", kelvin - ); - } -- if (transition_msecs != 60) { -- errx( -- 1, "Invalid transition duration: %d, expected: 60", transition_msecs -- ); -+ switch (set_light_call_count++) { -+ case 0: -+ if (transition_msecs != 600) { -+ errx( -+ 1, "Invalid transition duration: %d, expected: 600", -+ transition_msecs -+ ); -+ } -+ break; -+ case 1: -+ if (transition_msecs != 0) { -+ errx( -+ 1, "Invalid transition duration: %d, expected: 0", -+ transition_msecs -+ ); -+ } -+ break; -+ default: -+ errx(1, "set_light_from_hsbk called too many times"); - } -- set_light_called = true; - } - - int - main(void) - { - jsmntok_t tokens[32]; -- const char json[] = ("{" -+ int parsed; -+ bool ok; -+ struct lgtd_jsonrpc_request req; -+ struct lgtd_client client = { .io = NULL, .current_request = &req }; -+ -+ const char *json = ("{" - "\"jsonrpc\": \"2.0\"," - "\"method\": \"set_light_from_hsbk\"," - "\"params\": {" -@@ -76,27 +95,44 @@ - "\"saturation\": 0.234, " - "\"brightness\": 1.0, " - "\"kelvin\": 4200," -- "\"transition\": 60" -+ "\"transition\": 600" - "}," - "\"id\": \"42\"" - "}"); -- int parsed = parse_json( -- tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) -- ); -- -- bool ok; -- struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; -- struct lgtd_client client = { -- .io = NULL, .current_request = &req, .json = json -- }; -+ client.json = json; -+ parsed = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); -+ memset(&req, 0, sizeof(req)); - ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); - if (!ok) { - errx(1, "can't parse request"); - } -+ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); -+ if (set_light_call_count != 1) { -+ errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); -+ } - -+ // optional transition argument -+ json = ("{" -+ "\"jsonrpc\": \"2.0\"," -+ "\"method\": \"set_light_from_hsbk\"," -+ "\"params\": {" -+ "\"target\": \"*\", " -+ "\"hue\": 324.2341514, " -+ "\"saturation\": 0.234, " -+ "\"brightness\": 1.0, " -+ "\"kelvin\": 4200" -+ "}," -+ "\"id\": \"42\"" -+ "}"); -+ client.json = json; -+ parsed = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); -+ memset(&req, 0, sizeof(req)); -+ ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); -+ if (!ok) { -+ errx(1, "can't parse request"); -+ } - lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); -- -- if (!set_light_called) { -+ if (set_light_call_count != 2) { - errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); - } - -diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c ---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c -+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c -@@ -8,7 +8,7 @@ - - #include "test_jsonrpc_utils.h" - --static bool set_light_called = false; -+static int set_light_call_count = 0; - - void - lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, -@@ -55,43 +55,73 @@ - 1, "Invalid temperature: %d, expected: 4200", kelvin - ); - } -- if (transition_msecs != 0) { -- errx( -- 1, "Invalid transition duration: %d, expected: 0", transition_msecs -- ); -+ switch (set_light_call_count++) { -+ case 0: -+ if (transition_msecs != 600) { -+ errx( -+ 1, "Invalid transition duration: %d, expected: 600", -+ transition_msecs -+ ); -+ } -+ break; -+ case 1: -+ if (transition_msecs != 0) { -+ errx( -+ 1, "Invalid transition duration: %d, expected: 0", -+ transition_msecs -+ ); -+ } -+ break; -+ default: -+ errx(1, "set_light_from_hsbk called too many times"); - } -- set_light_called = true; - } - - int - main(void) - { - jsmntok_t tokens[32]; -- const char json[] = ("{" -+ int parsed; -+ bool ok; -+ struct lgtd_jsonrpc_request req; -+ struct lgtd_client client = { -+ .io = NULL, .current_request = &req, -+ }; -+ -+ const char *json = ("{" - "\"jsonrpc\": \"2.0\"," - "\"method\": \"set_light_from_hsbk\"," -- "\"params\": [" -- "\"*\", 324.2341514, 0.234, 1.0, 4200, 0" -- "]," -+ "\"params\": [\"*\", 324.2341514, 0.234, 1.0, 4200, 600]," - "\"id\": \"42\"" - "}"); -- int parsed = parse_json( -- tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) -- ); -- -- bool ok; -- struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; -- struct lgtd_client client = { -- .io = NULL, .current_request = &req, .json = json -- }; -+ client.json = json; -+ parsed = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); -+ memset(&req, 0, sizeof(req)); - ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); - if (!ok) { - errx(1, "can't parse request"); - } -+ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); -+ if (set_light_call_count != 1) { -+ errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); -+ } - -+ // optional transition -+ json = ("{" -+ "\"jsonrpc\": \"2.0\"," -+ "\"method\": \"set_light_from_hsbk\"," -+ "\"params\": [\"*\", 324.2341514, 0.234, 1.0, 4200]," -+ "\"id\": \"42\"" -+ "}"); -+ client.json = json; -+ parsed = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); -+ memset(&req, 0, sizeof(req)); -+ ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); -+ if (!ok) { -+ errx(1, "can't parse request"); -+ } - lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); -- -- if (!set_light_called) { -+ if (set_light_call_count != 2) { - errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); - } - -diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c ---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c -+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c -@@ -133,5 +133,29 @@ - "}," - "\"id\": \"42\"" - "}"); -+ -+ // invalid temperature: -+ test_request("{" -+ "\"jsonrpc\": \"2.0\"," -+ "\"method\": \"set_light_from_hsbk\"," -+ "\"params\": {" -+ "\"target\": \"*\", " -+ "\"hue\": 324.2341514, " -+ "\"saturation\": 0.234, " -+ "\"brightness\": 1.0, " -+ "\"kelvin\": -4200," -+ "\"transition\": 42" -+ "}," -+ "\"id\": \"42\"" -+ "}"); -+ -+ // too many params -+ test_request("{" -+ "\"jsonrpc\": \"2.0\"," -+ "\"method\": \"set_light_from_hsbk\"," -+ "\"params\": [\"*\", 324.2341514, 0.234, 1.0, 4200, 600, \"extraarg\"]," -+ "\"id\": \"42\"" -+ "}"); -+ - return 0; - } -diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c ---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c -+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c -@@ -23,7 +23,7 @@ - return LGTD_LIFX_WAVEFORM_SAW; - } - --static bool set_waveform_called = false; -+static int set_waveform_call_count = 0; - - void - lgtd_proto_set_waveform(struct lgtd_client *client, -@@ -79,23 +79,38 @@ - if (skew_ratio != 0) { - errx(1, "Invalid skew_ratio: %d, expected: 0", skew_ratio); - } -- if (!transient) { -- errx(1, "transient is false instead of true"); -- } - if (waveform != LGTD_LIFX_WAVEFORM_SAW) { - errx( - 1, "Invalid waveform %d: expected: %d", - waveform, LGTD_LIFX_WAVEFORM_SAW - ); - } -- set_waveform_called = true; -+ switch (set_waveform_call_count++) { -+ case 0: -+ if (transient) { -+ errx(1, "Invalid transient: true (expected false)"); -+ } -+ break; -+ case 1: -+ if (!transient) { -+ errx(1, "Invalid transient: false (expected true)"); -+ } -+ break; -+ default: -+ errx(1, "set_waveform called too many times"); -+ } - } - - int - main(void) - { - jsmntok_t tokens[32]; -- const char json[] = ("{" -+ int parsed; -+ bool ok; -+ struct lgtd_jsonrpc_request req; -+ struct lgtd_client client = { .io = NULL, .current_request = &req }; -+ -+ const char *json = ("{" - "\"jsonrpc\": \"2.0\"," - "\"method\": \"set_waveform\"," - "\"params\": {" -@@ -107,28 +122,49 @@ - "\"cycles\": 10," - "\"period\": 1000," - "\"skew_ratio\": 0.5," -- "\"transient\": true," -+ "\"transient\": false," - "\"waveform\": \"SAW\"" - "}," - "\"id\": \"42\"" - "}"); -- int parsed = parse_json( -- tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) -- ); -- -- bool ok; -- struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; -- struct lgtd_client client = { -- .io = NULL, .current_request = &req, .json = json -- }; -+ client.json = json; -+ parsed = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); -+ memset(&req, 0, sizeof(req)); - ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); - if (!ok) { - errx(1, "can't parse request"); - } -+ lgtd_jsonrpc_check_and_call_set_waveform(&client); -+ if (set_waveform_call_count != 1) { -+ errx(1, "lgtd_proto_set_waveform wasn't called"); -+ } - -+ // optional transient argument -+ json = ("{" -+ "\"jsonrpc\": \"2.0\"," -+ "\"method\": \"set_waveform\"," -+ "\"params\": {" -+ "\"target\": \"*\", " -+ "\"hue\": 324.2341514, " -+ "\"saturation\": 0.234, " -+ "\"brightness\": 1.0, " -+ "\"kelvin\": 4200," -+ "\"cycles\": 10," -+ "\"period\": 1000," -+ "\"skew_ratio\": 0.5," -+ "\"waveform\": \"SAW\"" -+ "}," -+ "\"id\": \"42\"" -+ "}"); -+ client.json = json; -+ parsed = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); -+ memset(&req, 0, sizeof(req)); -+ ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); -+ if (!ok) { -+ errx(1, "can't parse request"); -+ } - lgtd_jsonrpc_check_and_call_set_waveform(&client); -- -- if (!set_waveform_called) { -+ if (set_waveform_call_count != 2) { - errx(1, "lgtd_proto_set_waveform wasn't called"); - } - -diff --git a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c ---- a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c -+++ b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c -@@ -40,7 +40,7 @@ - "\"error\": {" - "\"code\": -32602, " - "\"message\": " -- "\"Invalid number of parameters\"" -+ "\"Invalid parameters\"" - "}" - "}"); -
--- a/series Sat May 28 18:42:04 2016 -0700 +++ b/series Sat May 28 19:00:30 2016 -0700 @@ -1,10 +1,3 @@ -add_make_release.patch -fix_freebsd_build.patch -docs_setup_alabaster_and_ga.patch -optional_jsonrpc_args.patch -network_discovery.patch -dont_use_ev_assign.patch -white_colors_clarifications.patch add_power_transition.patch open_gateway_on_any_bulb_response.patch #+future make_gateway_write_callbacks_low_priority.patch #+future
--- a/white_colors_clarifications.patch Sat May 28 18:42:04 2016 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# HG changeset patch -# Parent 6b4f3d5c20f291c5fe0f908810931bd7a92ac228 -Improve the protocol docs a bit - -Go beyond just saying what the parameters are and clarify whites vs -colors in set_light_from_hsbk and set_waveform. - -Clarify the example client a bit and fix a typo. - -Closes GH-9. - -diff --git a/docs/protocol.rst b/docs/protocol.rst ---- a/docs/protocol.rst -+++ b/docs/protocol.rst -@@ -65,6 +65,11 @@ - - .. function:: set_light_from_hsbk(target, hue, saturation, brightness, kelvin[, transition]) - -+ Set the color of the given bulbs, if `hue` and `saturation` are both 0 then -+ the bulbs will be white otherwise you'll be setting an actual color. The -+ temperature parameter (`kelvin`) can be used to get a warmer or colder white -+ and will also affect colors. -+ - :param float hue: From 0 to 360. - :param float saturation: From 0 to 1. - :param float brightness: From 0 to 1. -@@ -74,6 +79,11 @@ - - .. function:: set_waveform(target, waveform, hue, saturation, brightness, kelvin, period, cycles, skew_ratio[, transient]) - -+ Repeatedly change the color of the given bulbs according to a periodic -+ (mathematical) function selected with the `waveform` parameter. The `hue`, -+ `saturation`, `brightness` and `kelvin` parameters work like in -+ :func:`set_light_from_hsbk`. -+ - :param string waveform: One of ``SAW``, ``SINE``, ``HALF_SINE``, - ``TRIANGLE``, ``SQUARE``. - :param float hue: From 0 to 360. -@@ -88,7 +98,7 @@ - its original state. This argument is optional and - defaults to true. - -- The meaning of the ``skew_ratio`` argument depends on the type of waveform: -+ The meaning of the `skew_ratio` argument depends on the selected `waveform`: - - +---------------+-----------------------------------------------------------+ - | ``SAW`` | Should be 0.5. | -@@ -111,18 +121,20 @@ - targeted bulb, the list is not in any specific order. Each dict has the - following fields: - -- - hsbk: tuple (h, s, b, k) see function:`set_light_from_hsbk`; -- - label: bulb label (utf-8 encoded string); -- - power: boolean, true when the bulb is powered on false otherwise; -- - tags: list of tags applied to the bulb (utf-8 encoded strings). -+ - hsbk: tuple (h, s, b, k) as in :func:`set_light_from_hsbk`; -+ - label: bulb label; -+ - power: boolean, true when the bulb is powered on, false otherwise; -+ - tags: list of tags applied to the bulb. - - .. function:: set_label(target, label) - -- Label the target bulb(s) with the given label. -+ Label the target bulb(s) with the given label. UTF-8 encoded values are -+ recommended. - - .. note:: - -- Use :func:`tag` instead set_label to give a common name to multiple bulbs. -+ Use :func:`tag` instead of :func:`set_label` to give a common name to -+ multiple bulbs. - - .. function:: tag(target, label) - -@@ -227,13 +239,16 @@ - while True: - # Read a chunk of data, and accumulate it in the response buffer: - response += lightsd_socket.recv(READ_SIZE) -+ # Try to load the received the data, we ignore encoding and parsing -+ # errors since we only wanna know if the received data is complete: - try: -- # Try to load the received the data, we ignore encoding errors -- # since we only wanna know if the received data is complete. - json.loads(response.decode(ENCODING, "ignore")) -- break # Decoding was successful, we have received everything. -+ # Decoding and JSON parsing were successful, we have received -+ # everything: -+ break - except Exception: -- continue # Decoding failed, data must be missing. -+ # Decoding or parsing failed, data must be missing, try again: -+ continue - - response = response.decode(ENCODING, "surrogateescape") - print(json.loads(response))