diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..d80f7934 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,45 @@ +# Always ignore node_modules. +**/node_modules/**/*.* + +# lint eslint config files which are excluded by default +!**/.eslintrc.js + +# Exclude expected objdirs. +obj*/** + +# Exclude mozilla directory, this one is checked separately +mozilla/** + +# These directories don't contain any js and are not meant to +config/** +db/** +other-licenses/** +testing/** + +# We ignore all these directories by default, until we get them enabled. +# If you are enabling a directory, please add directory specific exclusions +# below. +build/** +chat/** +editor/** +im/** +ldap/** +mail/** +mailnews/** +suite/** + +# calendar/ exclusions + +# prefs files +calendar/lightning/content/lightning.js +calendar/locales/en-US/lightning-l10n.js + +# gdata-provider uses non-standard javascript for Postbox compatibility +calendar/providers/gdata/** + +# third party library +calendar/base/modules/ical.js + +# preprocessed files +calendar/base/content/dialogs/calendar-migration-dialog.js +calendar/base/content/calendar-dnd-listener.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..9820b703 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,14 @@ +"use strict"; + +module.exports = { + // When adding items to this file please check for effects on sub-directories. + "plugins": [ + "mozilla" + ], + "rules": { + "mozilla/import-globals": 1, + }, + "env": { + "es6": true + }, +}; diff --git a/.treestate b/.treestate new file mode 100644 index 00000000..5032e3ca --- /dev/null +++ b/.treestate @@ -0,0 +1,3 @@ +[treestate] +src_update_version = 2 + diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py new file mode 100644 index 00000000..05fb579f --- /dev/null +++ b/.ycm_extra_conf.py @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import imp, os, sys + +old_bytecode = sys.dont_write_bytecode +sys.dont_write_bytecode = True + +ycm_module = imp.load_source("_ycm_extra_conf", os.path.join("mozilla", ".ycm_extra_conf.py")) + +sys.dont_write_bytecode = old_bytecode + +# Expose the FlagsForFile function from mozilla/.ycm_extra_conf.py +FlagsForFile = ycm_module.FlagsForFile diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..5f14eaac --- /dev/null +++ b/AUTHORS @@ -0,0 +1,434 @@ +This is an (incomplete) list of people who have contributed to the +codebase which lives in this repository. If you make a contribution +here, you may add your name and, optionally, email address in the +appropriate place. + +For a full list of the people who are credited with making a +contribution to Mozilla, see http://www.mozilla.org/credits/ . + +Aaron Kaluszka +Aaron Leventhal +Abdelrhman Ahmed +ActiveState Software Inc +Adam Becevello +Adam Christian +Adam D. Moss +Adam Lock +Adrian Havill +Akihiro Misaki +Akkana Peck +Alec Flett +Alexey Chernyak +Allan Masri +alta88 + +Andreas Nilsson +Andrew Sutherland +Andrew Wooldridge +Andrey Terentyev +Anirvana Mishra +Annie Sullivan +Archaeopteryx +ArentJan Banck +Arlo Breault +Arthur Wiebe +Asaf Romano +Atul Varma +ause +Axel Zechner +Baki Bon +Ben Bucksch +Benedikt Pfeifer +Ben Frisch +Ben Goodger +Benjamin Smedberg +Benoît Renard +Ben Turner +Berend Cornelius +Beth Epperson +Bhuvan Racham +Bill Law +Blair McBride +Blake Ross +Blake Winton +Bob Lord +Boris Zbarsky +Bradley Baetz +Brandon Pung +Brant Gurganus +Brett Wilson +Brian Bober +Brian King +Brian Lu +Brian Nesse +Brian R. Bondy +Brian Ryner +Brodie Thiesfield +Bruno Browning +Bruno Escherl +Bryan Clark +Caio Tiago Oliveira +Cédric Corazza +Charles Manske +Chase Phillips +Chinmay Deepakbhai Patel +Chip Clark +Chris Allen +Chris Beard +Chris Charabaruk +Chris McAfee +Chris Nelson +Christian Biesinger +Christian Eyrich +Christian Jansen +Christian Schmidt +Christopher A. Aillon +Christopher Blizzard +Christopher Cook +Christopher Hoess +Christopher Seawood +Christopher Thomas +Chris Waterson + +Chuck Boatwright +Clint Talbert +Colin Phillips +Csaba Borbola +Curtis Bartley +Curtis Jewell +Cyrille Moureaux +Dan Cannon +Dan Haddix +Daniel Aquino +Daniel Boelzle +Daniel Brooks +Daniel Glazman +Daniel Veditz +Daniel Witte +Dan Matejka +Dan Mills +Dan Mosedale +Dan Parent +Dan Veditz +Dão Gottwald +Darin Fisher +Dave Townsend +David Ascher +David Baron +David Bienvenu +David Drinan +David Humphrey +David Hyatt +David P. Drinan +David Turley +Dean Tessman + +Diego Biurrun +Diego Mira David +Dietrich Ayala +Dimas Perez Gago +Don Bragg +Don Crandall +Donna Oberes +Doron Rosenberg +Douglas Thrift +Drew Willcoxon +Edmund Wong +Eduardo Teruo Katayama +Edward Lee +Ehsan Akhgari +Emre Birol +Eric Ballet Baz +Eric Belhaire +Eric Hodel +Erik Fabert +Ernst Herbst +Evan Stratford +Fabian Guisset +Fidesfit +Florian Quèze +Francisco Jose Mulero +Frank Schönheit +Frank Tang ftang@netscape.com +Frank Wein +Fred Jendrzejewski +Fredrik Holmqvist +Friedrich Beckmann +Fritz Schneider +Garth Smedley +Gary Kwong +Gary van der Merwe +Gavin Sharp +Gayatri Bhimaraju +Gekacheka +Geoffrey C. Wenger +Gervase Markham +Gianfranco Balza +Gijs Kruitbosch +Giorgio Maone +Glaucus Augustus Grecco Cardoso +Google Inc +Hans-Andreas Engel +Heather Arthur +Heikki Toivonen +Henrik Gemal +Henrik Skupin +Henri Sivonen +Henry Jia +Henry Sobotka +Håkan Waara +Howard Chu +Hubert Gajewski +Ian McGreer +Ian Neal +IBM Corporation +Irakli Gozalishvili +James Green +Jamie Zawinski +Jan Horak +Jan Varga + +Jason Barnabe +Jason Eager +Jason Oster +Javier Delgadillo +Jean-François Ducarroz +Jeff Beckley +Jeff Hammel +Jeff Tsai +Jeff Walden +Jens Bannmann +Jens Hatlak +Jeremy Laine +Jeremy M. Dolan +Jeremy Morton +Jesse Ruderman +Jie Zhang +Jim Mathies +Jim Porter +J.M Betak jbetak@netscape.com +Joachim Herb +Joe Hewitt +Joe Hughes +Joey Minta +Johnathan Nightingale +John Gaunt +John G Myers +John Morkel +John Ratke +John Resig +Jonas Joergensen +Jonas Sicking +Jonathan Kamens +Jonathan Protzenko +Jonathan Wilson +Jon Baumgartner +Jono X +Jorge Villalobos +Josh Aas +Josh Geenen +Josh Soref +Joshua Cranmer +J. Paul Reed +Jungshik Shin +Jussi Kukkonen +Justin Dolske +Justin Lebar +Justin Wood +Kai Engert +Karl Guertin +Karsten Düsterloh +Kathleen Brade +Kefu (Fisher) Zhao +Kent James +Kevin Gerich +Kevin Puetz +Kin Blas +Kipp Hickman +Kohei Yoshino +Krishna Mohan Khandrika +Kuden +Kyle Huey +Kyle Yuan +Lars Wohlfahrt +Leif Hedstrom +Lennart Bublies + +Lorenzo Colitti +Ludovic Hirlimann +M.-A. Darche +Magnus Melin +Makoto Kato +Manuel Reimer +Marco Bonardo +Marc Zahnlecker +Margaret Leibovic +Mark Banner +Mark Finkle +Mark Mentovai +Mark "Mook" Yen +Mark Olson +Mark Smith +Mark Swaffer +Markus Adrario +Markus Hossner +Markus Stange +Martin Schroeder +Masayuki Nakano +Matt Dudziak +Matthew Mecca +Matthew Willis +Mauro Cicognini +Mehdi Mulani +Merike Sell +Michael Braun +Michael Büttner +Michael Foord +Michael Hein +Michael Johnston +Michael Kohler +Michael Kraft +Michael Lowe +Michael Ventnor +Michiel van Leeuwen +Mikeal Rogers +Mike Conley +Mike Connor +Mike Kaganski +Mike Kowalski +Mike Norton +Mike Pinkerton +Mike Potter +Mike Shaver +Misak Khachatryan +Mitesh Shah +Mohan Bhamidipati +Mostafa Hosseini +Mozilla Foundation +Mozilla Japan +Myk Melez +Navin Gupta +Neil Deakin +Neil Rashbrook +Netscape Communications Corporation +New Dimensions Consulting, Inc +Nick Kreeger +Niels Provos +Nils Maier +OEone Corporation +Olivier Parniere +Olli Pettay +Oracle Corporation +Oren Nachmore +Pamela Greene +Patrick C. Beard +Patrick Cloke +Patrick Thompson +Paul Hangas +Paul O'Shannessy +Paul Sandoz +Paul Tomlin +Pete Collins +Peter Annema +Peter Parente +Peter Van der Beken +Peter Weilbacher +Philip Chee +Philipp Kewisch +Philipp von Weitershausen +Philip Zhao +Phil Lacy +Phil Ringnalda +Pierre Chanial +Pierre Phaneuf +Prasad Sunkari +Princess Marshmallow +QUALCOMM Incorporated +Quentin Castier + +Rajiv Dayal +Ramalingam Saravanan +Ramiro Estrugo +Raymond Lee +Ray Whitmer + +Richard Marti +R.J. Keller +Rob Arnold +Robert Ginda +Robert John Churchill +Robert Kaiser +Robert Sayre +Robert Strong +Robin Edrenius +Robin Foster-Clark +Rod Spears +Romain Bezut +Roman Kaeppeler +Ryan Cassin +Ryan Flint +Ryan Jones +Samir Gehani +Sammy Ford +Scott MacGregor +Scott Putterman +Sean Su +Sebastian Schwieger +Serge Gautherie +Seth Spitzer +Shane Caraveo +Shawn Wilsher + +Siddharth Agarwal +Simdesk Technologies Inc +Simeon Morrison +Simon Bünzli +Simon Fraser +Simon Paquet +Simon Vaillancourt +Simon Wilkinson +slucy@objectivesw.co.uk +smorrison@gte.com +Spencer Murray +Srilatha Moturi +Stefan Borggraefe +Stefan Fleiter +Stefan Hermes +Stefan Sitter +Steffen Wilberg +Stephane Saux +Stephen Clavering +Stephen Donner +Stephen Horlander +Stephen Lamm +Stephen Walker +Steve Dorner +Steve Hampton +Steven Garrity +Stuart Ballard +Stuart Parmenter +Sungjoon Steve Won +Sun Microsystems, Inc +Takayuki Tei + +Terry Hayes +Thomas Benisch +Thomas Düllmann +Thomas Schmid +timeless +Tim Taylor +Tobias Koenig +Tom Germeau +Tuukka Tolvanen +Varada Parthasarathi +Varuna Jayasiri +Vladimir Vukicevic +Warren Harris +Wei Xian Woo +Will Guaraldi +William A. Law +William Bonnet +William R. Price +Wolfgang Rosenauer +Wolfgang Sourdeau +Žiga Sancin diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 00000000..2a4ee81f --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,27 @@ +dnl +dnl Local autoconf macros used with mozilla +dnl The contents of this file are under the Public Domain. +dnl + +builtin(include, mozilla/build/autoconf/toolchain.m4)dnl +builtin(include, mozilla/build/autoconf/config.status.m4)dnl +builtin(include, mozilla/build/autoconf/nspr.m4)dnl +builtin(include, mozilla/build/autoconf/nss.m4)dnl +builtin(include, mozilla/build/autoconf/pkg.m4)dnl +builtin(include, mozilla/build/autoconf/codeset.m4)dnl +builtin(include, mozilla/build/autoconf/altoptions.m4)dnl +builtin(include, mozilla/build/autoconf/mozprog.m4)dnl +builtin(include, mozilla/build/autoconf/acwinpaths.m4)dnl +builtin(include, mozilla/build/autoconf/lto.m4)dnl +builtin(include, mozilla/build/autoconf/frameptr.m4)dnl +builtin(include, mozilla/build/autoconf/compiler-opts.m4)dnl +builtin(include, mozilla/build/autoconf/zlib.m4)dnl +builtin(include, mozilla/build/autoconf/expandlibs.m4)dnl + +MOZ_PROG_CHECKMSYS() + +# Read the user's .mozconfig script. We can't do this in +# configure.in: autoconf puts the argument parsing code above anything +# expanded from configure.in, and we need to get the configure options +# from .mozconfig in place before that argument parsing code. +dnl MOZ_READ_MOZCONFIG(mozilla) diff --git a/build/autoconf/check-sync-exceptions b/build/autoconf/check-sync-exceptions new file mode 100644 index 00000000..005bb6b5 --- /dev/null +++ b/build/autoconf/check-sync-exceptions @@ -0,0 +1,2 @@ +mozconfig-find +mozconfig2client-mk diff --git a/build/autoconf/config.guess b/build/autoconf/config.guess new file mode 100644 index 00000000..d5d667d4 --- /dev/null +++ b/build/autoconf/config.guess @@ -0,0 +1,1454 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2016 Free Software Foundation, Inc. + +timestamp='2016-03-24' + +# This file 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 program 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 . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess +# +# Please send patches to . + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2016 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_SYSTEM}" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval $set_cc_for_build + cat <<-EOF > $dummy.c + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + /sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || \ + echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo ${UNAME_MACHINE_ARCH} | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo ${UNAME_MACHINE_ARCH} | sed -ne 's,^.*\(eb\)$,\1,p'` + machine=${arch}${endian}-unknown + ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|earm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case "${UNAME_MACHINE_ARCH}" in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo ${UNAME_MACHINE_ARCH} | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE} | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}${abi}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-libertybsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + *:SolidBSD:*:*) + echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:Sortix:*:*) + echo ${UNAME_MACHINE}-unknown-sortix + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE=alpha ;; + "EV4.5 (21064)") + UNAME_MACHINE=alpha ;; + "LCA4 (21066/21068)") + UNAME_MACHINE=alpha ;; + "EV5 (21164)") + UNAME_MACHINE=alphaev5 ;; + "EV5.6 (21164A)") + UNAME_MACHINE=alphaev56 ;; + "EV5.6 (21164PC)") + UNAME_MACHINE=alphapca56 ;; + "EV5.7 (21164PC)") + UNAME_MACHINE=alphapca57 ;; + "EV6 (21264)") + UNAME_MACHINE=alphaev6 ;; + "EV6.7 (21264A)") + UNAME_MACHINE=alphaev67 ;; + "EV6.8CB (21264C)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8AL (21264B)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8CX (21264D)") + UNAME_MACHINE=alphaev68 ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE=alphaev69 ;; + "EV7 (21364)") + UNAME_MACHINE=alphaev7 ;; + "EV7.9 (21364A)") + UNAME_MACHINE=alphaev79 ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux${UNAME_RELEASE} + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval $set_cc_for_build + SUN_ARCH=i386 + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = x && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/lslpp ] ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 + 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH=hppa2.0n ;; + 64) HP_ARCH=hppa2.0w ;; + '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = hppa2.0w ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH=hppa2.0w + else + HP_ARCH=hppa64 + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case ${UNAME_PROCESSOR} in + amd64) + echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + *) + echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + esac + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + *:MINGW64*:*) + echo ${UNAME_MACHINE}-pc-mingw64 + exit ;; + *:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + *:MSYS*:*) + echo ${UNAME_MACHINE}-pc-msys + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + *:Interix*:*) + case ${UNAME_MACHINE} in + x86) + echo i586-pc-interix${UNAME_RELEASE} + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix${UNAME_RELEASE} + exit ;; + IA64) + echo ia64-unknown-interix${UNAME_RELEASE} + exit ;; + esac ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + 8664:Windows_NT:*) + echo x86_64-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + aarch64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC=gnulibc1 ; fi + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arm*:Linux:*:*) + eval $set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi + else + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + cris:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + crisv32:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + e2k:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + frv:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + hexagon:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:Linux:*:*) + echo ${UNAME_MACHINE}-pc-linux-${LIBC} + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + k1om:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } + ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-${LIBC} + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-${LIBC} + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; + PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; + *) echo hppa-unknown-linux-${LIBC} ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-${LIBC} + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-${LIBC} + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-${LIBC} + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-${LIBC} + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux-${LIBC} + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + tile*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + vax:Linux:*:*) + echo ${UNAME_MACHINE}-dec-linux-${LIBC} + exit ;; + x86_64:Linux:*:*) + echo ${UNAME_MACHINE}-pc-linux-${LIBC} + exit ;; + xtensa*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configure will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux${UNAME_RELEASE} + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux${UNAME_RELEASE} + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux${UNAME_RELEASE} + exit ;; + SX-ACE:SUPER-UX:*:*) + echo sxace-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval $set_cc_for_build + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-?:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk${UNAME_RELEASE} + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = 386; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; + i*86:rdos:*:*) + echo ${UNAME_MACHINE}-pc-rdos + exit ;; + i*86:AROS:*:*) + echo ${UNAME_MACHINE}-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo ${UNAME_MACHINE}-unknown-esx + exit ;; + amd64:Isilon\ OneFS:*:*) + echo x86_64-unknown-onefs + exit ;; +esac + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/build/autoconf/mozconfig-find b/build/autoconf/mozconfig-find new file mode 100644 index 00000000..97dd90c3 --- /dev/null +++ b/build/autoconf/mozconfig-find @@ -0,0 +1,76 @@ +#! /bin/sh +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# mozconfigfind - Loads options from .mozconfig onto configure's +# command-line. The .mozconfig file is searched for in the +# order: +# If $MOZCONFIG is set, use that. +# If one of $TOPSRCDIR/.mozconfig or $TOPSRCDIR/mozconfig exists, use it. +# If both exist, or if various legacy locations contain a mozconfig, error. +# Otherwise, use the default build options. +# +topsrcdir=$1 + +abspath() { + if uname -s | grep -q MINGW; then + # We have no way to figure out whether we're in gmake or pymake right + # now. gmake gives us Unix-style paths while pymake gives us Windows-style + # paths, so attempt to handle both. + regexes='^\([A-Za-z]:\|\\\\\|\/\) ^\/' + else + regexes='^\/' + fi + + for regex in $regexes; do + if echo $1 | grep -q $regex; then + echo $1 + return + fi + done + + # If we're at this point, we have a relative path + echo `pwd`/$1 +} + +if [ -n "$MOZCONFIG" ] && ! [ -f "$MOZCONFIG" ]; then + echo "Specified MOZCONFIG \"$MOZCONFIG\" does not exist!" 1>&2 + exit 1 +fi + +if [ -n "$MOZ_MYCONFIG" ]; then + echo "Your environment currently has the MOZ_MYCONFIG variable set to \"$MOZ_MYCONFIG\". MOZ_MYCONFIG is no longer supported. Please use MOZCONFIG instead." 1>&2 + exit 1 +fi + +if [ -z "$MOZCONFIG" ] && [ -f "$topsrcdir/.mozconfig" ] && [ -f "$topsrcdir/mozconfig" ]; then + echo "Both \$topsrcdir/.mozconfig and \$topsrcdir/mozconfig are supported, but you must choose only one. Please remove the other." 1>&2 + exit 1 +fi + +for _config in "$MOZCONFIG" \ + "$topsrcdir/.mozconfig" \ + "$topsrcdir/mozconfig" +do + if test -f "$_config"; then + abspath $_config + exit 0 + fi +done + +# We used to support a number of other implicit .mozconfig locations. We now +# detect if we were about to use any of these locations and issue an error if we +# find any. +for _config in "$topsrcdir/mozconfig.sh" \ + "$topsrcdir/myconfig.sh" \ + "$HOME/.mozconfig" \ + "$HOME/.mozconfig.sh" \ + "$HOME/.mozmyconfig.sh" +do + if test -f "$_config"; then + echo "You currently have a mozconfig at \"$_config\". This implicit location is no longer supported. Please move it to $topsrcdir/.mozconfig or specify it explicitly via \$MOZCONFIG." 1>&2 + exit 1 + fi +done diff --git a/build/autoconf/mozconfig2client-mk b/build/autoconf/mozconfig2client-mk new file mode 100644 index 00000000..aaf8de18 --- /dev/null +++ b/build/autoconf/mozconfig2client-mk @@ -0,0 +1,76 @@ +#! /bin/sh +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# mozconfig2client-mk - Translates .mozconfig into options for client.mk. +# Prints defines to stdout. +# +# See mozconfig2configure for more details + +print_header() { + cat <@' with '$()'. + _opt=`echo "$_opt" | sed -e 's/\([\"\\]\)/\\\\\1/g; s/@\([^@]*\)@/\$(\1)/g;'` + echo $_opt; + done +} + +# Main +#-------------------------------------------------- + +scriptdir=`dirname $0` +topsrcdir=$1 + +# If the path changes, configure should be rerun +echo "# PATH=$PATH" + +# If FOUND_MOZCONFIG isn't set, look for it and make sure the script doesn't error out +isfoundset=${FOUND_MOZCONFIG+yes} +if [ -z $isfoundset ]; then + FOUND_MOZCONFIG=`$scriptdir/mozconfig-find $topsrcdir` + if [ $? -ne 0 ]; then + echo '$(error Fix above errors before continuing.)' + else + isfoundset=yes + fi +fi + +if [ -n $isfoundset ]; then + if [ "$FOUND_MOZCONFIG" ] + then + print_header + . "$FOUND_MOZCONFIG" + echo "FOUND_MOZCONFIG := $FOUND_MOZCONFIG" + fi +fi diff --git a/build/check-sync-exceptions b/build/check-sync-exceptions new file mode 100644 index 00000000..de0da9f6 --- /dev/null +++ b/build/check-sync-exceptions @@ -0,0 +1,17 @@ +dumbmake-dependencies +mach_bootstrap.py +merge-installrdf.py +pymake +client.py-args +client.py-l10n-args +configobj.py + +# Ignore detritus left lying around by editing tools. +*~ +.#* +#*# +*.orig +*.rej + +# Ignore "compiled" python files +*.pyc diff --git a/build/client.py-args b/build/client.py-args new file mode 100644 index 00000000..4f38e4ea --- /dev/null +++ b/build/client.py-args @@ -0,0 +1 @@ +--hg-options='--time' --hgtool=../tools/buildfarm/utils/hgtool.py --hgtool1=../scripts/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --tinderbox-print --mozilla-repo=https://hg.mozilla.org/releases/mozilla-esr52 --mozilla-rev=THUNDERBIRD_52_VERBRANCH diff --git a/build/client.py-l10n-args b/build/client.py-l10n-args new file mode 100644 index 00000000..4f38e4ea --- /dev/null +++ b/build/client.py-l10n-args @@ -0,0 +1 @@ +--hg-options='--time' --hgtool=../tools/buildfarm/utils/hgtool.py --hgtool1=../scripts/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --tinderbox-print --mozilla-repo=https://hg.mozilla.org/releases/mozilla-esr52 --mozilla-rev=THUNDERBIRD_52_VERBRANCH diff --git a/build/dumbmake-dependencies b/build/dumbmake-dependencies new file mode 100644 index 00000000..7704a681 --- /dev/null +++ b/build/dumbmake-dependencies @@ -0,0 +1,6 @@ +im/app + chat + im + mozilla/extensions/purple +chat + im diff --git a/build/mach_bootstrap.py b/build/mach_bootstrap.py new file mode 100644 index 00000000..ed79f3ab --- /dev/null +++ b/build/mach_bootstrap.py @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import unicode_literals + +import os, sys + +def bootstrap(topsrcdir, mozilla_dir=None): + if mozilla_dir is None: + mozilla_dir = os.path.join(topsrcdir, 'mozilla') + sys.path[0:0] = [mozilla_dir] + import build.mach_bootstrap + return build.mach_bootstrap.bootstrap(topsrcdir, mozilla_dir) diff --git a/build/macosx/cross-mozconfig.common b/build/macosx/cross-mozconfig.common new file mode 100644 index 00000000..8e56394d --- /dev/null +++ b/build/macosx/cross-mozconfig.common @@ -0,0 +1,47 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOZ_AUTOMATION_L10N_CHECK=0 + +if [ "x$IS_NIGHTLY" = "xyes" ]; then + # Some nightlies (eg: Mulet) don't want these set. + MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1} + MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1} +fi +. "$topsrcdir/build/mozconfig.common" + +# ld needs libLTO.so from llvm +mk_add_options "export LD_LIBRARY_PATH=$topsrcdir/clang/lib" + +CROSS_CCTOOLS_PATH=$topsrcdir/cctools +CROSS_SYSROOT=$topsrcdir/MacOSX10.7.sdk +CROSS_PRIVATE_FRAMEWORKS=$CROSS_SYSROOT/System/Library/PrivateFrameworks +FLAGS="-target x86_64-apple-darwin10 -mlinker-version=136 -B $CROSS_CCTOOLS_PATH/bin -isysroot $CROSS_SYSROOT" + +export CC="$topsrcdir/clang/bin/clang $FLAGS" +export CXX="$topsrcdir/clang/bin/clang++ $FLAGS" +export CPP="$topsrcdir/clang/bin/clang $FLAGS -E" +export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config +export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip" +export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin10- +export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil +export GENISOIMAGE=$topsrcdir/genisoimage/genisoimage +export DMG_TOOL=$topsrcdir/dmg/dmg + +export HOST_CC="$topsrcdir/clang/bin/clang" +export HOST_CXX="$topsrcdir/clang/bin/clang++" +export HOST_CPP="$topsrcdir/clang/bin/clang -E" +export HOST_CFLAGS="-g" +export HOST_CXXFLAGS="-g" +export HOST_LDFLAGS="-g" + +ac_add_options --target=x86_64-apple-darwin +ac_add_options --with-macos-private-frameworks=$CROSS_PRIVATE_FRAMEWORKS + +# Enable static analysis checks by default on OSX cross builds. +ac_add_options --enable-clang-plugin + +. "$topsrcdir/build/mozconfig.cache" + +export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token diff --git a/build/macosx/local-mozconfig.common b/build/macosx/local-mozconfig.common new file mode 100644 index 00000000..02a09d2f --- /dev/null +++ b/build/macosx/local-mozconfig.common @@ -0,0 +1,46 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if [ "x$IS_NIGHTLY" = "xyes" ]; then + # Some nightlies (eg: Mulet) don't want these set. + MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1} + MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1} + MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1} +fi +. "$topsrcdir/build/mozconfig.common" + +if [ -d "$topsrcdir/clang" ]; then + # mozilla-central based build + export CC=$topsrcdir/clang/bin/clang + export CXX=$topsrcdir/clang/bin/clang++ + export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config + export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil + # Use an updated linker. + ldflags="-B$topsrcdir/cctools/bin" +elif [ -d "$topsrcdir/../clang" ]; then + # comm-central based build + export CC=$topsrcdir/../clang/bin/clang + export CXX=$topsrcdir/../clang/bin/clang++ + export LLVMCONFIG=$topsrcdir/../clang/bin/llvm-config + export DSYMUTIL=$topsrcdir/../clang/bin/llvm-dsymutil + # Use an updated linker. + ldflags="-B$topsrcdir/../cctools/bin" +fi + +# Ensure the updated linker doesn't generate things our older build tools +# don't understand. +ldflags="$ldflags -Wl,-no_data_in_code_info" +export LDFLAGS="$ldflags" + +# If not set use the system default clang +if [ -z "$CC" ]; then + export CC=clang +fi + +# If not set use the system default clang++ +if [ -z "$CXX" ]; then + export CXX=clang++ +fi + +export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token diff --git a/build/macosx/mozconfig.common b/build/macosx/mozconfig.common new file mode 100644 index 00000000..27634b7f --- /dev/null +++ b/build/macosx/mozconfig.common @@ -0,0 +1,5 @@ +if test `uname -s` = Linux; then + . $topsrcdir/build/macosx/cross-mozconfig.common +else + . $topsrcdir/build/macosx/local-mozconfig.common +fi diff --git a/build/macosx/universal/check-sync-exceptions b/build/macosx/universal/check-sync-exceptions new file mode 100644 index 00000000..fc514928 --- /dev/null +++ b/build/macosx/universal/check-sync-exceptions @@ -0,0 +1,9 @@ +flight.mk +mozconfig.common + +# Ignore detritus left lying around by editing tools. +*~ +.#* +#*# +*.orig +*.rej diff --git a/build/macosx/universal/flight.mk b/build/macosx/universal/flight.mk new file mode 100644 index 00000000..2b79ef7a --- /dev/null +++ b/build/macosx/universal/flight.mk @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# BE CAREFUL! This makefile handles a postflight_all rule for a +# multi-project build, so DON'T rely on anything that might differ between +# the two OBJDIRs. + +ifndef OBJDIR +OBJDIR_ARCH_1 = $(MOZ_OBJDIR)/$(firstword $(MOZ_BUILD_PROJECTS)) +OBJDIR_ARCH_2 = $(MOZ_OBJDIR)/$(word 2,$(MOZ_BUILD_PROJECTS)) +DIST_ARCH_1 = $(OBJDIR_ARCH_1)/dist +DIST_ARCH_2 = $(OBJDIR_ARCH_2)/dist +DIST_UNI = $(DIST_ARCH_1)/universal +OBJDIR = $(OBJDIR_ARCH_1) +endif + +topsrcdir = $(TOPSRCDIR) +DEPTH = $(OBJDIR) +include $(OBJDIR)/config/autoconf.mk + +core_abspath = $(if $(filter /%,$(1)),$(1),$(CURDIR)/$(1)) + +DIST = $(OBJDIR)/dist + +postflight_all: + mkdir -p $(DIST_UNI)/$(MOZ_PKG_APPNAME) + rm -f $(DIST_ARCH_2)/universal + ln -s $(abspath $(DIST_UNI)) $(DIST_ARCH_2)/universal +# Stage a package for buildsymbols to be happy. Doing so in OBJDIR_ARCH_1 +# actually does a universal staging with both OBJDIR_ARCH_1 and OBJDIR_ARCH_2. + $(MAKE) -C $(OBJDIR_ARCH_1)/$(subst ../,,$(MOZ_BUILD_APP))/installer \ + PKG_SKIP_STRIP=1 stage-package diff --git a/build/macosx/universal/mozconfig b/build/macosx/universal/mozconfig new file mode 100644 index 00000000..32ab66f2 --- /dev/null +++ b/build/macosx/universal/mozconfig @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# i386/x86-64 Universal Build mozconfig + +# As used here, arguments in $MOZ_BUILD_PROJECTS are suitable as arguments +# to gcc's -arch parameter. +mk_add_options MOZ_BUILD_PROJECTS="x86_64 i386" + +. $topsrcdir/build/macosx/universal/mozconfig.common diff --git a/build/macosx/universal/mozconfig.common b/build/macosx/universal/mozconfig.common new file mode 100644 index 00000000..518274b5 --- /dev/null +++ b/build/macosx/universal/mozconfig.common @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +mk_add_options MOZ_UNIFY_BDATE=1 + +mk_add_options MOZ_POSTFLIGHT_ALL+=build/macosx/universal/flight.mk + +DARWIN_VERSION=`uname -r` +ac_add_app_options i386 --target=i386-apple-darwin$DARWIN_VERSION +ac_add_app_options x86_64 --target=x86_64-apple-darwin$DARWIN_VERSION +ac_add_app_options i386 --with-unify-dist=../x86_64/dist +ac_add_app_options x86_64 --with-unify-dist=../i386/dist + +ac_add_options --with-macos-sdk=/Developer/SDKs/MacOSX10.7.sdk + +. $topsrcdir/build/macosx/mozconfig.common + +# $MOZ_BUILD_APP is only defined when sourced by configure. That's not a +# problem, because the variables it affects only need to be set for +# configure. +if test -n "$MOZ_BUILD_APP" ; then +if test "$MOZ_BUILD_APP" = "i386" -o "$MOZ_BUILD_APP" = "x86_64"; then + TARGET_CPU=$MOZ_BUILD_APP + + # $HOST_CXX is presently unused. $HOST_CC will only be used during a cross + # compile. + HOST_CC=$CC + HOST_CXX=$CXX + + NATIVE_CPU=`$topsrcdir/build/autoconf/config.guess | cut -f1 -d-` + + # It's not strictly necessary to specify -arch during native builds, but it + # makes the merged about:buildconfig easier to follow, and it reduces + # conditionalized differences between builds. + CC="$CC -arch $TARGET_CPU" + CXX="$CXX -arch $TARGET_CPU" + + # These must be set for cross builds, and don't hurt straight builds. + RANLIB=ranlib + AR=ar + AS=$CC + LD=ld + STRIP="strip" + OTOOL="otool" + + # Each per-CPU build should be entirely oblivious to the fact that a + # universal binary will be produced. The exception is packager.mk, which + # needs to know to look for universal bits when building the .dmg. + UNIVERSAL_BINARY=1 + + export CC CXX HOST_CC HOST_CXX RANLIB AR AS LD STRIP OTOOL +fi +fi diff --git a/build/merge-installrdf.py b/build/merge-installrdf.py new file mode 100644 index 00000000..7a211c34 --- /dev/null +++ b/build/merge-installrdf.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Get the target platform from a set of install.rdf files, and +# return the first install.rdf with the platform replaced by the list of +# platforms as parsed from all the files +# Allows to create a install.rdf for multiple platforms + +import sys +from xml.dom.minidom import parse + +elems = [] +for arg in sys.argv[2:]: + doc = parse(arg + "/install.rdf") + elem = doc.getElementsByTagName("em:targetPlatform")[0] + elems.append(elem.cloneNode(True)) + +doc = parse(sys.argv[1] + "/install.rdf") +elem = doc.getElementsByTagName("em:targetPlatform")[0] +for newelem in elems: + elem.parentNode.insertBefore(newelem, elem) +print doc.toxml() diff --git a/build/mozconfig.automation b/build/mozconfig.automation new file mode 100644 index 00000000..057a4a0b --- /dev/null +++ b/build/mozconfig.automation @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Common mozconfig for automation builds. +# +# We export MOZ_AUTOMATION_* variables here to trigger various steps in +# automation builds. For example, if MOZ_AUTOMATION_PACKAGE is set, then the +# package step will run. This file contains the default settings, which can be +# overridden by setting them earlier in the appropriate mozconfig. + +mk_add_options "export MOZ_AUTOMATION_BUILD_SYMBOLS=${MOZ_AUTOMATION_BUILD_SYMBOLS-1}" +mk_add_options "export MOZ_AUTOMATION_L10N_CHECK=${MOZ_AUTOMATION_L10N_CHECK-1}" +mk_add_options "export MOZ_AUTOMATION_PACKAGE=${MOZ_AUTOMATION_PACKAGE-1}" +mk_add_options "export MOZ_AUTOMATION_PACKAGE_TESTS=${MOZ_AUTOMATION_PACKAGE_TESTS-1}" +mk_add_options "export MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}" +mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}" +mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}" +mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}" +mk_add_options "export MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-0}" + +# If we are also building with MOZ_PKG_PRETTYNAMES, set the corresponding +# stages. +if test "$MOZ_AUTOMATION_PRETTY" = "1"; then + mk_add_options "export MOZ_AUTOMATION_PRETTY_PACKAGE=${MOZ_AUTOMATION_PACKAGE-1}" + mk_add_options "export MOZ_AUTOMATION_PRETTY_PACKAGE_TESTS=${MOZ_AUTOMATION_PACKAGE_TESTS-1}" + mk_add_options "export MOZ_AUTOMATION_PRETTY_L10N_CHECK=${MOZ_AUTOMATION_L10N_CHECK-1}" + mk_add_options "export MOZ_AUTOMATION_PRETTY_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}" + + # Note that we always build the update packaging with pretty names even if + # we don't build it without, so this is set to 1. + mk_add_options "export MOZ_AUTOMATION_PRETTY_UPDATE_PACKAGING=1" +fi diff --git a/build/mozconfig.cache b/build/mozconfig.cache new file mode 100644 index 00000000..be740e29 --- /dev/null +++ b/build/mozconfig.cache @@ -0,0 +1,135 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Setup for build cache + +# Avoid duplication if the file happens to be included twice. +if test -z "$bucket" -a -z "$NO_CACHE"; then + +# buildbot (or builders that use buildprops.json): +if [ -f $topsrcdir/../buildprops.json ]; then +read branch platform master < /dev/null) +EOF + +bucket= +if test -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PGO_IS_SET" -a -z "$MOZ_PGO"; then + case "${branch}" in + try) + case "${master}" in + *scl1.mozilla.com*|*.scl3.mozilla.com*) + bucket=mozilla-releng-s3-cache-us-west-1-try + ;; + *use1.mozilla.com*) + bucket=mozilla-releng-s3-cache-us-east-1-try + ;; + *usw2.mozilla.com*) + bucket=mozilla-releng-s3-cache-us-west-2-try + ;; + esac + ;; + autoland|mozilla-inbound) + case "${master}" in + *use1.mozilla.com*) + bucket=mozilla-releng-s3-cache-us-east-1-prod + ;; + *usw2.mozilla.com*) + bucket=mozilla-releng-s3-cache-us-west-2-prod + ;; + esac + ;; + esac +fi + +# builds without buildprops (eg: taskcluster or non-buildbot) and without ccache env config and without sccache disabled: +elif test -z "$CCACHE_DIR" -a -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PGO_IS_SET" -a -z "$MOZ_PGO"; then + + # prevent rerun if az is set, or wget is not available + if test -z "$availability_zone" -a -x "$(command -v wget)"; then + # timeout after 1 second, and don't retry (failure indicates instance is not in ec2 or network issue) + # availability_zone is of the form where region is e.g. us-west-2, and az is us-west-2a + availability_zone=$(wget -T 1 -t 1 -q -O - http://169.254.169.254/latest/meta-data/placement/availability-zone || true) + if test -z "$availability_zone" -o "$availability_zone" = "not-ec2"; then + availability_zone=not-ec2 + else + # region is az with last letter trimmed + region=${availability_zone%?} + # set S3 bucket according to tree (level) + case "${GECKO_HEAD_REPOSITORY}" in + *hg.mozilla.org/try*) + bucket=taskcluster-level-1-sccache-${region} + ;; + *hg.mozilla.org/integration/autoland*|*hg.mozilla.org/integration/mozilla-inbound*) + bucket=taskcluster-level-3-sccache-${region} + ;; + esac + + # set a dummy master + case "${region}" in + eu-central-1) + master=dummy.euc1.mozilla.com + ;; + us-east-1) + master=dummy.use1.mozilla.com + ;; + us-west-1) + master=dummy.usw1.mozilla.com + ;; + us-west-2) + master=dummy.usw2.mozilla.com + ;; + esac + fi + fi +fi + +# if platform hasn't been determined from buildprops, and we're on windows, +# it must be set to prevent adding ac_add_options --with-ccache below +if test -z "$platform"; then + # set platform based on the SYSTEMROOT env var + case "${SYSTEMROOT}" in + *Windows) + platform=windows + ;; + esac +fi + +if test -z "$bucket"; then + case "$platform" in + win*) : ;; + *) + ac_add_options --with-ccache + esac +else + if ! test -e $topsrcdir/sccache/sccache.py; then + echo "Sccache missing in the tooltool manifest" >&2 + exit 1 + fi + mk_add_options "export SCCACHE_BUCKET=$bucket" + case "$master" in + *us[ew][12].mozilla.com*|*euc1.mozilla.com*) + mk_add_options "export SCCACHE_NAMESERVER=169.254.169.253" + ;; + esac + ac_add_options "--with-compiler-wrapper=python2.7 $topsrcdir/sccache/sccache.py" + mk_add_options MOZ_PREFLIGHT_ALL+=build/sccache.mk + mk_add_options MOZ_POSTFLIGHT_ALL+=build/sccache.mk + mk_add_options "UPLOAD_EXTRA_FILES+=sccache.log.gz" + case "$platform" in + win*) + # sccache supports a special flag to create depfiles. + export _DEPEND_CFLAGS='-deps$(MDDEPDIR)/$(@F).pp' + # Windows builds have a default wrapper that needs to be overridden + mk_add_options "export CC_WRAPPER=" + mk_add_options "export CXX_WRAPPER=" + # For now, sccache doesn't support separate PDBs so force debug info to be + # in object files. + mk_add_options "export COMPILE_PDB_FLAG=" + mk_add_options "export HOST_PDB_FLAG=" + mk_add_options "export MOZ_DEBUG_FLAGS=-Z7" + ;; + esac +fi + +fi diff --git a/build/mozconfig.common b/build/mozconfig.common new file mode 100644 index 00000000..3d2d0b28 --- /dev/null +++ b/build/mozconfig.common @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Common mozconfig for official builds. +# +# Add options to this file that will be inherited by all in-tree mozconfigs. +# This is useful for eg try builds with nondefault options that apply to all +# architectures, though note that if you want to override options set in +# another mozconfig file, you'll need to use mozconfig.common.override instead +# of this file. + +mk_add_options AUTOCLOBBER=1 + +ac_add_options --enable-crashreporter + +ac_add_options --enable-release + +# Disable checking that add-ons are signed by the trusted root +MOZ_ADDON_SIGNING=${MOZ_ADDON_SIGNING-0} +# Disable enforcing that add-ons are signed by the trusted root +MOZ_REQUIRE_SIGNING=${MOZ_REQUIRE_SIGNING-0} + +ac_add_options --enable-js-shell + +. "$topsrcdir/build/mozconfig.automation" diff --git a/build/mozconfig.rust b/build/mozconfig.rust new file mode 100644 index 00000000..65177a6b --- /dev/null +++ b/build/mozconfig.rust @@ -0,0 +1,10 @@ +# Options to enable rust in automation builds. + +# Tell configure to use the tooltool rustc. +# Assume this is compiled with --enable-rpath so we don't +# have to set LD_LIBRARY_PATH. +RUSTC="$topsrcdir/rustc/bin/rustc" +CARGO="$topsrcdir/cargo/bin/cargo" + +# Enable rust in the build. +ac_add_options --enable-rust diff --git a/build/mozconfig.vs-common b/build/mozconfig.vs-common new file mode 100644 index 00000000..ca5df2f3 --- /dev/null +++ b/build/mozconfig.vs-common @@ -0,0 +1,4 @@ +# Pymake needs Windows-style paths. Use cmd.exe to hack around this. +mk_export_correct_style() { + mk_add_options "export $1=$(cmd.exe //c echo %$1%)" +} diff --git a/build/mozconfig.win-common b/build/mozconfig.win-common new file mode 100644 index 00000000..6e25b7ce --- /dev/null +++ b/build/mozconfig.win-common @@ -0,0 +1,16 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if [ "x$IS_NIGHTLY" = "xyes" ]; then + # Some nightlies (eg: Mulet) don't want these set. + MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1} + MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1} + MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1} +fi + +# Some builds (eg: Mulet) don't want the installer, so only set this if it +# hasn't already been set. +MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-1} + +export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=c:/builds/crash-stats-api.token diff --git a/build/pymake/make.py b/build/pymake/make.py new file mode 100644 index 00000000..178bdd74 --- /dev/null +++ b/build/pymake/make.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This is a wrapper around mozilla-central's pymake. If that isn't found then +# this uses client.py to pull it in. + +import os +import sys +import subprocess +import shlex + +def getpath(relpath): + thisdir = os.path.dirname(__file__) + return os.path.abspath(os.path.join(thisdir, *relpath)) + +PYMAKE = getpath(["..", "..", "mozilla", "build", "pymake", "make.py"]) +CLIENT_PY = getpath(["..", "..", "client.py"]) +CLIENT_PY_ARGS = getpath(["..", "client.py-args"]) + +def main(args): + if 'TINDERBOX_OUTPUT' in os.environ: + # When building on mozilla build slaves, execute mozmake instead. Until bug + # 978211, this is the easiest, albeit hackish, way to do this. + mozmake = os.path.join(os.path.dirname(__file__), '..', '..', + 'mozmake.exe') + if os.path.exists(mozmake): + cmd = [mozmake] + cmd.extend(sys.argv[1:]) + shell = os.environ.get('SHELL') + if shell and not shell.lower().endswith('.exe'): + cmd += ['SHELL=%s.exe' % shell] + sys.exit(subprocess.call(cmd)) + + if not os.path.exists(PYMAKE): + clientpyargs = open(CLIENT_PY_ARGS, "r").read().strip() + clientpyargs = shlex.split(clientpyargs) + subprocess.check_call([sys.executable, CLIENT_PY, "checkout"] + + clientpyargs) + + if not os.path.exists(PYMAKE): + raise Exception("Pymake not found even after client.py was run") + + subprocess.check_call([sys.executable, PYMAKE] + args) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/build/pypng/check-sync-exceptions b/build/pypng/check-sync-exceptions new file mode 100644 index 00000000..b326f7c3 --- /dev/null +++ b/build/pypng/check-sync-exceptions @@ -0,0 +1,3 @@ +# Nothing in this directory needs to be in sync with mozilla +# The contents are used only in c-c +* \ No newline at end of file diff --git a/build/pypng/exnumpy.py b/build/pypng/exnumpy.py new file mode 100644 index 00000000..82daf0a6 --- /dev/null +++ b/build/pypng/exnumpy.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/exnumpy.py $ +# $Rev: 126 $ + +# Numpy example. +# Original code created by Mel Raab, modified by David Jones. + +''' + Example code integrating RGB PNG files, PyPNG and NumPy + (abstracted from Mel Raab's functioning code) +''' + +# http://www.python.org/doc/2.4.4/lib/module-itertools.html +import itertools + +import numpy +import png + + +''' If you have a PNG file for an RGB image, + and want to create a numpy array of data from it. +''' +# Read the file "picture.png" from the current directory. The `Reader` +# class can take a filename, a file-like object, or the byte data +# directly; this suggests alternatives such as using urllib to read +# an image from the internet: +# png.Reader(file=urllib.urlopen('http://www.libpng.org/pub/png/PngSuite/basn2c16.png')) +pngReader=png.Reader(filename='picture.png') +# Tuple unpacking, using multiple assignment, is very useful for the +# result of asDirect (and other methods). +# See +# http://docs.python.org/tutorial/introduction.html#first-steps-towards-programming +row_count, column_count, pngdata, meta = pngReader.asDirect() +bitdepth=meta['bitdepth'] +plane_count=meta['planes'] + +# Make sure we're dealing with RGB files +assert plane_count == 3 + +''' Boxed row flat pixel: + list([R,G,B, R,G,B, R,G,B], + [R,G,B, R,G,B, R,G,B]) + Array dimensions for this example: (2,9) + + Create `image_2d` as a two-dimensional NumPy array by stacking a + sequence of 1-dimensional arrays (rows). + The NumPy array mimics PyPNG's (boxed row flat pixel) representation; + it will have dimensions ``(row_count,column_count*plane_count)``. +''' +# The use of ``numpy.uint16``, below, is to convert each row to a NumPy +# array with data type ``numpy.uint16``. This is a feature of NumPy, +# discussed further in +# http://docs.scipy.org/doc/numpy/user/basics.types.html . +# You can use avoid the explicit conversion with +# ``numpy.vstack(pngdata)``, but then NumPy will pick the array's data +# type; in practice it seems to pick ``numpy.int32``, which is large enough +# to hold any pixel value for any PNG image but uses 4 bytes per value when +# 1 or 2 would be enough. +# --- extract 001 start +image_2d = numpy.vstack(itertools.imap(numpy.uint16, pngdata)) +# --- extract 001 end +# Do not be tempted to use ``numpy.asarray``; when passed an iterator +# (`pngdata` is often an iterator) it will attempt to create a size 1 +# array with the iterator as its only element. +# An alternative to the above is to create the target array of the right +# shape, then populate it row by row: +if 0: + image_2d = numpy.zeros((row_count,plane_count*column_count), + dtype=numpy.uint16) + for row_index, one_boxed_row_flat_pixels in enumerate(pngdata): + image_2d[row_index,:]=one_boxed_row_flat_pixels + +del pngReader +del pngdata + + +''' Reconfigure for easier referencing, similar to + Boxed row boxed pixel: + list([ (R,G,B), (R,G,B), (R,G,B) ], + [ (R,G,B), (R,G,B), (R,G,B) ]) + Array dimensions for this example: (2,3,3) + + ``image_3d`` will contain the image as a three-dimensional numpy + array, having dimensions ``(row_count,column_count,plane_count)``. +''' +# --- extract 002 start +image_3d = numpy.reshape(image_2d, + (row_count,column_count,plane_count)) +# --- extract 002 end + + +''' ============= ''' + +''' Convert NumPy image_3d array to PNG image file. + + If the data is three-dimensional, as it is above, the best thing + to do is reshape it into a two-dimensional array with a shape of + ``(row_count, column_count*plane_count)``. Because a + two-dimensional numpy array is an iterator, it can be passed + directly to the ``png.Writer.write`` method. +''' + +row_count, column_count, plane_count = image_3d.shape +assert plane_count==3 + +pngfile = open('picture_out.png', 'wb') +try: + # This example assumes that you have 16-bit pixel values in the data + # array (that's what the ``bitdepth=16`` argument is for). + # If you don't, then the resulting PNG file will likely be + # very dark. Hey, it's only an example. + pngWriter = png.Writer(column_count, row_count, + greyscale=False, + alpha=False, + bitdepth=16) + # As of 2009-04-13 passing a numpy array that has an element type + # that is a numpy integer type (for example, the `image_3d` array has an + # element type of ``numpy.uint16``) generates a deprecation warning. + # This is probably a bug in numpy; it may go away in the future. + # The code still works despite the warning. + # See http://code.google.com/p/pypng/issues/detail?id=44 +# --- extract 003 start + pngWriter.write(pngfile, + numpy.reshape(image_3d, (-1, column_count*plane_count))) +# --- extract 003 end +finally: + pngfile.close() + diff --git a/build/pypng/iccp.py b/build/pypng/iccp.py new file mode 100644 index 00000000..190db734 --- /dev/null +++ b/build/pypng/iccp.py @@ -0,0 +1,537 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/iccp.py $ +# $Rev: 182 $ + +# iccp +# +# International Color Consortium Profile +# +# Tools for manipulating ICC profiles. +# +# An ICC profile can be extracted from a PNG image (iCCP chunk). +# +# +# Non-standard ICCP tags. +# +# Apple use some (widespread but) non-standard tags. These can be +# displayed in Apple's ColorSync Utility. +# - 'vcgt' (Video Card Gamma Tag). Table to load into video +# card LUT to apply gamma. +# - 'ndin' Apple display native information. +# - 'dscm' Apple multi-localized description strings. +# - 'mmod' Apple display make and model information. +# + +# References +# +# [ICC 2001] ICC Specification ICC.1:2001-04 (Profile version 2.4.0) +# [ICC 2004] ICC Specification ICC.1:2004-10 (Profile version 4.2.0.0) + +import struct + +import png + +class FormatError(Exception): + pass + +class Profile: + """An International Color Consortium Profile (ICC Profile).""" + + def __init__(self): + self.rawtagtable = None + self.rawtagdict = {} + self.d = dict() + + def fromFile(self, inp, name=''): + + # See [ICC 2004] + profile = inp.read(128) + if len(profile) < 128: + raise FormatError("ICC Profile is too short.") + size, = struct.unpack('>L', profile[:4]) + profile += inp.read(d['size'] - len(profile)) + return self.fromString(profile, name) + + def fromString(self, profile, name=''): + self.d = dict() + d = self.d + if len(profile) < 128: + raise FormatError("ICC Profile is too short.") + d.update( + zip(['size', 'preferredCMM', 'version', + 'profileclass', 'colourspace', 'pcs'], + struct.unpack('>L4sL4s4s4s', profile[:24]))) + if len(profile) < d['size']: + warnings.warn( + 'Profile size declared to be %d, but only got %d bytes' % + (d['size'], len(profile))) + d['version'] = '%08x' % d['version'] + d['created'] = readICCdatetime(profile[24:36]) + d.update( + zip(['acsp', 'platform', 'flag', 'manufacturer', 'model'], + struct.unpack('>4s4s3L', profile[36:56]))) + if d['acsp'] != 'acsp': + warnings.warn('acsp field not present (not an ICC Profile?).') + d['deviceattributes'] = profile[56:64] + d['intent'], = struct.unpack('>L', profile[64:68]) + d['pcsilluminant'] = readICCXYZNumber(profile[68:80]) + d['creator'] = profile[80:84] + d['id'] = profile[84:100] + ntags, = struct.unpack('>L', profile[128:132]) + d['ntags'] = ntags + fmt = '4s2L' * ntags + # tag table + tt = struct.unpack('>' + fmt, profile[132:132+12*ntags]) + tt = group(tt, 3) + + # Could (should) detect 2 or more tags having the same sig. But + # we don't. Two or more tags with the same sig is illegal per + # the ICC spec. + + # Convert (sig,offset,size) triples into (sig,value) pairs. + rawtag = map(lambda x: (x[0], profile[x[1]:x[1]+x[2]]), tt) + self.rawtagtable = rawtag + self.rawtagdict = dict(rawtag) + tag = dict() + # Interpret the tags whose types we know about + for sig, v in rawtag: + if sig in tag: + warnings.warn("Duplicate tag %r found. Ignoring." % sig) + continue + v = ICCdecode(v) + if v is not None: + tag[sig] = v + self.tag = tag + return self + + def greyInput(self): + """Adjust ``self.d`` dictionary for greyscale input device. + ``profileclass`` is 'scnr', ``colourspace`` is 'GRAY', ``pcs`` + is 'XYZ '. + """ + + self.d.update(dict(profileclass='scnr', + colourspace='GRAY', pcs='XYZ ')) + return self + + def maybeAddDefaults(self): + if self.rawtagdict: + return + self._addTags( + cprt='Copyright unknown.', + desc='created by $URL: http://pypng.googlecode.com/svn/trunk/code/iccp.py $ $Rev: 182 $', + wtpt=D50(), + ) + + def addTags(self, **k): + self.maybeAddDefaults() + self._addTags(**k) + + def _addTags(self, **k): + """Helper for :meth:`addTags`.""" + + for tag, thing in k.items(): + if not isinstance(thing, (tuple, list)): + thing = (thing,) + typetag = defaulttagtype[tag] + self.rawtagdict[tag] = encode(typetag, *thing) + return self + + def write(self, out): + """Write ICC Profile to the file.""" + + if not self.rawtagtable: + self.rawtagtable = self.rawtagdict.items() + tags = tagblock(self.rawtagtable) + self.writeHeader(out, 128 + len(tags)) + out.write(tags) + out.flush() + + return self + + def writeHeader(self, out, size=999): + """Add default values to the instance's `d` dictionary, then + write a header out onto the file stream. The size of the + profile must be specified using the `size` argument. + """ + + def defaultkey(d, key, value): + """Add ``[key]==value`` to the dictionary `d`, but only if + it does not have that key already. + """ + + if key in d: + return + d[key] = value + + z = '\x00' * 4 + defaults = dict(preferredCMM=z, + version='02000000', + profileclass=z, + colourspace=z, + pcs='XYZ ', + created=writeICCdatetime(), + acsp='acsp', + platform=z, + flag=0, + manufacturer=z, + model=0, + deviceattributes=0, + intent=0, + pcsilluminant=encodefuns()['XYZ'](*D50()), + creator=z, + ) + for k,v in defaults.items(): + defaultkey(self.d, k, v) + + hl = map(self.d.__getitem__, + ['preferredCMM', 'version', 'profileclass', 'colourspace', + 'pcs', 'created', 'acsp', 'platform', 'flag', + 'manufacturer', 'model', 'deviceattributes', 'intent', + 'pcsilluminant', 'creator']) + # Convert to struct.pack input + hl[1] = int(hl[1], 16) + + out.write(struct.pack('>L4sL4s4s4s12s4s4sL4sLQL12s4s', size, *hl)) + out.write('\x00' * 44) + return self + +def encodefuns(): + """Returns a dictionary mapping ICC type signature sig to encoding + function. Each function returns a string comprising the content of + the encoded value. To form the full value, the type sig and the 4 + zero bytes should be prefixed (8 bytes). + """ + + def desc(ascii): + """Return textDescription type [ICC 2001] 6.5.17. The ASCII part is + filled in with the string `ascii`, the Unicode and ScriptCode parts + are empty.""" + + ascii += '\x00' + l = len(ascii) + + return struct.pack('>L%ds2LHB67s' % l, + l, ascii, 0, 0, 0, 0, '') + + def text(ascii): + """Return textType [ICC 2001] 6.5.18.""" + + return ascii + '\x00' + + def curv(f=None, n=256): + """Return a curveType, [ICC 2001] 6.5.3. If no arguments are + supplied then a TRC for a linear response is generated (no entries). + If an argument is supplied and it is a number (for *f* to be a + number it means that ``float(f)==f``) then a TRC for that + gamma value is generated. + Otherwise `f` is assumed to be a function that maps [0.0, 1.0] to + [0.0, 1.0]; an `n` element table is generated for it. + """ + + if f is None: + return struct.pack('>L', 0) + try: + if float(f) == f: + return struct.pack('>LH', 1, int(round(f*2**8))) + except (TypeError, ValueError): + pass + assert n >= 2 + table = [] + M = float(n-1) + for i in range(n): + x = i/M + table.append(int(round(f(x) * 65535))) + return struct.pack('>L%dH' % n, n, *table) + + def XYZ(*l): + return struct.pack('>3l', *map(fs15f16, l)) + + return locals() + +# Tag type defaults. +# Most tags can only have one or a few tag types. +# When encoding, we associate a default tag type with each tag so that +# the encoding is implicit. +defaulttagtype=dict( + A2B0='mft1', + A2B1='mft1', + A2B2='mft1', + bXYZ='XYZ', + bTRC='curv', + B2A0='mft1', + B2A1='mft1', + B2A2='mft1', + calt='dtim', + targ='text', + chad='sf32', + chrm='chrm', + cprt='desc', + crdi='crdi', + dmnd='desc', + dmdd='desc', + devs='', + gamt='mft1', + kTRC='curv', + gXYZ='XYZ', + gTRC='curv', + lumi='XYZ', + meas='', + bkpt='XYZ', + wtpt='XYZ', + ncol='', + ncl2='', + resp='', + pre0='mft1', + pre1='mft1', + pre2='mft1', + desc='desc', + pseq='', + psd0='data', + psd1='data', + psd2='data', + psd3='data', + ps2s='data', + ps2i='data', + rXYZ='XYZ', + rTRC='curv', + scrd='desc', + scrn='', + tech='sig', + bfd='', + vued='desc', + view='view', +) + +def encode(tsig, *l): + """Encode a Python value as an ICC type. `tsig` is the type + signature to (the first 4 bytes of the encoded value, see [ICC 2004] + section 10. + """ + + fun = encodefuns() + if tsig not in fun: + raise "No encoder for type %r." % tsig + v = fun[tsig](*l) + # Padd tsig out with spaces. + tsig = (tsig + ' ')[:4] + return tsig + '\x00'*4 + v + +def tagblock(tag): + """`tag` should be a list of (*signature*, *element*) pairs, where + *signature* (the key) is a length 4 string, and *element* is the + content of the tag element (another string). + + The entire tag block (consisting of first a table and then the + element data) is constructed and returned as a string. + """ + + n = len(tag) + tablelen = 12*n + + # Build the tag table in two parts. A list of 12-byte tags, and a + # string of element data. Offset is the offset from the start of + # the profile to the start of the element data (so the offset for + # the next element is this offset plus the length of the element + # string so far). + offset = 128 + tablelen + 4 + # The table. As a string. + table = '' + # The element data + element = '' + for k,v in tag: + table += struct.pack('>4s2L', k, offset + len(element), len(v)) + element += v + return struct.pack('>L', n) + table + element + +def iccp(out, inp): + profile = Profile().fromString(*profileFromPNG(inp)) + print >>out, profile.d + print >>out, map(lambda x: x[0], profile.rawtagtable) + print >>out, profile.tag + +def profileFromPNG(inp): + """Extract profile from PNG file. Return (*profile*, *name*) + pair.""" + r = png.Reader(file=inp) + _,chunk = r.chunk('iCCP') + i = chunk.index('\x00') + name = chunk[:i] + compression = chunk[i+1] + assert compression == chr(0) + profile = chunk[i+2:].decode('zlib') + return profile, name + +def iccpout(out, inp): + """Extract ICC Profile from PNG file `inp` and write it to + the file `out`.""" + + out.write(profileFromPNG(inp)[0]) + +def fs15f16(x): + """Convert float to ICC s15Fixed16Number (as a Python ``int``).""" + + return int(round(x * 2**16)) + +def D50(): + """Return D50 illuminant as an (X,Y,Z) triple.""" + + # See [ICC 2001] A.1 + return (0.9642, 1.0000, 0.8249) + + +def writeICCdatetime(t=None): + """`t` should be a gmtime tuple (as returned from + ``time.gmtime()``). If not supplied, the current time will be used. + Return an ICC dateTimeNumber in a 12 byte string. + """ + + import time + if t is None: + t = time.gmtime() + return struct.pack('>6H', *t[:6]) + +def readICCdatetime(s): + """Convert from 12 byte ICC representation of dateTimeNumber to + ISO8601 string. See [ICC 2004] 5.1.1""" + + return '%04d-%02d-%02dT%02d:%02d:%02dZ' % struct.unpack('>6H', s) + +def readICCXYZNumber(s): + """Convert from 12 byte ICC representation of XYZNumber to (x,y,z) + triple of floats. See [ICC 2004] 5.1.11""" + + return s15f16l(s) + +def s15f16l(s): + """Convert sequence of ICC s15Fixed16 to list of float.""" + # Note: As long as float has at least 32 bits of mantissa, all + # values are preserved. + n = len(s)//4 + t = struct.unpack('>%dl' % n, s) + return map((2**-16).__mul__, t) + +# Several types and their byte encodings are defined by [ICC 2004] +# section 10. When encoded, a value begins with a 4 byte type +# signature. We use the same 4 byte type signature in the names of the +# Python functions that decode the type into a Pythonic representation. + +def ICCdecode(s): + """Take an ICC encoded tag, and dispatch on its type signature + (first 4 bytes) to decode it into a Python value. Pair (*sig*, + *value*) is returned, where *sig* is a 4 byte string, and *value* is + some Python value determined by the content and type. + """ + + sig = s[0:4].strip() + f=dict(text=RDtext, + XYZ=RDXYZ, + curv=RDcurv, + vcgt=RDvcgt, + sf32=RDsf32, + ) + if sig not in f: + return None + return (sig, f[sig](s)) + +def RDXYZ(s): + """Convert ICC XYZType to rank 1 array of trimulus values.""" + + # See [ICC 2001] 6.5.26 + assert s[0:4] == 'XYZ ' + return readICCXYZNumber(s[8:]) + +def RDsf32(s): + """Convert ICC s15Fixed16ArrayType to list of float.""" + # See [ICC 2004] 10.18 + assert s[0:4] == 'sf32' + return s15f16l(s[8:]) + +def RDmluc(s): + """Convert ICC multiLocalizedUnicodeType. This types encodes + several strings together with a language/country code for each + string. A list of (*lc*, *string*) pairs is returned where *lc* is + the 4 byte language/country code, and *string* is the string + corresponding to that code. It seems unlikely that the same + language/country code will appear more than once with different + strings, but the ICC standard does not prohibit it.""" + # See [ICC 2004] 10.13 + assert s[0:4] == 'mluc' + n,sz = struct.unpack('>2L', s[8:16]) + assert sz == 12 + record = [] + for i in range(n): + lc,l,o = struct.unpack('4s2L', s[16+12*n:28+12*n]) + record.append(lc, s[o:o+l]) + # How are strings encoded? + return record + +def RDtext(s): + """Convert ICC textType to Python string.""" + # Note: type not specified or used in [ICC 2004], only in older + # [ICC 2001]. + # See [ICC 2001] 6.5.18 + assert s[0:4] == 'text' + return s[8:-1] + +def RDcurv(s): + """Convert ICC curveType.""" + # See [ICC 2001] 6.5.3 + assert s[0:4] == 'curv' + count, = struct.unpack('>L', s[8:12]) + if count == 0: + return dict(gamma=1) + table = struct.unpack('>%dH' % count, s[12:]) + if count == 1: + return dict(gamma=table[0]*2**-8) + return table + +def RDvcgt(s): + """Convert Apple CMVideoCardGammaType.""" + # See + # http://developer.apple.com/documentation/GraphicsImaging/Reference/ColorSync_Manager/Reference/reference.html#//apple_ref/c/tdef/CMVideoCardGammaType + assert s[0:4] == 'vcgt' + tagtype, = struct.unpack('>L', s[8:12]) + if tagtype != 0: + return s[8:] + if tagtype == 0: + # Table. + channels,count,size = struct.unpack('>3H', s[12:18]) + if size == 1: + fmt = 'B' + elif size == 2: + fmt = 'H' + else: + return s[8:] + l = len(s[18:])//size + t = struct.unpack('>%d%s' % (l, fmt), s[18:]) + t = group(t, count) + return size, t + return s[8:] + + +def group(s, n): + # See + # http://www.python.org/doc/2.6/library/functions.html#zip + return zip(*[iter(s)]*n) + + +def main(argv=None): + import sys + from getopt import getopt + if argv is None: + argv = sys.argv + argv = argv[1:] + opt,arg = getopt(argv, 'o:') + if len(arg) > 0: + inp = open(arg[0], 'rb') + else: + inp = sys.stdin + for o,v in opt: + if o == '-o': + f = open(v, 'wb') + return iccpout(f, inp) + return iccp(sys.stdout, inp) + +if __name__ == '__main__': + main() diff --git a/build/pypng/mkiccp.py b/build/pypng/mkiccp.py new file mode 100644 index 00000000..08e8df63 --- /dev/null +++ b/build/pypng/mkiccp.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/mkiccp.py $ +# $Rev: 182 $ +# Make ICC Profile + +# References +# +# [ICC 2001] ICC Specification ICC.1:2001-04 (Profile version 2.4.0) +# [ICC 2004] ICC Specification ICC.1:2004-10 (Profile version 4.2.0.0) + +import struct + +# Local module. +import iccp + +def black(m): + """Return a function that maps all values from [0.0,m] to 0, and maps + the range [m,1.0] into [0.0, 1.0] linearly. + """ + + m = float(m) + + def f(x): + if x <= m: + return 0.0 + return (x-m)/(1.0-m) + return f + +# For monochrome input the required tags are (See [ICC 2001] 6.3.1.1): +# profileDescription [ICC 2001] 6.4.32 +# grayTRC [ICC 2001] 6.4.19 +# mediaWhitePoint [ICC 2001] 6.4.25 +# copyright [ICC 2001] 6.4.13 + +def agreyprofile(out): + it = iccp.Profile().greyInput() + it.addTags(kTRC=black(0.07)) + it.write(out) + +def main(): + import sys + agreyprofile(sys.stdout) + +if __name__ == '__main__': + main() diff --git a/build/pypng/pdsimgtopng b/build/pypng/pdsimgtopng new file mode 100644 index 00000000..975db930 --- /dev/null +++ b/build/pypng/pdsimgtopng @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pdsimgtopng $ +# $Rev: 154 $ +# PDS Image to PNG + +import re +import struct + +import png + +class FormatError(Exception): + pass + +def pdskey(s, k): + """Lookup key `k` in string `s`. Returns value (as a string), or + raises exception if not found. + """ + + assert re.match(r' *\^?[:\w]+$', k) + safere = '^' + re.escape(k) +r' *= *(\w+)' + m = re.search(safere, s, re.MULTILINE) + if not m: + raise FormatError("Can't find %s." % k) + return m.group(1) + +def img(inp): + """Open the PDS IMG file `inp` and return (*pixels*, *info*). + *pixels* is an iterator over the rows, *info* is the information + dictionary. + """ + + err = __import__('sys').stderr + + consumed = 1024 + + s = inp.read(consumed) + record_type = pdskey(s, 'RECORD_TYPE') + if record_type != 'FIXED_LENGTH': + raise FormatError( + "Can only deal with FIXED_LENGTH record type (found %s)" % + record_type) + record_bytes = int(pdskey(s,'RECORD_BYTES')) + file_records = int(pdskey(s, 'FILE_RECORDS')) + label_records = int(pdskey(s, 'LABEL_RECORDS')) + remaining = label_records * record_bytes - consumed + s += inp.read(remaining) + consumed += remaining + + image_pointer = int(pdskey(s, '^IMAGE')) + # "^IMAGE" locates a record. Records are numbered starting from 1. + image_index = image_pointer - 1 + image_offset = image_index * record_bytes + gap = image_offset - consumed + assert gap >= 0 + if gap: + inp.read(gap) + # This assumes there is only one OBJECT in the file, and it is the + # IMAGE. + height = int(pdskey(s, ' LINES')) + width = int(pdskey(s, ' LINE_SAMPLES')) + sample_type = pdskey(s, ' SAMPLE_TYPE') + sample_bits = int(pdskey(s, ' SAMPLE_BITS')) + # For Messenger MDIS, SAMPLE_BITS is reported as 16, but only values + # from 0 ot 4095 are used. + bitdepth = 12 + if sample_type == 'MSB_UNSIGNED_INTEGER': + fmt = '>H' + else: + raise 'Unknown sample type: %s.' % sample_type + sample_bytes = (1,2)[bitdepth > 8] + row_bytes = sample_bytes * width + fmt = fmt[:1] + str(width) + fmt[1:] + def rowiter(): + for y in range(height): + yield struct.unpack(fmt, inp.read(row_bytes)) + info = dict(greyscale=True, alpha=False, bitdepth=bitdepth, + size=(width,height), gamma=1.0) + return rowiter(), info + + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + argv = argv[1:] + arg = argv + if len(arg) >= 1: + f = open(arg[0], 'rb') + else: + f = sys.stdin + pixels,info = img(f) + w = png.Writer(**info) + w.write(sys.stdout, pixels) + +if __name__ == '__main__': + main() + + diff --git a/build/pypng/pipasgrey b/build/pypng/pipasgrey new file mode 100644 index 00000000..2b3727f9 --- /dev/null +++ b/build/pypng/pipasgrey @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipasgrey $ +# $Rev: 187 $ + +# pipasgrey + +# Convert image to grey (L, or LA), but only if that involves no colour +# change. + +def asgrey(out, inp, quiet=False): + """Convert image to greyscale, but only when no colour change. This + works by using the input G channel (green) as the output L channel + (luminance) and checking that every pixel is grey as we go. A non-grey + pixel will raise an error, but if `quiet` is true then the grey pixel + check is suppressed. + """ + + from array import array + + import png + + r = png.Reader(file=inp) + _,_,pixels,info = r.asDirect() + if info['greyscale']: + w = png.Writer(**info) + return w.write(out, pixels) + planes = info['planes'] + targetplanes = planes - 2 + alpha = info['alpha'] + width = info['size'][0] + typecode = 'BH'[info['bitdepth'] > 8] + # Values per target row + vpr = width * (targetplanes) + def iterasgrey(): + for i,row in enumerate(pixels): + row = array(typecode, row) + targetrow = array(typecode, [0]*vpr) + # Copy G (and possibly A) channel. + green = row[0::planes] + if alpha: + targetrow[0::2] = green + targetrow[1::2] = row[3::4] + else: + targetrow = green + # Check R and B channel match. + if not quiet and ( + green != row[0::planes] or green != row[2::planes]): + raise ValueError('Row %i contains non-grey pixel.' % i) + yield targetrow + info['greyscale'] = True + del info['planes'] + w = png.Writer(**info) + w.write(out, iterasgrey()) + +def main(argv=None): + from getopt import getopt + import sys + if argv is None: + argv = sys.argv + argv = argv[1:] + opt,argv = getopt(argv, 'q') + quiet = False + for o,v in opt: + if o == '-q': + quiet = True + if len(argv) > 0: + f = open(argv[0], 'rb') + else: + f = sys.stdin + return asgrey(sys.stdout, f, quiet) + +if __name__ == '__main__': + main() diff --git a/build/pypng/pipcat b/build/pypng/pipcat new file mode 100644 index 00000000..e0d08052 --- /dev/null +++ b/build/pypng/pipcat @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipcat $ +# $Rev: 77 $ + +# http://www.python.org/doc/2.4.4/lib/module-itertools.html +import itertools +import sys + +import png + +def cat(out, l): + """Concatenate the list of images. All input images must be same + height and have the same number of channels. They are concatenated + left-to-right. `out` is the (open file) destination for the + output image. `l` should be a list of open files (the input + image files). + """ + + l = map(lambda f: png.Reader(file=f), l) + # Ewgh, side effects. + map(lambda r: r.preamble(), l) + # The reference height; from the first image. + height = l[0].height + # The total target width + width = 0 + for i,r in enumerate(l): + if r.height != height: + raise Error('Image %d, height %d, does not match %d.' % + (i, r.height, height)) + width += r.width + pixel,info = zip(*map(lambda r: r.asDirect()[2:4], l)) + tinfo = dict(info[0]) + del tinfo['size'] + w = png.Writer(width, height, **tinfo) + def itercat(): + for row in itertools.izip(*pixel): + yield itertools.chain(*row) + w.write(out, itercat()) + +def main(argv): + return cat(sys.stdout, map(lambda n: open(n, 'rb'), argv[1:])) + +if __name__ == '__main__': + main(sys.argv) diff --git a/build/pypng/pipcolours b/build/pypng/pipcolours new file mode 100644 index 00000000..7c76df8c --- /dev/null +++ b/build/pypng/pipcolours @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipcolours $ +# $Rev: 96 $ + +# pipcolours - extract all colours present in source image. + +def colours(out, inp): + import itertools + import png + + r = png.Reader(file=inp) + _,_,pixels,info = r.asDirect() + planes = info['planes'] + col = set() + for row in pixels: + # Ewgh, side effects on col + map(col.add, png.group(row, planes)) + col,planes = channel_reduce(col, planes) + col = list(col) + col.sort() + col = list(itertools.chain(*col)) + width = len(col)//planes + greyscale = planes in (1,2) + alpha = planes in (2,4) + bitdepth = info['bitdepth'] + w = png.Writer(width, 1, + bitdepth=bitdepth, greyscale=greyscale, alpha=alpha) + w.write(out, [col]) + +def channel_reduce(col, planes): + """Attempt to reduce the number of channels in the set of + colours.""" + if planes >= 3: + def isgrey(c): + return c[0] == c[1] == c[2] + if min(map(isgrey, col)) == True: + # Every colour is grey. + col = set(map(lambda x: x[0::3], col)) + planes -= 2 + return col,planes + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + + argv = argv[1:] + if len(argv) > 0: + f = open(argv[0], 'rb') + else: + f = sys.stdin + return colours(sys.stdout, f) + +if __name__ == '__main__': + main() diff --git a/build/pypng/pipcomposite b/build/pypng/pipcomposite new file mode 100644 index 00000000..21dd283a --- /dev/null +++ b/build/pypng/pipcomposite @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipcomposite $ +# $Rev: 208 $ +# pipcomposite +# Image alpha compositing. + +""" +pipcomposite [--background #rrggbb] file.png + +Composite an image onto a background and output the result. The +background colour is specified with an HTML-style triple (3, 6, or 12 +hex digits), and defaults to black (#000). + +The output PNG has no alpha channel. + +It is valid for the input to have no alpha channel, but it doesn't +make much sense: the output will equal the input. +""" + +import sys + +def composite(out, inp, background): + import png + + p = png.Reader(file=inp) + w,h,pixel,info = p.asRGBA() + + outinfo = dict(info) + outinfo['alpha'] = False + outinfo['planes'] -= 1 + outinfo['interlace'] = 0 + + # Convert to tuple and normalise to same range as source. + background = rgbhex(background) + maxval = float(2**info['bitdepth'] - 1) + background = map(lambda x: int(0.5 + x*maxval/65535.0), + background) + # Repeat background so that it's a whole row of sample values. + background *= w + + def iterrow(): + for row in pixel: + # Remove alpha from row, then create a list with one alpha + # entry _per channel value_. + # Squirrel the alpha channel away (and normalise it). + t = map(lambda x: x/maxval, row[3::4]) + row = list(row) + del row[3::4] + alpha = row[:] + for i in range(3): + alpha[i::3] = t + assert len(alpha) == len(row) == len(background) + yield map(lambda a,v,b: int(0.5 + a*v + (1.0-a)*b), + alpha, row, background) + + w = png.Writer(**outinfo) + w.write(out, iterrow()) + +def rgbhex(s): + """Take an HTML style string of the form "#rrggbb" and return a + colour (R,G,B) triple. Following the initial '#' there can be 3, 6, + or 12 digits (for 4-, 8- or 16- bits per channel). In all cases the + values are expanded to a full 16-bit range, so the returned values + are all in range(65536). + """ + + assert s[0] == '#' + s = s[1:] + assert len(s) in (3,6,12) + + # Create a target list of length 12, and expand the string s to make + # it length 12. + l = ['z']*12 + if len(s) == 3: + for i in range(4): + l[i::4] = s + if len(s) == 6: + for i in range(2): + l[i::4] = s[i::2] + l[i+2::4] = s[i::2] + if len(s) == 12: + l[:] = s + s = ''.join(l) + return map(lambda x: int(x, 16), (s[:4], s[4:8], s[8:])) + +class Usage(Exception): + pass + +def main(argv=None): + import getopt + import sys + + if argv is None: + argv = sys.argv + + argv = argv[1:] + + try: + try: + opt,arg = getopt.getopt(argv, '', + ['background=']) + except getopt.error, msg: + raise Usage(msg) + background = '#000' + for o,v in opt: + if o in ['--background']: + background = v + except Usage, err: + print >>sys.stderr, __doc__ + print >>sys.stderr, str(err) + return 2 + + if len(arg) > 0: + f = open(arg[0], 'rb') + else: + f = sys.stdin + return composite(sys.stdout, f, background) + + +if __name__ == '__main__': + main() diff --git a/build/pypng/pipdither b/build/pypng/pipdither new file mode 100644 index 00000000..c14c76c3 --- /dev/null +++ b/build/pypng/pipdither @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipdither $ +# $Rev: 150 $ + +# pipdither +# Error Diffusing image dithering. +# Now with serpentine scanning. + +# See http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT + +# http://www.python.org/doc/2.4.4/lib/module-bisect.html +from bisect import bisect_left + +import png + +def dither(out, inp, + bitdepth=1, linear=False, defaultgamma=1.0, targetgamma=None, + cutoff=0.75): + """Dither the input PNG `inp` into an image with a smaller bit depth + and write the result image onto `out`. `bitdepth` specifies the bit + depth of the new image. + + Normally the source image gamma is honoured (the image is + converted into a linear light space before being dithered), but + if the `linear` argument is true then the image is treated as + being linear already: no gamma conversion is done (this is + quicker, and if you don't care much about accuracy, it won't + matter much). + + Images with no gamma indication (no ``gAMA`` chunk) are normally + treated as linear (gamma = 1.0), but often it can be better + to assume a different gamma value: For example continuous tone + photographs intended for presentation on the web often carry + an implicit assumption of being encoded with a gamma of about + 0.45 (because that's what you get if you just "blat the pixels" + onto a PC framebuffer), so ``defaultgamma=0.45`` might be a + good idea. `defaultgamma` does not override a gamma value + specified in the file itself: It is only used when the file + does not specify a gamma. + + If you (pointlessly) specify both `linear` and `defaultgamma`, + `linear` wins. + + The gamma of the output image is, by default, the same as the input + image. The `targetgamma` argument can be used to specify a + different gamma for the output image. This effectively recodes the + image to a different gamma, dithering as we go. The gamma specified + is the exponent used to encode the output file (and appears in the + output PNG's ``gAMA`` chunk); it is usually less than 1. + + """ + + # Encoding is what happened when the PNG was made (and also what + # happens when we output the PNG). Decoding is what we do to the + # source PNG in order to process it. + + # The dithering algorithm is not completely general; it + # can only do bit depth reduction, not arbitrary palette changes. + import operator + maxval = 2**bitdepth - 1 + r = png.Reader(file=inp) + # If image gamma is 1 or gamma is not present and we are assuming a + # value of 1, then it is faster to pass a maxval parameter to + # asFloat (the multiplications get combined). With gamma, we have + # to have the pixel values from 0.0 to 1.0 (as long as we are doing + # gamma correction here). + # Slightly annoyingly, we don't know the image gamma until we've + # called asFloat(). + _,_,pixels,info = r.asDirect() + planes = info['planes'] + assert planes == 1 + width = info['size'][0] + sourcemaxval = 2**info['bitdepth'] - 1 + if linear: + gamma = 1 + else: + gamma = info.get('gamma') or defaultgamma + # Convert gamma from encoding gamma to the required power for + # decoding. + decode = 1.0/gamma + # Build a lookup table for decoding; convert from pixel values to linear + # space: + sourcef = 1.0/sourcemaxval + incode = map(sourcef.__mul__, range(sourcemaxval+1)) + if decode != 1.0: + incode = map(decode.__rpow__, incode) + # Could be different, later on. targetdecode is the assumed gamma + # that is going to be used to decoding the target PNG. It is the + # reciprocal of the exponent that we use to encode the target PNG. + # This is the value that we need to build our table that we use for + # converting from linear to target colour space. + if targetgamma is None: + targetdecode = decode + else: + targetdecode = 1.0/targetgamma + # The table we use for encoding (creating the target PNG), still + # maps from pixel value to linear space, but we use it inverted, by + # searching through it with bisect. + targetf = 1.0/maxval + outcode = map(targetf.__mul__, range(maxval+1)) + if targetdecode != 1.0: + outcode = map(targetdecode.__rpow__, outcode) + # The table used for choosing output codes. These values represent + # the cutoff points between two adjacent output codes. + choosecode = zip(outcode[1:], outcode) + p = cutoff + choosecode = map(lambda x: x[0]*p+x[1]*(1.0-p), choosecode) + def iterdither(): + # Errors diffused downwards (into next row) + ed = [0.0]*width + flipped = False + for row in pixels: + row = map(incode.__getitem__, row) + row = map(operator.add, ed, row) + if flipped: + row = row[::-1] + targetrow = [0] * width + for i,v in enumerate(row): + # Clamp. Necessary because previously added errors may take + # v out of range. + v = max(0.0, min(v, 1.0)) + # `it` will be the index of the chosen target colour; + it = bisect_left(choosecode, v) + t = outcode[it] + targetrow[i] = it + # err is the error that needs distributing. + err = v - t + # Sierra "Filter Lite" distributes * 2 + # as per this diagram. 1 1 + ef = err/2.0 + # :todo: consider making rows one wider at each end and + # removing "if"s + if i+1 < width: + row[i+1] += ef + ef /= 2.0 + ed[i] = ef + if i: + ed[i-1] += ef + if flipped: + ed = ed[::-1] + targetrow = targetrow[::-1] + yield targetrow + flipped = not flipped + info['bitdepth'] = bitdepth + info['gamma'] = 1.0/targetdecode + w = png.Writer(**info) + w.write(out, iterdither()) + + +def main(argv=None): + # http://www.python.org/doc/2.4.4/lib/module-getopt.html + from getopt import getopt + import sys + if argv is None: + argv = sys.argv + opt,argv = getopt(argv[1:], 'b:c:g:lo:') + k = {} + for o,v in opt: + if o == '-b': + k['bitdepth'] = int(v) + if o == '-c': + k['cutoff'] = float(v) + if o == '-g': + k['defaultgamma'] = float(v) + if o == '-l': + k['linear'] = True + if o == '-o': + k['targetgamma'] = float(v) + if o == '-?': + print >>sys.stderr, "pipdither [-b bits] [-c cutoff] [-g assumed-gamma] [-l] [in.png]" + + if len(argv) > 0: + f = open(argv[0], 'rb') + else: + f = sys.stdin + + return dither(sys.stdout, f, **k) + + +if __name__ == '__main__': + main() diff --git a/build/pypng/piprgb b/build/pypng/piprgb new file mode 100644 index 00000000..fbe1082a --- /dev/null +++ b/build/pypng/piprgb @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/piprgb $ +# $Rev: 131 $ +# piprgb +# +# Convert input image to RGB or RGBA format. Output will be colour type +# 2 or 6, and will not have a tRNS chunk. + +import png + +def rgb(out, inp): + """Convert to RGB/RGBA.""" + + r = png.Reader(file=inp) + r.preamble() + if r.alpha or r.trns: + get = r.asRGBA + else: + get = r.asRGB + pixels,info = get()[2:4] + w = png.Writer(**info) + w.write(out, pixels) + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + if len(argv) > 1: + f = open(argv[1], 'rb') + else: + f = sys.stdin + return rgb(sys.stdout, f) + +if __name__ == '__main__': + main() diff --git a/build/pypng/pipscalez b/build/pypng/pipscalez new file mode 100644 index 00000000..c60762d7 --- /dev/null +++ b/build/pypng/pipscalez @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipscalez $ +# $Rev: 131 $ + +# pipscalez +# Enlarge an image by an integer factor horizontally and vertically. + +def rescale(inp, out, xf, yf): + from array import array + import png + + r = png.Reader(file=inp) + _,_,pixels,meta = r.asDirect() + typecode = 'BH'[meta['bitdepth'] > 8] + planes = meta['planes'] + # We are going to use meta in the call to Writer, so expand the + # size. + x,y = meta['size'] + x *= xf + y *= yf + meta['size'] = (x,y) + del x + del y + # Values per row, target row. + vpr = meta['size'][0] * planes + def iterscale(): + for row in pixels: + bigrow = array(typecode, [0]*vpr) + row = array(typecode, row) + for c in range(planes): + channel = row[c::planes] + for i in range(xf): + bigrow[i*planes+c::xf*planes] = channel + for _ in range(yf): + yield bigrow + w = png.Writer(**meta) + w.write(out, iterscale()) + + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + xf = int(argv[1]) + if len(argv) > 2: + yf = int(argv[2]) + else: + yf = xf + return rescale(sys.stdin, sys.stdout, xf, yf) + +if __name__ == '__main__': + main() diff --git a/build/pypng/pipstack b/build/pypng/pipstack new file mode 100644 index 00000000..5523670e --- /dev/null +++ b/build/pypng/pipstack @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipstack $ +# $Rev: 190 $ + +# pipstack +# Combine input PNG files into a multi-channel output PNG. + +""" +pipstack file1.png [file2.png ...] + +pipstack can be used to combine 3 greyscale PNG files into a colour, RGB, +PNG file. In fact it is slightly more general than that. The number of +channels in the output PNG is equal to the sum of the numbers of +channels in the input images. It is an error if this sum exceeds 4 (the +maximum number of channels in a PNG image is 4, for an RGBA image). The +output colour model corresponds to the number of channels: 1 - +greyscale; 2 - greyscale+alpha; 3 - RGB; 4 - RGB+alpha. + +In this way it is possible to combine 3 greyscale PNG files into an RGB +PNG (a common expected use) as well as more esoteric options: rgb.png + +grey.png = rgba.png; grey.png + grey.png = greyalpha.png. + +Color Profile, Gamma, and so on. + +[This is not implemented yet] + +If an input has an ICC Profile (``iCCP`` chunk) then the output will +have an ICC Profile, but only if it is possible to combine all the input +ICC Profiles. It is possible to combine all the input ICC Profiles +only when: they all use the same Profile Connection Space; the PCS white +point is the same (specified in the header; should always be D50); +possibly some other things I haven't thought of yet. + +If some of the inputs have a ``gAMA`` chunk (specifying gamma) and +an output ICC Profile is being generated, then the gamma information +will be incorporated into the ICC Profile. + +When the output is an RGB colour type and the output ICC Profile is +synthesized, it is necessary to supply colorant tags (``rXYZ`` and so +on). These are taken from ``sRGB``. + +If the input images have ``gAMA`` chunks and no input image has an ICC +Profile then the output image will have a ``gAMA`` chunk, but only if +all the ``gAMA`` chunks specify the same value. Otherwise a warning +will be emitted and no ``gAMA`` chunk. It is possible to add or replace +a ``gAMA`` chunk using the ``pipchunk`` tool. + +gAMA, pHYs, iCCP, sRGB, tIME, any other chunks. +""" + +class Error(Exception): + pass + +def stack(out, inp): + """Stack the input PNG files into a single output PNG.""" + + from array import array + import itertools + # Local module + import png + + if len(inp) < 1: + raise Error("Required input is missing.") + + l = map(png.Reader, inp) + # Let data be a list of (pixel,info) pairs. + data = map(lambda p: p.asDirect()[2:], l) + totalchannels = sum(map(lambda x: x[1]['planes'], data)) + + if not (0 < totalchannels <= 4): + raise Error("Too many channels in input.") + alpha = totalchannels in (2,4) + greyscale = totalchannels in (1,2) + bitdepth = [] + for b in map(lambda x: x[1]['bitdepth'], data): + try: + if b == int(b): + bitdepth.append(b) + continue + except (TypeError, ValueError): + pass + # Assume a tuple. + bitdepth += b + # Currently, fail unless all bitdepths equal. + if len(set(bitdepth)) > 1: + raise NotImplemented("Cannot cope when bitdepths differ - sorry!") + bitdepth = bitdepth[0] + arraytype = 'BH'[bitdepth > 8] + size = map(lambda x: x[1]['size'], data) + # Currently, fail unless all images same size. + if len(set(size)) > 1: + raise NotImplemented("Cannot cope when sizes differ - sorry!") + size = size[0] + # Values per row + vpr = totalchannels * size[0] + def iterstack(): + # the izip call creates an iterator that yields the next row + # from all the input images combined into a tuple. + for irow in itertools.izip(*map(lambda x: x[0], data)): + row = array(arraytype, [0]*vpr) + # output channel + och = 0 + for i,arow in enumerate(irow): + # ensure incoming row is an array + arow = array(arraytype, arow) + n = data[i][1]['planes'] + for j in range(n): + row[och::totalchannels] = arow[j::n] + och += 1 + yield row + w = png.Writer(size[0], size[1], + greyscale=greyscale, alpha=alpha, bitdepth=bitdepth) + w.write(out, iterstack()) + + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + argv = argv[1:] + arg = argv[:] + return stack(sys.stdout, arg) + + +if __name__ == '__main__': + main() diff --git a/build/pypng/pipwindow b/build/pypng/pipwindow new file mode 100644 index 00000000..2f8c7a26 --- /dev/null +++ b/build/pypng/pipwindow @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pipwindow $ +# $Rev: 173 $ + +# pipwindow +# Tool to crop/expand an image to a rectangular window. Come the +# revolution this tool will allow the image and the window to be placed +# arbitrarily (in particular the window can be bigger than the picture +# and/or overlap it only partially) and the image can be OpenGL style +# border/repeat effects (repeat, mirrored repeat, clamp, fixed +# background colour, background colour from source file). For now it +# only acts as crop. The window must be no greater than the image in +# both x and y. + +def window(tl, br, inp, out): + """Place a window onto the image and cut-out the resulting + rectangle. The window is an axis aligned rectangle opposite corners + at *tl* and *br* (each being an (x,y) pair). *inp* specifies the + input file which should be a PNG image. + """ + + import png + + r = png.Reader(file=inp) + x,y,pixels,meta = r.asDirect() + if not (0 <= tl[0] < br[0] <= x): + raise NotImplementedError() + if not (0 <= tl[1] < br[1] <= y): + raise NotImplementedError() + # Compute left and right bounds for each row + l = tl[0] * meta['planes'] + r = br[0] * meta['planes'] + def itercrop(): + """An iterator to perform the crop.""" + + for i,row in enumerate(pixels): + if i < tl[1]: + continue + if i >= br[1]: + # Same as "raise StopIteration" + return + yield row[l:r] + meta['size'] = (br[0]-tl[0], br[1]-tl[1]) + w = png.Writer(**meta) + w.write(out, itercrop()) + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + argv = argv[1:] + + tl = (0,0) + br = tuple(map(int, argv[:2])) + if len(argv) >= 4: + tl = br + br = tuple(map(int, argv[2:4])) + if len(argv) in (2, 4): + f = sys.stdin + else: + f = open(argv[-1], 'rb') + + return window(tl, br, f, sys.stdout) + +if __name__ == '__main__': + main() diff --git a/build/pypng/plan9topng.py b/build/pypng/plan9topng.py new file mode 100644 index 00000000..4600b4c9 --- /dev/null +++ b/build/pypng/plan9topng.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# $Rev: 184 $ +# $URL: http://pypng.googlecode.com/svn/trunk/code/plan9topng.py $ + +# Imported from //depot/prj/plan9topam/master/code/plan9topam.py#4 on +# 2009-06-15. + +"""Command line tool to convert from Plan 9 image format to PNG format. + +Plan 9 image format description: +http://plan9.bell-labs.com/magic/man2html/6/image +""" + +# http://www.python.org/doc/2.3.5/lib/module-itertools.html +import itertools +# http://www.python.org/doc/2.3.5/lib/module-re.html +import re +# http://www.python.org/doc/2.3.5/lib/module-sys.html +import sys + +def block(s, n): + # See http://www.python.org/doc/2.6.2/library/functions.html#zip + return zip(*[iter(s)]*n) + +def convert(f, output=sys.stdout) : + """Convert Plan 9 file to PNG format. Works with either uncompressed + or compressed files. + """ + + r = f.read(11) + if r == 'compressed\n' : + png(output, *decompress(f)) + else : + png(output, *glue(f, r)) + + +def glue(f, r) : + """Return (metadata, stream) pair where `r` is the initial portion of + the metadata that has already been read from the stream `f`. + """ + + r = r + f.read(60-len(r)) + return (r, f) + +def meta(r) : + """Convert 60 character string `r`, the metadata from an image file. + Returns a 5-tuple (*chan*,*minx*,*miny*,*limx*,*limy*). 5-tuples may + settle into lists in transit. + + As per http://plan9.bell-labs.com/magic/man2html/6/image the metadata + comprises 5 words separated by blanks. As it happens each word starts + at an index that is a multiple of 12, but this routine does not care + about that.""" + + r = r.split() + # :todo: raise FormatError + assert len(r) == 5 + r = [r[0]] + map(int, r[1:]) + return r + +def bitdepthof(pixel) : + """Return the bitdepth for a Plan9 pixel format string.""" + + maxd = 0 + for c in re.findall(r'[a-z]\d*', pixel) : + if c[0] != 'x': + maxd = max(maxd, int(c[1:])) + return maxd + +def maxvalof(pixel): + """Return the netpbm MAXVAL for a Plan9 pixel format string.""" + + bitdepth = bitdepthof(pixel) + return (2**bitdepth)-1 + +def pixmeta(metadata, f) : + """Convert (uncompressed) Plan 9 image file to pair of (*metadata*, + *pixels*). This is intended to be used by PyPNG format. *metadata* + is the metadata returned in a dictionary, *pixels* is an iterator that + yields each row in boxed row flat pixel format. + + `f`, the input file, should be cued up to the start of the image data. + """ + + chan,minx,miny,limx,limy = metadata + rows = limy - miny + width = limx - minx + nchans = len(re.findall('[a-wyz]', chan)) + alpha = 'a' in chan + # Iverson's convention for the win! + ncolour = nchans - alpha + greyscale = ncolour == 1 + bitdepth = bitdepthof(chan) + maxval = 2**bitdepth - 1 + # PNG style metadata + meta=dict(size=(width,rows), bitdepth=bitdepthof(chan), + greyscale=greyscale, alpha=alpha, planes=nchans) + + return itertools.imap(lambda x: itertools.chain(*x), + block(unpack(f, rows, width, chan, maxval), width)), meta + +def png(out, metadata, f): + """Convert to PNG format. `metadata` should be a Plan9 5-tuple; `f` + the input file (see :meth:`pixmeta`). + """ + + import png + + pixels,meta = pixmeta(metadata, f) + p = png.Writer(**meta) + p.write(out, pixels) + +def spam(): + """Not really spam, but old PAM code, which is in limbo.""" + + if nchans == 3 or nchans == 1 : + # PGM (P5) or PPM (P6) format. + output.write('P%d\n%d %d %d\n' % (5+(nchans==3), width, rows, maxval)) + else : + # PAM format. + output.write("""P7 +WIDTH %d +HEIGHT %d +DEPTH %d +MAXVAL %d +""" % (width, rows, nchans, maxval)) + +def unpack(f, rows, width, pixel, maxval) : + """Unpack `f` into pixels. Assumes the pixel format is such that the depth + is either a multiple or a divisor of 8. + `f` is assumed to be an iterator that returns blocks of input such + that each block contains a whole number of pixels. An iterator is + returned that yields each pixel as an n-tuple. `pixel` describes the + pixel format using the Plan9 syntax ("k8", "r8g8b8", and so on). + """ + + def mask(w) : + """An integer, to be used as a mask, with bottom `w` bits set to 1.""" + + return (1 << w)-1 + + def deblock(f, depth, width) : + """A "packer" used to convert multiple bytes into single pixels. + `depth` is the pixel depth in bits (>= 8), `width` is the row width in + pixels. + """ + + w = depth // 8 + i = 0 + for block in f : + for i in range(len(block)//w) : + p = block[w*i:w*(i+1)] + i += w + # Convert p to little-endian integer, x + x = 0 + s = 1 # scale + for j in p : + x += s * ord(j) + s <<= 8 + yield x + + def bitfunge(f, depth, width) : + """A "packer" used to convert single bytes into multiple pixels. + Depth is the pixel depth (< 8), width is the row width in pixels. + """ + + for block in f : + col = 0 + for i in block : + x = ord(i) + for j in range(8/depth) : + yield x >> (8 - depth) + col += 1 + if col == width : + # A row-end forces a new byte even if we haven't consumed + # all of the current byte. Effectively rows are bit-padded + # to make a whole number of bytes. + col = 0 + break + x <<= depth + + # number of bits in each channel + chan = map(int, re.findall(r'\d+', pixel)) + # type of each channel + type = re.findall('[a-z]', pixel) + + depth = sum(chan) + + # According to the value of depth pick a "packer" that either gathers + # multiple bytes into a single pixel (for depth >= 8) or split bytes + # into several pixels (for depth < 8) + if depth >= 8 : + # + assert depth % 8 == 0 + packer = deblock + else : + assert 8 % depth == 0 + packer = bitfunge + + for x in packer(f, depth, width) : + # x is the pixel as an unsigned integer + o = [] + # This is a bit yucky. Extract each channel from the _most_ + # significant part of x. + for j in range(len(chan)) : + v = (x >> (depth - chan[j])) & mask(chan[j]) + x <<= chan[j] + if type[j] != 'x' : + # scale to maxval + v = v * float(maxval) / mask(chan[j]) + v = int(v+0.5) + o.append(v) + yield o + + +def decompress(f) : + """Decompress a Plan 9 image file. Assumes f is already cued past the + initial 'compressed\n' string. + """ + + r = meta(f.read(60)) + return r, decomprest(f, r[4]) + + +def decomprest(f, rows) : + """Iterator that decompresses the rest of a file once the metadata + have been consumed.""" + + row = 0 + while row < rows : + row,o = deblock(f) + yield o + + +def deblock(f) : + """Decompress a single block from a compressed Plan 9 image file. + Each block starts with 2 decimal strings of 12 bytes each. Yields a + sequence of (row, data) pairs where row is the total number of rows + processed according to the file format and data is the decompressed + data for a set of rows.""" + + row = int(f.read(12)) + size = int(f.read(12)) + if not (0 <= size <= 6000) : + raise 'block has invalid size; not a Plan 9 image file?' + + # Since each block is at most 6000 bytes we may as well read it all in + # one go. + d = f.read(size) + i = 0 + o = [] + + while i < size : + x = ord(d[i]) + i += 1 + if x & 0x80 : + x = (x & 0x7f) + 1 + lit = d[i:i+x] + i += x + o.extend(lit) + continue + # x's high-order bit is 0 + l = (x >> 2) + 3 + # Offset is made from bottom 2 bits of x and all 8 bits of next + # byte. http://plan9.bell-labs.com/magic/man2html/6/image doesn't + # say whether x's 2 bits are most signiificant or least significant. + # But it is clear from inspecting a random file, + # http://plan9.bell-labs.com/sources/plan9/sys/games/lib/sokoban/images/cargo.bit + # that x's 2 bit are most significant. + # + offset = (x & 3) << 8 + offset |= ord(d[i]) + i += 1 + # Note: complement operator neatly maps (0 to 1023) to (-1 to + # -1024). Adding len(o) gives a (non-negative) offset into o from + # which to start indexing. + offset = ~offset + len(o) + if offset < 0 : + raise 'byte offset indexes off the begininning of the output buffer; not a Plan 9 image file?' + for j in range(l) : + o.append(o[offset+j]) + return row,''.join(o) + +def main(argv=None) : + if argv is None : + argv = sys.argv + if len(sys.argv) <= 1 : + return convert(sys.stdin) + else : + return convert(open(argv[1], 'rb')) + +if __name__ == '__main__' : + sys.exit(main()) diff --git a/build/pypng/pngchunk b/build/pypng/pngchunk new file mode 100644 index 00000000..b00e4b1d --- /dev/null +++ b/build/pypng/pngchunk @@ -0,0 +1,172 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pngchunk $ +# $Rev: 156 $ +# pngchunk +# Chunk editing/extraction tool. + +import struct +import warnings + +# Local module. +import png + +""" +pngchunk [--gamma g] [--iccprofile file] [--sigbit b] [-c cHNK!] [-c cHNK:foo] [-c cHNKI', v) + elif t == 'sigbit': + t = 'sBIT' + try: + v[0] + except TypeError: + v = (v,) + v = struct.pack('%dB' % len(v), *v) + elif t == 'iccprofile': + t = 'iCCP' + # http://www.w3.org/TR/PNG/#11iCCP + v = 'a color profile\x00\x00' + v.encode('zip') + else: + warnings.warn('Unknown chunk type %r' % t) + return t[:4],v + + l = map(canonical, l) + # Some chunks automagically replace ones that are present in the + # source PNG. There can only be one of each of these chunk types. + # Create a 'replace' dictionary to record these chunks. + add = [] + delete = set() + replacing = set(['gAMA', 'sBIT', 'PLTE', 'tRNS', 'sPLT', 'IHDR']) + replace = dict() + for t,v in l: + if v is None: + delete.add(t) + elif t in replacing: + replace[t] = v + else: + add.append((t,v)) + del l + r = png.Reader(file=inp) + chunks = r.chunks() + def iterchunks(): + for t,v in chunks: + if t in delete: + continue + if t in replace: + yield t,replace[t] + del replace[t] + continue + if t == 'IDAT' and replace: + # Insert into the output any chunks that are on the + # replace list. We haven't output them yet, because we + # didn't see an original chunk of the same type to + # replace. Thus the "replace" is actually an "insert". + for u,w in replace.items(): + yield u,w + del replace[u] + if t == 'IDAT' and add: + for item in add: + yield item + del add[:] + yield t,v + return png.write_chunks(out, iterchunks()) + +class Usage(Exception): + pass + +def main(argv=None): + import getopt + import re + import sys + + if argv is None: + argv = sys.argv + + argv = argv[1:] + + try: + try: + opt,arg = getopt.getopt(argv, 'c:', + ['gamma=', 'iccprofile=', 'sigbit=']) + except getopt.error, msg: + raise Usage(msg) + k = [] + for o,v in opt: + if o in ['--gamma']: + k.append(('gamma', float(v))) + if o in ['--sigbit']: + k.append(('sigbit', int(v))) + if o in ['--iccprofile']: + k.append(('iccprofile', open(v, 'rb').read())) + if o in ['-c']: + type = v[:4] + if not re.match('[a-zA-Z]{4}', type): + raise Usage('Chunk type must consist of 4 letters.') + if v[4] == '!': + k.append((type, None)) + if v[4] == ':': + k.append((type, v[5:])) + if v[4] == '<': + k.append((type, open(v[5:], 'rb').read())) + except Usage, err: + print >>sys.stderr, ( + "usage: pngchunk [--gamma d.dd] [--sigbit b] [-c cHNK! | -c cHNK:text-string]") + print >>sys.stderr, err.message + return 2 + + if len(arg) > 0: + f = open(arg[0], 'rb') + else: + f = sys.stdin + return chunk(sys.stdout, f, k) + + +if __name__ == '__main__': + main() diff --git a/build/pypng/pnghist b/build/pypng/pnghist new file mode 100644 index 00000000..4fbbd0a6 --- /dev/null +++ b/build/pypng/pnghist @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pnghist $ +# $Rev: 153 $ +# PNG Histogram +# Only really works on grayscale images. + +from array import array +import getopt + +import png + +def decidemax(level): + """Given an array of levels, decide the maximum value to use for the + histogram. This is normally chosen to be a bit bigger than the 99th + percentile, but if the 100th percentile is not much more (within a + factor of 2) then the 100th percentile is chosen. + """ + + truemax = max(level) + sl = level[:] + sl.sort(reverse=True) + i99 = int(round(len(level)*0.01)) + if truemax <= 2*sl[i99]: + return truemax + return 1.05*sl[i99] + +def hist(out, inp, verbose=None): + """Open the PNG file `inp` and generate a histogram.""" + + r = png.Reader(file=inp) + x,y,pixels,info = r.asDirect() + bitdepth = info['bitdepth'] + level = [0]*2**bitdepth + for row in pixels: + for v in row: + level[v] += 1 + maxlevel = decidemax(level) + + h = 100 + outbitdepth = 8 + outmaxval = 2**outbitdepth - 1 + def genrow(): + for y in range(h): + y = h-y-1 + # :todo: vary typecode according to outbitdepth + row = array('B', [0]*len(level)) + fl = y*maxlevel/float(h) + ce = (y+1)*maxlevel/float(h) + for x in range(len(row)): + if level[x] <= fl: + # Relies on row being initialised to all 0 + continue + if level[x] >= ce: + row[x] = outmaxval + continue + frac = (level[x] - fl)/(ce - fl) + row[x] = int(round(outmaxval*frac)) + yield row + w = png.Writer(len(level), h, gamma=1.0, + greyscale=True, alpha=False, bitdepth=outbitdepth) + w.write(out, genrow()) + if verbose: print >>verbose, level + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + argv = argv[1:] + opt,arg = getopt.getopt(argv, '') + + if len(arg) < 1: + f = sys.stdin + else: + f = open(arg[0]) + hist(sys.stdout, f) + +if __name__ == '__main__': + main() diff --git a/build/pypng/pnglsch b/build/pypng/pnglsch new file mode 100644 index 00000000..d10d2380 --- /dev/null +++ b/build/pypng/pnglsch @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/pnglsch $ +# $Rev: 107 $ +# pnglsch +# PNG List Chunks + +import png + +def list(out, inp): + r = png.Reader(file=inp) + for t,v in r.chunks(): + add = '' + if len(v) <= 28: + add = ' ' + v.encode('hex') + print >>out, "%s %10d%s" % (t, len(v), add) + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + arg = argv[1:] + + if len(arg) > 0: + f = open(arg[0], 'rb') + else: + f = sys.stdin + return list(sys.stdout, f) + +if __name__ == '__main__': + main() diff --git a/build/pypng/texttopng b/build/pypng/texttopng new file mode 100644 index 00000000..ab0c6900 --- /dev/null +++ b/build/pypng/texttopng @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# $URL: http://pypng.googlecode.com/svn/trunk/code/texttopng $ +# $Rev: 132 $ +# Script to renders text as a PNG image. + +from array import array +import itertools + +font = { + 32: '0000000000000000', + 33: '0010101010001000', + 34: '0028280000000000', + 35: '0000287c287c2800', + 36: '00103c5038147810', + 37: '0000644810244c00', + 38: '0020502054483400', + 39: '0010100000000000', + 40: '0008101010101008', + 41: '0020101010101020', + 42: '0010543838541000', + 43: '000010107c101000', + 44: '0000000000301020', + 45: '000000007c000000', + 46: '0000000000303000', + 47: '0000040810204000', + 48: '0038445454443800', + 49: '0008180808080800', + 50: '0038043840407c00', + 51: '003c041804043800', + 52: '00081828487c0800', + 53: '0078407804047800', + 54: '0038407844443800', + 55: '007c040810101000', + 56: '0038443844443800', + 57: '0038443c04040400', + 58: '0000303000303000', + 59: '0000303000301020', + 60: '0004081020100804', + 61: '0000007c007c0000', + 62: '0040201008102040', + 63: '0038440810001000', + 64: '00384c545c403800', + 65: '0038447c44444400', + 66: '0078447844447800', + 67: '0038444040443800', + 68: '0070484444487000', + 69: '007c407840407c00', + 70: '007c407840404000', + 71: '003844405c443c00', + 72: '0044447c44444400', + 73: '0038101010103800', + 74: '003c040404443800', + 75: '0044487048444400', + 76: '0040404040407c00', + 77: '006c545444444400', + 78: '004464544c444400', + 79: '0038444444443800', + 80: '0078447840404000', + 81: '0038444444443c02', + 82: '0078447844444400', + 83: '0038403804047800', + 84: '007c101010101000', + 85: '0044444444443c00', + 86: '0044444444281000', + 87: '0044445454543800', + 88: '0042241818244200', + 89: '0044443810101000', + 90: '007c081020407c00', + 91: '0038202020202038', + 92: '0000402010080400', + 93: '0038080808080838', + 94: '0010284400000000', + 95: '000000000000fe00', + 96: '0040200000000000', + 97: '000038043c443c00', + 98: '0040784444447800', + 99: '0000384040403800', + 100: '00043c4444443c00', + 101: '000038447c403c00', + 102: '0018203820202000', + 103: '00003c44443c0438', + 104: '0040784444444400', + 105: '0010003010101000', + 106: '0010003010101020', + 107: '0040404870484400', + 108: '0030101010101000', + 109: '0000385454444400', + 110: '0000784444444400', + 111: '0000384444443800', + 112: '0000784444784040', + 113: '00003c44443c0406', + 114: '00001c2020202000', + 115: '00003c4038047800', + 116: '0020203820201800', + 117: '0000444444443c00', + 118: '0000444444281000', + 119: '0000444454543800', + 120: '0000442810284400', + 121: '00004444443c0438', + 122: '00007c0810207c00', + 123: '0018202060202018', + 124: '0010101000101010', + 125: '003008080c080830', + 126: '0020540800000000', +} + +def char(i): + """Get image data for the character `i` (a one character string). + Returned as a list of rows. Each row is a tuple containing the + packed pixels. + """ + + i = ord(i) + if i not in font: + return [(0,)]*8 + return map(lambda row: (ord(row),), font[i].decode('hex')) + +def texttoraster(m): + """Convert string *m* to a raster image (by rendering it using the + font in *font*). A triple of (*width*, *height*, *pixels*) is + returned; *pixels* is in boxed row packed pixel format. + """ + + # Assumes monospaced font. + x = 8*len(m) + y = 8 + return x,y,itertools.imap(lambda row: itertools.chain(*row), + zip(*map(char, m))) + + +def render(message, out): + import png + + x,y,pixels = texttoraster(message) + w = png.Writer(x, y, greyscale=True, bitdepth=1) + w.write_packed(out, pixels) + out.flush() + +def main(argv=None): + import sys + + if argv is None: + argv = sys.argv + if len(argv) > 1: + message = argv[1] + else: + message = sys.stdin.read() + render(message, sys.stdout) + +if __name__ == '__main__': + main() diff --git a/build/unix/mozconfig.gtk b/build/unix/mozconfig.gtk new file mode 100644 index 00000000..6f535f8a --- /dev/null +++ b/build/unix/mozconfig.gtk @@ -0,0 +1,28 @@ +# To do try builds with Gtk+2, uncomment the following line, and remove +# everything after that. +#ac_add_options --enable-default-toolkit=cairo-gtk2 + +TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} + +# $TOOLTOOL_DIR/gtk3 comes from tooltool, and must be included in the tooltool manifest. +if [ -z "$PKG_CONFIG_LIBDIR" ]; then + echo PKG_CONFIG_LIBDIR must be set >&2 + exit 1 +fi +export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3" +export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig" +PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config" +export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}" +# Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages. +LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}" +ac_add_options --enable-default-toolkit=cairo-gtk3 + +# Set things up to use Gtk+3 from the tooltool package +mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts" +mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc" +mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib" +mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" +mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders" + +LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/gtk3/usr/local/lib +mk_add_options "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" diff --git a/build/unix/mozconfig.linux b/build/unix/mozconfig.linux new file mode 100644 index 00000000..f63f406e --- /dev/null +++ b/build/unix/mozconfig.linux @@ -0,0 +1,38 @@ +if [ "x$IS_NIGHTLY" = "xyes" ]; then + # Some nightlies (eg: Mulet) don't want these set. + MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1} + MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1} + MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1} +fi + +. "$topsrcdir/build/mozconfig.common" + +TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} + +# some b2g desktop builds still happen on i686 machines, and the tooltool +# toolchain is x86_64 only. +# We also deal with valgrind builds here, they don't use tooltool manifests at +# all yet. +if [ -z "$no_tooltool" ] +then + CC="$TOOLTOOL_DIR/gcc/bin/gcc" + CXX="$TOOLTOOL_DIR/gcc/bin/g++" + + # We want to make sure we use binutils and other binaries in the tooltool + # package. + mk_add_options PATH="$TOOLTOOL_DIR/gcc/bin:$PATH" +else + CC="/tools/gcc-4.7.3-0moz1/bin/gcc" + CXX="/tools/gcc-4.7.3-0moz1/bin/g++" +fi + +ac_add_options --enable-elf-hack + +. "$topsrcdir/build/unix/mozconfig.stdcxx" + +# PKG_CONFIG_LIBDIR is appropriately overridden in mozconfig.linux32 +export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig + +export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token + +. "$topsrcdir/build/unix/mozconfig.gtk" diff --git a/build/unix/mozconfig.linux32 b/build/unix/mozconfig.linux32 new file mode 100644 index 00000000..30976775 --- /dev/null +++ b/build/unix/mozconfig.linux32 @@ -0,0 +1,12 @@ +. "$topsrcdir/build/unix/mozconfig.linux" + +export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig + +if test `uname -m` = "x86_64"; then + # -march=pentiumpro is what our 32-bit native toolchain defaults to + CC="$CC -m32 -march=pentiumpro" + CXX="$CXX -m32 -march=pentiumpro" + ac_add_options --target=i686-pc-linux + ac_add_options --host=i686-pc-linux + ac_add_options --x-libraries=/usr/lib +fi diff --git a/build/unix/mozconfig.stdcxx b/build/unix/mozconfig.stdcxx new file mode 100644 index 00000000..787e9b44 --- /dev/null +++ b/build/unix/mozconfig.stdcxx @@ -0,0 +1,15 @@ +# Avoid dependency on libstdc++ 4.7 +ac_add_options --enable-stdcxx-compat + +TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} + +if [ -f "$TOOLTOOL_DIR/clang/lib/libstdc++.so" ]; then + LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/clang/lib +elif [ -f "$TOOLTOOL_DIR/gcc/lib/libstdc++.so" ]; then + # We put both 32-bits and 64-bits library path in LD_LIBRARY_PATH: ld.so + # will prefer the files in the 32-bits path when loading 32-bits executables, + # and the files in the 64-bits path when loading 64-bits executables. + LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/gcc/lib64:$TOOLTOOL_DIR/gcc/lib +fi + +mk_add_options "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" diff --git a/build/win32/mozconfig.vs-latest b/build/win32/mozconfig.vs-latest new file mode 100644 index 00000000..9c8726a8 --- /dev/null +++ b/build/win32/mozconfig.vs-latest @@ -0,0 +1 @@ +. $topsrcdir/build/win32/mozconfig.vs2015-win64 diff --git a/build/win32/mozconfig.vs2015-win64 b/build/win32/mozconfig.vs2015-win64 new file mode 100644 index 00000000..b81afa68 --- /dev/null +++ b/build/win32/mozconfig.vs2015-win64 @@ -0,0 +1,25 @@ +if [ -z "${VSPATH}" ]; then + TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} + VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3" +fi + +VSWINPATH="$(cd ${VSPATH} && pwd -W)" + +export WINDOWSSDKDIR="${VSWINPATH}/SDK" +export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT" +export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86" + +export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}" +export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}" + +export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include" +export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib" + +. $topsrcdir/build/mozconfig.vs-common + +mk_export_correct_style WINDOWSSDKDIR +mk_export_correct_style INCLUDE +mk_export_correct_style LIB +mk_export_correct_style PATH +mk_export_correct_style WIN32_REDIST_DIR +mk_export_correct_style WIN_UCRT_REDIST_DIR diff --git a/build/win64/mozconfig.vs-latest b/build/win64/mozconfig.vs-latest new file mode 100644 index 00000000..3470d4ac --- /dev/null +++ b/build/win64/mozconfig.vs-latest @@ -0,0 +1 @@ +. $topsrcdir/build/win64/mozconfig.vs2015 diff --git a/build/win64/mozconfig.vs2015 b/build/win64/mozconfig.vs2015 new file mode 100644 index 00000000..e81a0006 --- /dev/null +++ b/build/win64/mozconfig.vs2015 @@ -0,0 +1,24 @@ +if [ -z "${VSPATH}" ]; then + TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} + VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3" +fi + +VSWINPATH="$(cd ${VSPATH} && pwd -W)" + +export WINDOWSSDKDIR="${VSWINPATH}/SDK" +export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT +export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64" + +export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}" + +export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include" +export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64:${VSPATH}/DIA SDK/lib/amd64" + +. $topsrcdir/build/mozconfig.vs-common + +mk_export_correct_style WINDOWSSDKDIR +mk_export_correct_style INCLUDE +mk_export_correct_style LIB +mk_export_correct_style PATH +mk_export_correct_style WIN32_REDIST_DIR +mk_export_correct_style WIN_UCRT_REDIST_DIR diff --git a/calendar/.eslintrc.js b/calendar/.eslintrc.js new file mode 100644 index 00000000..fec9091e --- /dev/null +++ b/calendar/.eslintrc.js @@ -0,0 +1,474 @@ +"use strict"; + +module.exports = { + "extends": [ + "../mozilla/toolkit/.eslintrc.js" + ], + "rules": { + // Enforce one true brace style (opening brace on the same line) + // Allow single line (for now) because of the vast number of changes needed + "brace-style": [2, "1tbs", { allowSingleLine: true }], + + // Enforce newline at the end of file, with no multiple empty lines. + "eol-last": 2, + + // Disallow using variables outside the blocks they are defined + "block-scoped-var": 2, + + // Allow trailing commas for easy list extension. Having them does not + // impair readability, but also not required either. + "comma-dangle": 0, + + // Enforce spacing before and after comma + "comma-spacing": [2, { before: false, after: true }], + + // Enforce one true comma style. + "comma-style": [2, "last"], + + // Enforce curly brace conventions for all control statements. + "curly": 2, + + // Enforce the spacing around the * in generator functions. + "generator-star-spacing": [2, "after"], + + // Require space before/after arrow function's arrow + "arrow-spacing": [2, { before: true, after: true }], + + // Enforces spacing between keys and values in object literal properties. + "key-spacing": [2, { beforeColon: false, afterColon: true, mode: "minimum" }], + + // Disallow the omission of parentheses when invoking a constructor with no + // arguments. + "new-parens": 2, + + // Disallow use of the Array constructor. + "no-array-constructor": 2, + + // disallow use of the Object constructor + "no-new-object": 2, + + // Disallow Primitive Wrapper Instances + "no-new-wrappers": 2, + + // Disallow the catch clause parameter name being the same as a variable in + // the outer scope, to avoid confusion. + "no-catch-shadow": 2, + + // Disallow assignment in conditional expressions. + "no-cond-assign": 2, + + // Disallow use of debugger. + "no-debugger": 2, + + // Disallow deletion of variables (deleting properties is fine). + "no-delete-var": 2, + + // Disallow duplicate arguments in functions. + "no-dupe-args": 2, + + // Disallow duplicate keys when creating object literals. + "no-dupe-keys": 2, + + // Disallow a duplicate case label. + "no-duplicate-case": 2, + + // Disallow the use of empty character classes in regular expressions. + "no-empty-character-class": 2, + + // Disallow assigning to the exception in a catch block. + "no-ex-assign": 2, + + // Disallow adding to native types + "no-extend-native": 2, + + // Disallow double-negation boolean casts in a boolean context. + "no-extra-boolean-cast": 2, + + // Disallow unnecessary semicolons. + "no-extra-semi": 2, + + // Disallow mixed spaces and tabs for indentation. + "no-mixed-spaces-and-tabs": 2, + + // Disallow reassignments of native objects. + "no-native-reassign": 2, + + // Disallow nested ternary expressions, they make the code hard to read. + "no-nested-ternary": 2, + + // Disallow use of octal literals. + "no-octal": 2, + + // Disallow comparisons where both sides are exactly the same. + "no-self-compare": 2, + + // Disallow sparse arrays, eg. let arr = [,,2]. + // Array destructuring is fine though: + // for (let [, breakpointPromise] of aPromises) + "no-sparse-arrays": 2, + + // Disallow trailing whitespace at the end of lines. + "no-trailing-spaces": 2, + + // Disallow use of the with statement. + "no-with": 2, + + // Disallow comparisons with the value NaN. + "use-isnan": 2, + + // Ensure that the results of typeof are compared against a valid string. + "valid-typeof": 2, + + // disallow the use of object properties of the global object (Math and + // JSON) as functions + "no-obj-calls": 2, + + // disallow use of octal escape sequences in string literals, such as + // var foo = "Copyright \251"; + "no-octal-escape": 2, + + // disallow use of void operator + "no-void": 2, + + // Disallow Yoda conditions (where literal value comes first). + "yoda": 2, + + // Require a space immediately following the // in a line comment. + "spaced-comment": [2, "always"], + + // Require use of the second argument for parseInt(). + "radix": 2, + + // Require spaces before/after unary operators (words on by default, + // nonwords off by default). + "space-unary-ops": [2, { words: true, nonwords: false }], + + // Enforce spacing after semicolons. + "semi-spacing": [2, { before: false, after: true }], + + // Disallow the use of Boolean literals in conditional expressions. + "no-unneeded-ternary": 2, + + // Disallow use of multiple spaces (sometimes used to align const values, + // array or object items, etc.). It's hard to maintain and doesn't add that + // much benefit. + "no-multi-spaces": 2, + + // Require spaces around operators, except for a|0. + // Disabled for now given eslint doesn't support default args yet + // "space-infix-ops": [2, { "int32Hint": true }], + + // Require a space around all keywords. + "keyword-spacing": 2, + + // Disallow space between function identifier and application. + "no-spaced-func": 2, + + // Disallow shadowing of names such as arguments. + "no-shadow-restricted-names": 2, + + // Disallow use of comma operator. + "no-sequences": 2, + + // Disallow use of assignment in return statement. It is preferable for a + // single line of code to have only one easily predictable effect. + "no-return-assign": 2, + + // Require return statements to either always or never specify values + "consistent-return": 2, + + // Disallow padding within blocks. + "padded-blocks": [2, "never"], + + // Disallow spaces inside parentheses. + "space-in-parens": [2, "never"], + + // Require space after keyword for anonymous functions, but disallow space + // after name of named functions. + "space-before-function-paren": [2, { anonymous: "never", named: "never" }], + + // Disallow unreachable statements after a return, throw, continue, or break + // statement. + "no-unreachable": 2, + + // Always require use of semicolons wherever they are valid. + "semi": [2, "always"], + + // Disallow empty statements. This will report an error for: + // try { something(); } catch (e) {} + // but will not report it for: + // try { something(); } catch (e) { /* Silencing the error because ...*/ } + // which is a valid use case. + "no-empty": 2, + + // Disallow declaring the same variable more than once (we use let anyway). + "no-redeclare": 2, + + // Warn about declaration of variables already declared in the outer scope. + // This isn't an error because it sometimes is useful to use the same name + // in a small helper function rather than having to come up with another + // random name. Still, making this a warning can help people avoid being + // confused. + "no-shadow": 2, + + // We use var-only-at-top-level instead of no-var as we allow top level + // vars. + "no-var": 0, + "mozilla/var-only-at-top-level": 1, + + // Disallow global and local variables that aren't used, but allow unused function arguments. + "no-unused-vars": [2, { vars: "all", args: "none", varsIgnorePattern: "EXPORTED_SYMBOLS" }], + + "mozilla/mark-test-function-used": 1, + + // Require padding inside curly braces + "object-curly-spacing": [2, "always"], + + // Disallow spaces inside of brackets + "array-bracket-spacing": [2, "never"], + + // Disallow control characters in regular expressions + "no-control-regex": 2, + + // Disallow invalid regular expression strings in RegExp constructors + "no-invalid-regexp": 2, + + // Disallow multiple spaces in regular expression literals + "no-regex-spaces": 2, + + // Disallow irregular whitespace + "no-irregular-whitespace": 2, + + // Disallow negating the left operand in `in` expressions + "no-negated-in-lhs": 2, + + // Allow constant expressions in conditions + // With 2.11.0 we can enable this with checkLoops: false + "no-constant-condition": [2, { checkLoops: false }], + + // Disallow Regexs That Look Like Division + "no-div-regex": 2, + + // Disallow Iterator (using __iterator__) + "no-iterator": 2, + + // Enforce consistent linebreak style + "linebreak-style": [2, "unix"], + + // Enforces return statements in callbacks of array's methods + "array-callback-return": 2, + + // Verify super() calls in constructors + "constructor-super": 2, + + // Disallow modifying variables of class declarations + "no-class-assign": 2, + + // Disallow modifying variables that are declared using const + "no-const-assign": 2, + + // Disallow duplicate name in class members + "no-dupe-class-members": 2, + + // Disallow use of this/super before calling super() in constructors + "no-this-before-super": 2, + + // Disallow duplicate imports + "no-duplicate-imports": 2, + + // Disallow empty destructuring patterns + "no-empty-pattern": 2, + + // Disallow Labeled Statements + "no-labels": 2, + + // Disallow Multiline Strings + "no-multi-str": 2, + + // Disallow Symbol Constructor + "no-new-symbol": 2, + + // Disallow Initializing to undefined + "no-undef-init": 2, + + // Disallow control flow statements in finally blocks + "no-unsafe-finally": 2, + + // Disallow Unused Labels + "no-unused-labels": 2, + + // Disallow unnecessary computed property keys on objects + "no-useless-computed-key": 2, + + // Disallow unnecessary constructor + "no-useless-constructor": 2, + + // Disallow renaming import, export, and destructured assignments to the + // same name + "no-useless-rename": 2, + + // Enforce spacing between rest and spread operators and their expressions + "rest-spread-spacing": [2, "never"], + + // Disallow usage of spacing in template string expressions + "template-curly-spacing": [2, "never"], + + // Disallow the Unicode Byte Order Mark + "unicode-bom": [2, "never"], + + // Enforce spacing around the * in yield* expressions + "yield-star-spacing": [2, "after"], + + // Disallow Implied eval + "no-implied-eval": 2, + + // Disallow unnecessary function binding + "no-extra-bind": 2, + + // Disallow new For Side Effects + "no-new": 2, + + // Disallow Self Assignment + "no-self-assign": 2, + + // Disallow confusing multiline expressions + "no-unexpected-multiline": 2, + + // Require IIFEs to be Wrapped + "wrap-iife": [2, "inside"], + + // Disallow Unused Expressions + "no-unused-expressions": 2, + + // Disallow function or var declarations in nested blocks + "no-inner-declarations": 2, + + // Enforce newline before and after dot + "dot-location": [2, "property"], + + // Disallow Use of caller/callee + "no-caller": 2, + + // Disallow Case Statement Fallthrough + "no-fallthrough": 2, + + // Disallow Floating Decimals + "no-floating-decimal": 2, + + // Require Space Before Blocks + "space-before-blocks": 2, + + // Operators always before the line break + "operator-linebreak": [2, "after", { overrides: { ":": "before", "?": "ignore" } }], + + // Restricts the use of parentheses to only where they are necessary + // Disabled for now since this also removes parens around assignments, e.g. let foo = bar == baz + // "no-extra-parens": [2, "all", { "conditionalAssign": false, "returnAssign": false, "nestedBinaryExpressions": false }], + + // Double quotes should be used. + "quotes": [2, "double", { avoidEscape: true }], + + // Disallow if as the only statement in an else block. + "no-lonely-if": 2, + + // Not more than two empty lines with in the file, and no extra lines at + // beginning or end of file. + "no-multiple-empty-lines": [2, { max: 2, maxEOF: 0, maxBOF: 0 }], + + // Make sure all setters have a corresponding getter + "accessor-pairs": 2, + + // Enforce spaces inside of single line blocks + "block-spacing": [2, "always"], + + // Disallow spaces inside of computed properties + "computed-property-spacing": [2, "never"], + + // Require consistent this (using |self|) + "consistent-this": [2, "self"], + + // Disallow unnecessary .call() and .apply() + "no-useless-call": 2, + + // Require dot notation when accessing properties + "dot-notation": 2, + + // Disallow named function expressions + "func-names": [2, "never"], + + // Enforce placing object properties on separate lines + "object-property-newline": [2, { allowMultiplePropertiesPerLine: true }], + + // Enforce consistent line breaks inside braces + "object-curly-newline": [2, { multiline: true }], + + // Disallow whitespace before properties + "no-whitespace-before-property": 2, + + // Disallow unnecessary escape usage + "no-useless-escape": 2, + + // Disallow mixes of different operators, but allow simple math operations. + "no-mixed-operators": [2, { + groups: [ + /* ["+", "-", "*", "/", "%", "**"], */ + ["&", "|", "^", "~", "<<", ">>", ">>>"], + ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + ["&&", "||"], + ["in", "instanceof"] + ] + }], + + // Disallow unnecessary concatenation of strings + "no-useless-concat": 2, + + // Disallow unmodified conditions of loops + "no-unmodified-loop-condition": 2, + + // Suggest using arrow functions as callbacks + "prefer-arrow-callback": [2, { allowNamedFunctions: true }], + + // Suggest using the spread operator instead of .apply() + "prefer-spread": 2, + + // Quoting style for property names + "quote-props": [2, "consistent-as-needed", { keywords: true }], + + // Disallow negated conditions + "no-negated-condition": 2, + + // Enforce a maximum number of statements allowed per line + "max-statements-per-line": [2, { max: 2 }], + + // Disallow arrow functions where they could be confused with comparisons + "no-confusing-arrow": 2, + + // Disallow Unnecessary Nested Blocks + "no-lone-blocks": 2, + + // Enforce minimum identifier length + "id-length": [2, { + min: 3, + exceptions: [ + /* sorting */ "a", "b", + /* exceptions */ "e", "ex", + /* loop indices */ "i", "j", "k", "n", + /* coordinates */ "x", "y", + /* regexes */ "re", + /* known words */ "rc", "rv", "id", "OS", "os", "db", + /* mail/calendar words */ "to", "cc", + /* Components */ "Ci", "Cc", "Cu", "Cr", + ] + }], + + // Disallow lexical declarations in case/default clauses + "no-case-declarations": 2, + + // Enforce consistent indentation (4-space) + "indent": [2, 4, { SwitchCase: 1 }], + + // The following rules will not be enabled currently, but are kept here for + // easier updates in the future. + "no-else-return": 0, + } +}; diff --git a/calendar/base/backend/calBackendLoader.js b/calendar/base/backend/calBackendLoader.js new file mode 100644 index 00000000..449f8a2f --- /dev/null +++ b/calendar/base/backend/calBackendLoader.js @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function calBackendLoader() { + this.wrappedJSObject = this; + try { + this.loadBackend(); + } catch (e) { + dump("### Error loading backend: " + e + "\n"); + } +} + +var calBackendLoaderClassID = Components.ID("{0314c271-7168-40fa-802e-83c8c46a557e}"); +var calBackendLoaderInterfaces = [Components.interfaces.nsIObserver]; +calBackendLoader.prototype = { + classID: calBackendLoaderClassID, + QueryInterface: XPCOMUtils.generateQI(calBackendLoaderInterfaces), + classInfo: XPCOMUtils.generateCI({ + classID: calBackendLoaderClassID, + contractID: "@mozilla.org/calendar/backend-loader;1", + classDescription: "Calendar Backend Loader", + interfaces: calBackendLoaderInterfaces, + flags: Components.interfaces.nsIClassInfo.SINGLETON + }), + + loaded: false, + + observe: function() { + // Nothing to do here, just need the entry so this is instanciated + }, + + loadBackend: function() { + if (this.loaded) { + return; + } + + if (Services.prefs.getBoolPref("calendar.icaljs")) { + let contracts = [ + "@mozilla.org/calendar/datetime;1", + "@mozilla.org/calendar/duration;1", + "@mozilla.org/calendar/ics-service;1", + "@mozilla.org/calendar/period;1", + "@mozilla.org/calendar/recurrence-rule;1" + ]; + + // Unregister libical components + let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + for (let contractId of contracts) { + let classobj = Components.classes[contractId]; + let factory = Components.manager.getClassObject(classobj, Components.interfaces.nsIFactory); + let classId = registrar.contractIDToCID(contractId); + registrar.unregisterFactory(classId, factory); + } + + // Now load ical.js backend + let uri = Services.io.getProtocolHandler("resource") + .QueryInterface(Components.interfaces.nsIResProtocolHandler) + .getSubstitution("calendar"); + + let file = Services.io.getProtocolHandler("file") + .QueryInterface(Components.interfaces.nsIFileProtocolHandler) + .getFileFromURLSpec(uri.spec); + file.append("components"); + file.append("icaljs-manifest"); + + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar) + .autoRegister(file); + dump("[calBackendLoader] Using icaljs backend at " + file.path + "\n"); + } else { + dump("[calBackendLoader] Using Thunderbird's builtin libical backend\n"); + } + + this.loaded = true; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([calBackendLoader]); diff --git a/calendar/base/backend/calBackendLoader.manifest b/calendar/base/backend/calBackendLoader.manifest new file mode 100644 index 00000000..a66a25e8 --- /dev/null +++ b/calendar/base/backend/calBackendLoader.manifest @@ -0,0 +1,3 @@ +component {0314c271-7168-40fa-802e-83c8c46a557e} calBackendLoader.js +contract @mozilla.org/calendar/backend-loader;1 {0314c271-7168-40fa-802e-83c8c46a557e} +category profile-after-change calendar-backend-loader @mozilla.org/calendar/backend-loader;1 diff --git a/calendar/base/backend/icaljs/calDateTime.js b/calendar/base/backend/icaljs/calDateTime.js new file mode 100644 index 00000000..0a315f33 --- /dev/null +++ b/calendar/base/backend/icaljs/calDateTime.js @@ -0,0 +1,136 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://calendar/modules/ical.js"); +Components.utils.import("resource://calendar/modules/calUtils.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var UNIX_TIME_TO_PRTIME = 1000000; + +function calDateTime(innerObject) { + this.wrappedJSObject = this; + this.innerObject = innerObject || ICAL.Time.epochTime.clone(); +} + +var calDateTimeInterfaces = [Components.interfaces.calIDateTime]; +var calDateTimeClassID = Components.ID("{36783242-ec94-4d8a-9248-d2679edd55b9}"); +calDateTime.prototype = { + QueryInterface: XPCOMUtils.generateQI(calDateTimeInterfaces), + classID: calDateTimeClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/datetime;1", + classDescription: "Describes a Date/Time Object", + classID: calDateTimeClassID, + interfaces: calDateTimeInterfaces + }), + + isMutable: true, + makeImmutable: function() { this.isMutable = false; }, + clone: function() { return new calDateTime(this.innerObject.clone()); }, + + isValid: true, + innerObject: null, + + get nativeTime() { return this.innerObject.toUnixTime() * UNIX_TIME_TO_PRTIME; }, + set nativeTime(val) { this.innerObject.fromUnixTime(val / UNIX_TIME_TO_PRTIME); }, + + get year() { return this.innerObject.year; }, + set year(val) { this.innerObject.year = val; }, + + get month() { return this.innerObject.month - 1; }, + set month(val) { this.innerObject.month = val + 1; }, + + get day() { return this.innerObject.day; }, + set day(val) { this.innerObject.day = val; }, + + get hour() { return this.innerObject.hour; }, + set hour(val) { this.innerObject.hour = val; }, + + get minute() { return this.innerObject.minute; }, + set minute(val) { this.innerObject.minute = val; }, + + get second() { return this.innerObject.second; }, + set second(val) { this.innerObject.second = val; }, + + get timezone() { return new calICALJSTimezone(this.innerObject.zone); }, + set timezone(rawval) { + unwrapSetter(ICAL.Timezone, rawval, function(val) { + this.innerObject.zone = val; + return val; + }, this); + }, + + resetTo: function(year, month, day, hour, minute, second, timezone) { + this.innerObject.fromData({ + year: year, + month: month + 1, + day: day, + hour: hour, + minute: minute, + second: second, + }); + this.timezone = timezone; + }, + + reset: function() { this.innerObject.reset(); }, + + get timezoneOffset() { return this.innerObject.utcOffset(); }, + get isDate() { return this.innerObject.isDate; }, + set isDate(val) { this.innerObject.isDate = val; }, + + get weekday() { return this.innerObject.dayOfWeek() - 1; }, + get yearday() { return this.innerObject.dayOfYear(); }, + + toString: function() { return this.innerObject.toString(); }, + + getInTimezone: unwrap(ICAL.Timezone, function(val) { + return new calDateTime(this.innerObject.convertToZone(val)); + }), + + addDuration: unwrap(ICAL.Duration, function(val) { + this.innerObject.addDuration(val); + }), + + subtractDate: unwrap(ICAL.Time, function(val) { + return new calDuration(this.innerObject.subtractDateTz(val)); + }), + + compare: unwrap(ICAL.Time, function(val) { + let a = this.innerObject; + let b = val; + + // If either this or aOther is floating, both objects are treated + // as floating for the comparison. + if (a.zone == ICAL.Timezone.localTimezone || b.zone == ICAL.Timezone.localTimezone) { + a = a.convertToZone(ICAL.Timezone.localTimezone); + b = b.convertToZone(ICAL.Timezone.localTimezone); + } + + if (a.isDate || b.isDate) { + // Lightning expects 20120101 and 20120101T010101 to be equal + return a.compareDateOnlyTz(b, a.zone); + } else { + // If both are dates or date-times, then just do the normal compare + return a.compare(b); + } + }), + + get startOfWeek() { return new calDateTime(this.innerObject.startOfWeek()); }, + get endOfWeek() { return new calDateTime(this.innerObject.endOfWeek()); }, + get startOfMonth() { return new calDateTime(this.innerObject.startOfMonth()); }, + get endOfMonth() { return new calDateTime(this.innerObject.endOfMonth()); }, + get startOfYear() { return new calDateTime(this.innerObject.startOfYear()); }, + get endOfYear() { return new calDateTime(this.innerObject.endOfYear()); }, + + get icalString() { return this.innerObject.toICALString(); }, + set icalString(val) { + let jcalString; + if (val.length > 10) { + jcalString = ICAL.design.icalendar.value["date-time"].fromICAL(val); + } else { + jcalString = ICAL.design.icalendar.value.date.fromICAL(val); + } + this.innerObject = ICAL.Time.fromString(jcalString); + } +}; diff --git a/calendar/base/backend/icaljs/calDuration.js b/calendar/base/backend/icaljs/calDuration.js new file mode 100644 index 00000000..510dfcfc --- /dev/null +++ b/calendar/base/backend/icaljs/calDuration.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://calendar/modules/ical.js"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function calDuration(innerObject) { + this.innerObject = innerObject || new ICAL.Duration(); + this.wrappedJSObject = this; +} + +var calDurationInterfaces = [Components.interfaces.calIDuration]; +var calDurationClassID = Components.ID("{7436f480-c6fc-4085-9655-330b1ee22288}"); +calDuration.prototype = { + QueryInterface: XPCOMUtils.generateQI(calDurationInterfaces), + classID: calDurationClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/duration;1", + classDescription: "Calendar Duration Object", + classID: calDurationClassID, + interfaces: calDurationInterfaces + }), + + get icalDuration() { return this.innerObject; }, + set icalDuration(val) { this.innerObject = val; }, + + isMutable: true, + makeImmutable: function() { this.isMutable = false; }, + clone: function() { return new calDuration(this.innerObject.clone()); }, + + get isNegative() { return this.innerObject.isNegative; }, + set isNegative(val) { this.innerObject.isNegative = val; }, + + get weeks() { return this.innerObject.weeks; }, + set weeks(val) { this.innerObject.weeks = val; }, + + get days() { return this.innerObject.days; }, + set days(val) { this.innerObject.days = val; }, + + get hours() { return this.innerObject.hours; }, + set hours(val) { this.innerObject.hours = val; }, + + get minutes() { return this.innerObject.minutes; }, + set minutes(val) { this.innerObject.minutes = val; }, + + get seconds() { return this.innerObject.seconds; }, + set seconds(val) { this.innerObject.seconds = val; }, + + get inSeconds() { return this.innerObject.toSeconds(); }, + set inSeconds(val) { this.innerObject.fromSeconds(val); }, + + addDuration: unwrap(ICAL.Duration, function(val) { + this.innerObject.fromSeconds(this.innerObject.toSeconds() + val.toSeconds()); + }), + + compare: unwrap(ICAL.Duration, function(val) { + return this.innerObject.compare(val); + }), + + reset: function() { this.innerObject.reset(); }, + normalize: function() { this.innerObject.normalize(); }, + toString: function() { return this.innerObject.toString(); }, + + get icalString() { return this.innerObject.toString(); }, + set icalString(val) { this.innerObject = ICAL.Duration.fromString(val); } +}; diff --git a/calendar/base/backend/icaljs/calICALJSComponents.js b/calendar/base/backend/icaljs/calICALJSComponents.js new file mode 100644 index 00000000..eb0ea50f --- /dev/null +++ b/calendar/base/backend/icaljs/calICALJSComponents.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://calendar/modules/calUtils.jsm"); + +var scriptLoadOrder = [ + "calTimezone.js", + "calDateTime.js", + "calDuration.js", + "calICSService.js", + "calPeriod.js", + "calRecurrenceRule.js", +]; + +function getComponents() { + return [ + calDateTime, + calDuration, + calIcalComponent, + calIcalProperty, + calICSService, + calPeriod, + calRecurrenceRule, + ]; +} + +this.NSGetFactory = cal.loadingNSGetFactory(scriptLoadOrder, getComponents, this); diff --git a/calendar/base/backend/icaljs/calICSService-worker.js b/calendar/base/backend/icaljs/calICSService-worker.js new file mode 100644 index 00000000..baa3653d --- /dev/null +++ b/calendar/base/backend/icaljs/calICSService-worker.js @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * ChromeWorker for parseICSAsync method in calICSService.js + */ + +var NS_OK = 0; +var NS_ERROR_FAILURE = 2147500037; + +importScripts("resource://calendar/modules/ical.js"); + +onmessage = function(event) { + try { + let comp = ICAL.parse(event.data); + postMessage({ rc: NS_OK, data: comp }); + } catch (e) { + postMessage({ rc: NS_ERROR_FAILURE, data: "Exception occurred: " + e }); + } + close(); +}; diff --git a/calendar/base/backend/icaljs/calICSService.js b/calendar/base/backend/icaljs/calICSService.js new file mode 100644 index 00000000..b4d07f1e --- /dev/null +++ b/calendar/base/backend/icaljs/calICSService.js @@ -0,0 +1,520 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://calendar/modules/ical.js"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://calendar/modules/calUtils.jsm"); + +function calIcalProperty(innerObject) { + this.innerObject = innerObject || new ICAL.Property(); + this.wrappedJSObject = this; +} + +var calIcalPropertyInterfaces = [Components.interfaces.calIIcalProperty]; +var calIcalPropertyClassID = Components.ID("{423ac3f0-f612-48b3-953f-47f7f8fd705b}"); +calIcalProperty.prototype = { + QueryInterface: XPCOMUtils.generateQI(calIcalPropertyInterfaces), + classID: calIcalPropertyClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/ical-property;1", + classDescription: "Wrapper for a libical property", + classID: calIcalPropertyClassID, + interfaces: calIcalPropertyInterfaces + }), + + get icalString() { return this.innerObject.toICALString() + ICAL.newLineChar; }, + get icalProperty() { return this.innerObject; }, + set icalProperty(val) { this.innerObject = val; }, + + get parent() { return this.innerObject.parent; }, + toString: function() { return this.innerObject.toICAL(); }, + + get value() { + // Unescaped value for properties of TEXT, escaped otherwise. + if (this.innerObject.type == "text") { + return this.innerObject.getValues().join(","); + } + return this.valueAsIcalString; + }, + set value(val) { + // Unescaped value for properties of TEXT, escaped otherwise. + if (this.innerObject.type == "text") { + this.innerObject.setValue(val); + return val; + } + this.valueAsIcalString = val; + return val; + }, + + get valueAsIcalString() { + let type = this.innerObject.type; + return this.innerObject.getValues().map(val => { + if (type == "text") { + return ICAL.stringify.value(val, type, ICAL.design.icalendar); + } else if (typeof val == "number" || typeof val == "string") { + return val; + } else if ("toICALString" in val) { + return val.toICALString(); + } else { + return val.toString(); + } + }).join(","); + }, + set valueAsIcalString(val) { + let mockLine = this.propertyName + ":" + val; + let prop = ICAL.Property.fromString(mockLine, ICAL.design.icalendar); + + if (this.innerObject.isMultiValue) { + this.innerObject.setValues(prop.getValues()); + } else { + this.innerObject.setValue(prop.getFirstValue()); + } + return val; + }, + + get valueAsDatetime() { + let val = this.innerObject.getFirstValue(); + let isIcalTime = val && (typeof val == "object") && + ("icalclass" in val) && val.icalclass == "icaltime"; + return (isIcalTime ? new calDateTime(val) : null); + }, + set valueAsDatetime(rawval) { + unwrapSetter(ICAL.Time, rawval, function(val) { + if (val && val.zone && + val.zone != ICAL.Timezone.utcTimezone && + val.zone != ICAL.Timezone.localTimezone) { + this.innerObject.setParameter("TZID", val.zone.tzid); + if (this.parent) { + let tzref = wrapGetter(calICALJSTimezone, val.zone); + this.parent.addTimezoneReference(tzref); + } + } else { + this.innerObject.removeParameter("TZID"); + } + this.innerObject.setValue(val); + }, this); + }, + + get propertyName() { return this.innerObject.name.toUpperCase(); }, + + getParameter: function(name) { + // Unfortuantely getting the "VALUE" parameter won't work, since in + // jCal it has been translated to the value type id. + if (name == "VALUE") { + let defaultType = this.innerObject.getDefaultType(); + if (this.innerObject.type != defaultType) { + // Default type doesn't match object type, so we have a VALUE + // parameter + return this.innerObject.type.toUpperCase(); + } + } + + return this.innerObject.getParameter(name.toLowerCase()); + }, + setParameter: function(name, value) { + // Similar problems for setting the value parameter. Lightning code + // expects setting the value parameter to just change the value type + // and attempt to use the previous value as the new one. To do this in + // ICAL.js we need to save the value, reset the type and then try to + // set the value again. + if (name == "VALUE") { + let oldValues; + let type = this.innerObject.type; + let designSet = this.innerObject._designSet; + + let wasMultiValue = this.innerObject.isMultiValue; + if (wasMultiValue) { + oldValues = this.innerObject.getValues(); + } else { + let oldValue = this.innerObject.getFirstValue(); + oldValues = oldValue ? [oldValue] : []; + } + + this.innerObject.resetType(value.toLowerCase()); + try { + oldValues = oldValues.map(oldValue => { + let strvalue = ICAL.stringify.value(oldValue.toString(), type, designSet); + return ICAL.parse._parseValue(strvalue, value, designSet); + }); + } catch (e) { + // If there was an error reparsing the value, then just keep it + // empty. + oldValues = null; + } + + if (oldValues && oldValues.length) { + if (wasMultiValue && this.innerObject.isMultiValue) { + this.innerObject.setValues(oldValues); + } else { + this.innerObject.setValue(oldValues.join(",")); + } + } + } else { + this.innerObject.setParameter(name.toLowerCase(), value); + } + }, + removeParameter: function(name) { + // Again, VALUE needs special handling. Removing the value parameter is + // kind of like resetting it to the default type. So find out the + // default type and then set the value parameter to it. + if (name == "VALUE") { + let propname = this.innerObject.name.toLowerCase(); + if (propname in ICAL.design.icalendar.property) { + let details = ICAL.design.icalendar.property[propname]; + if ("defaultType" in details) { + this.setParameter("VALUE", details.defaultType); + } + } + } else { + this.innerObject.removeParameter(name.toLowerCase()); + } + }, + + clearXParameters: function() { + cal.WARN("calIICSService::clearXParameters is no longer implemented, " + + "please use removeParameter"); + }, + + paramIterator: null, + getFirstParameterName: function() { + let innerObject = this.innerObject; + this.paramIterator = (function* () { + let defaultType = innerObject.getDefaultType(); + if (defaultType != innerObject.type) { + yield "VALUE"; + } + + let paramNames = Object.keys(innerObject.jCal[1] || {}); + for (let name of paramNames) { + yield name.toUpperCase(); + } + })(); + return this.getNextParameterName(); + }, + + getNextParameterName: function() { + if (this.paramIterator) { + let next = this.paramIterator.next(); + if (next.done) { + this.paramIterator = null; + } + + return next.value; + } else { + return this.getFirstParameterName(); + } + } +}; + +function calIcalComponent(innerObject) { + this.innerObject = innerObject || new ICAL.Component(); + this.wrappedJSObject = this; + this.mReferencedZones = {}; +} + +var calIcalComponentInterfaces = [Components.interfaces.calIIcalComponent]; +var calIcalComponentClassID = Components.ID("{51ac96fd-1279-4439-a85b-6947b37f4cea}"); +calIcalComponent.prototype = { + QueryInterface: XPCOMUtils.generateQI(calIcalComponentInterfaces), + classID: calIcalComponentClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/ical-component;1", + classDescription: "Wrapper for a icaljs component", + classID: calIcalComponentClassID, + interfaces: calIcalComponentInterfaces + }), + + clone: function() { return new calIcalComponent(new ICAL.Component(this.innerObject.toJSON())); }, + + get parent() { return wrapGetter(calIcalComponent, this.innerObject.parent); }, + + get icalTimezone() { return this.innerObject.name == "vtimezone" ? this.innerObject : null; }, + get icalComponent() { return this.innerObject; }, + set icalComponent(val) { this.innerObject = val; }, + + componentIterator: null, + getFirstSubcomponent: function(kind) { + if (kind == "ANY") { + kind = null; + } else if (kind) { + kind = kind.toLowerCase(); + } + let innerObject = this.innerObject; + this.componentIterator = (function* () { + let comps = innerObject.getAllSubcomponents(kind); + if (comps) { + for (let comp of comps) { + yield new calIcalComponent(comp); + } + } + })(); + return this.getNextSubcomponent(kind); + }, + getNextSubcomponent: function(kind) { + if (this.componentIterator) { + let next = this.componentIterator.next(); + if (next.done) { + this.componentIterator = null; + } + + return next.value; + } else { + return this.getFirstSubcomponent(kind); + } + }, + + get componentType() { return this.innerObject.name.toUpperCase(); }, + + get uid() { return this.innerObject.getFirstPropertyValue("uid"); }, + set uid(val) { this.innerObject.updatePropertyWithValue("uid", val); }, + + get prodid() { return this.innerObject.getFirstPropertyValue("prodid"); }, + set prodid(val) { this.innerObject.updatePropertyWithValue("prodid", val); }, + + get version() { return this.innerObject.getFirstPropertyValue("version"); }, + set version(val) { this.innerObject.updatePropertyWithValue("version", val); }, + + get method() { return this.innerObject.getFirstPropertyValue("method"); }, + set method(val) { this.innerObject.updatePropertyWithValue("method", val); }, + + get status() { return this.innerObject.getFirstPropertyValue("status"); }, + set status(val) { this.innerObject.updatePropertyWithValue("status", val); }, + + get summary() { return this.innerObject.getFirstPropertyValue("summary"); }, + set summary(val) { this.innerObject.updatePropertyWithValue("summary", val); }, + + get description() { return this.innerObject.getFirstPropertyValue("description"); }, + set description(val) { this.innerObject.updatePropertyWithValue("description", val); }, + + get location() { return this.innerObject.getFirstPropertyValue("location"); }, + set location(val) { this.innerObject.updatePropertyWithValue("location", val); }, + + get categories() { return this.innerObject.getFirstPropertyValue("categories"); }, + set categories(val) { this.innerObject.updatePropertyWithValue("categories", val); }, + + get URL() { return this.innerObject.getFirstPropertyValue("url"); }, + set URL(val) { this.innerObject.updatePropertyWithValue("url", val); }, + + get priority() { + // If there is no value for this integer property, then we must return + // the designated INVALID_VALUE. + const INVALID_VALUE = Components.interfaces.calIIcalComponent.INVALID_VALUE; + let prop = this.innerObject.getFirstProperty("priority"); + let val = prop ? prop.getFirstValue() : null; + return (val === null ? INVALID_VALUE : val); + }, + set priority(val) { this.innerObject.updatePropertyWithValue("priority", val); }, + + _setTimeAttr: function(propName, val) { + let prop = this.innerObject.updatePropertyWithValue(propName, val); + if (val && val.zone && + val.zone != ICAL.Timezone.utcTimezone && + val.zone != ICAL.Timezone.localTimezone) { + prop.setParameter("TZID", val.zone.tzid); + this.addTimezoneReference(wrapGetter(calICALJSTimezone, val.zone)); + } else { + prop.removeParameter("TZID"); + } + }, + + get startTime() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("dtstart")); }, + set startTime(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "dtstart"), this); }, + + get endTime() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("dtend")); }, + set endTime(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "dtend"), this); }, + + get duration() { return wrapGetter(calDuration, this.innerObject.getFirstPropertyValue("duration")); }, + + get dueTime() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("due")); }, + set dueTime(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "due"), this); }, + + get stampTime() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("dtstamp")); }, + set stampTime(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "dtstamp"), this); }, + + get createdTime() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("created")); }, + set createdTime(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "created"), this); }, + + get completedTime() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("completed")); }, + set completedTime(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "completed"), this); }, + + get lastModified() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("last-modified")); }, + set lastModified(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "last-modified"), this); }, + + get recurrenceId() { return wrapGetter(calDateTime, this.innerObject.getFirstPropertyValue("recurrence-id")); }, + set recurrenceId(val) { unwrapSetter(ICAL.Time, val, this._setTimeAttr.bind(this, "recurrence-id"), this); }, + + serializeToICS: function() { return this.innerObject.toString() + ICAL.newLineChar; }, + toString: function() { return this.innerObject.toString(); }, + + addSubcomponent: function(comp) { + comp.getReferencedTimezones({}).forEach(this.addTimezoneReference, this); + let jscomp = unwrapSingle(ICAL.Component, comp); + this.innerObject.addSubcomponent(jscomp); + }, + + propertyIterator: null, + getFirstProperty: function(kind) { + if (kind == "ANY") { + kind = null; + } else if (kind) { + kind = kind.toLowerCase(); + } + let innerObject = this.innerObject; + this.propertyIterator = (function* () { + let props = innerObject.getAllProperties(kind); + if (!props) { + return; + } + for (let prop of props) { + let hell = prop.getValues(); + if (hell.length > 1) { + // Uh oh, multiple property values. Our code expects each as one + // property. I hate API incompatibility! + for (let devil of hell) { + let thisprop = new ICAL.Property(prop.toJSON(), + prop.parent); + thisprop.removeAllValues(); + thisprop.setValue(devil); + yield new calIcalProperty(thisprop); + } + } else { + yield new calIcalProperty(prop); + } + } + })(); + + return this.getNextProperty(kind); + }, + + getNextProperty: function(kind) { + if (this.propertyIterator) { + let next = this.propertyIterator.next(); + if (next.done) { + this.propertyIterator = null; + } + + return next.value; + } else { + return this.getFirstProperty(kind); + } + }, + + _getNextParentVCalendar: function() { + let vcalendar = this; // eslint-disable-line consistent-this + while (vcalendar && vcalendar.componentType != "VCALENDAR") { + vcalendar = vcalendar.parent; + } + return vcalendar || this; + }, + + addProperty: function(prop) { + try { + let datetime = prop.valueAsDatetime; + if (datetime && datetime.timezone) { + this._getNextParentVCalendar().addTimezoneReference(datetime.timezone); + } + } catch (e) { + // If there is an issue adding the timezone reference, don't make + // that break adding the property. + } + + let jsprop = unwrapSingle(ICAL.Property, prop); + this.innerObject.addProperty(jsprop); + }, + + addTimezoneReference: function(timezone) { + if (timezone) { + if (!(timezone.tzid in this.mReferencedZones) && + this.componentType == "VCALENDAR") { + let comp = timezone.icalComponent; + if (comp) { + this.addSubcomponent(comp); + } + } + + this.mReferencedZones[timezone.tzid] = timezone; + } + }, + + getReferencedTimezones: function(aCount) { + let vals = Object.keys(this.mReferencedZones).map(timezone => this.mReferencedZones[timezone]); + aCount.value = vals.length; + return vals; + }, + + serializeToICSStream: function() { + let unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); + unicodeConverter.charset = "UTF-8"; + return unicodeConverter.convertToInputStream(this.innerObject.toString()); + } +}; + +function calICSService() { + this.wrappedJSObject = this; +} + +var calICSServiceInterfaces = [Components.interfaces.calIICSService]; +var calICSServiceClassID = Components.ID("{c61cb903-4408-41b3-bc22-da0b27efdfe1}"); +calICSService.prototype = { + QueryInterface: XPCOMUtils.generateQI(calICSServiceInterfaces), + classID: calICSServiceClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/ics-service;1", + classDescription: "ICS component and property service", + classID: calICSServiceClassID, + interfaces: [Components.interfaces.calIICSService] + }), + + parseICS: function(serialized, tzProvider) { + // TODO ical.js doesn't support tz providers, but this is usually null + // or our timezone service anyway. + let comp = ICAL.parse(serialized); + return new calIcalComponent(new ICAL.Component(comp)); + }, + + parseICSAsync: function(serialized, tzProvider, listener) { + // There are way too many error checking messages here, but I had so + // much pain with this method that I don't want it to break again. + try { + let worker = new ChromeWorker("resource://calendar/calendar-js/calICSService-worker.js"); + worker.onmessage = function(event) { + let rc = Components.results.NS_ERROR_FAILURE; + let icalComp = null; + try { + rc = event.data.rc; + icalComp = new calIcalComponent(new ICAL.Component(event.data.data)); + if (!Components.isSuccessCode(rc)) { + cal.ERROR("[calICSService] Error in parser worker: " + data); + } + } catch (e) { + cal.ERROR("[calICSService] Exception parsing item: " + e); + } + + listener.onParsingComplete(rc, icalComp); + }; + worker.onerror = function(event) { + cal.ERROR("[calICSService] Error in parser worker: " + event.message); + listener.onParsingComplete(Components.results.NS_ERROR_FAILURE, null); + }; + worker.postMessage(serialized); + } catch (e) { + // If an error occurs above, the calling code will hang. Catch the exception just in case + cal.ERROR("[calICSService] Error starting parsing worker: " + e); + listener.onParsingComplete(Components.results.NS_ERROR_FAILURE, null); + } + }, + + createIcalComponent: function(kind) { + return new calIcalComponent(new ICAL.Component(kind.toLowerCase())); + }, + + createIcalProperty: function(kind) { + return new calIcalProperty(new ICAL.Property(kind.toLowerCase())); + }, + + createIcalPropertyFromString: function(str) { + return new calIcalProperty(ICAL.Property.fromString(str.trim(), ICAL.design.icalendar)); + } +}; diff --git a/calendar/base/backend/icaljs/calPeriod.js b/calendar/base/backend/icaljs/calPeriod.js new file mode 100644 index 00000000..fac6e091 --- /dev/null +++ b/calendar/base/backend/icaljs/calPeriod.js @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://calendar/modules/ical.js"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function calPeriod(innerObject) { + this.innerObject = innerObject || new ICAL.Period({}); + this.wrappedJSObject = this; +} + +var calPeriodInterfaces = [Components.interfaces.calIPeriod]; +var calPeriodClassID = Components.ID("{394a281f-7299-45f7-8b1f-cce21258972f}"); +calPeriod.prototype = { + QueryInterface: XPCOMUtils.generateQI(calPeriodInterfaces), + classID: calPeriodClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/period;1", + classDescription: "A period between two dates", + classID: calPeriodClassID, + interfaces: calPeriodInterfaces + }), + + isMutable: true, + innerObject: null, + + get icalPeriod() { return this.innerObject; }, + set icalPeriod(val) { this.innerObject = val; }, + + makeImmutable: function() { this.isMutable = false; }, + clone: function() { return new calPeriod(this.innerObject.clone()); }, + + get start() { return wrapGetter(calDateTime, this.innerObject.start); }, + set start(rawval) { + unwrapSetter(ICAL.Time, rawval, function(val) { + this.innerObject.start = val; + }, this); + }, + + get end() { return wrapGetter(calDateTime, this.innerObject.getEnd()); }, + set end(rawval) { + unwrapSetter(ICAL.Time, rawval, function(val) { + if (this.innerObject.duration) { + this.innerObject.duration = null; + } + this.innerObject.end = val; + }, this); + }, + + get duration() { return wrapGetter(calDuration, this.innerObject.getDuration()); }, + + get icalString() { return this.innerObject.toICALString(); }, + set icalString(val) { + let dates = ICAL.parse._parseValue(val, "period", ICAL.design.icalendar); + this.innerObject = ICAL.Period.fromString(dates.join("/")); + return val; + }, + + toString: function() { return this.innerObject.toString(); } +}; diff --git a/calendar/base/backend/icaljs/calRecurrenceRule.js b/calendar/base/backend/icaljs/calRecurrenceRule.js new file mode 100644 index 00000000..e8c1592f --- /dev/null +++ b/calendar/base/backend/icaljs/calRecurrenceRule.js @@ -0,0 +1,194 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://calendar/modules/ical.js"); +Components.utils.import("resource://calendar/modules/calUtils.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function calRecurrenceRule(innerObject) { + this.innerObject = innerObject || new ICAL.Recur(); + this.wrappedJSObject = this; +} + +var calRecurrenceRuleInterfaces = [Components.interfaces.calIRecurrenceRule]; +var calRecurrenceRuleClassID = Components.ID("{df19281a-5389-4146-b941-798cb93a7f0d}"); +calRecurrenceRule.prototype = { + QueryInterface: XPCOMUtils.generateQI(calRecurrenceRuleInterfaces), + classID: calRecurrenceRuleClassID, + classInfo: XPCOMUtils.generateCI({ + contractID: "@mozilla.org/calendar/recurrence-rule;1", + classDescription: "Calendar Recurrence Rule", + classID: calRecurrenceRuleClassID, + interfaces: calRecurrenceRuleInterfaces + }), + + innerObject: null, + + isMutable: true, + makeImmutable: function() { this.isMutable = false; }, + clone: function() { return new calRecurrenceRule(new ICAL.Recur(this.innerObject)); }, + + isNegative: false, // We don't support EXRULE anymore + get isFinite() { return this.innerObject.isFinite(); }, + + getNextOccurrence: function(aStartTime, aRecId) { + aStartTime = unwrapSingle(ICAL.Time, aStartTime); + aRecId = unwrapSingle(ICAL.Time, aRecId); + return wrapGetter(calDateTime, this.innerObject.getNextOccurrence(aStartTime, aRecId)); + }, + + getOccurrences: function(aStartTime, aRangeStart, aRangeEnd, aMaxCount, aCount) { + aStartTime = unwrapSingle(ICAL.Time, aStartTime); + aRangeStart = unwrapSingle(ICAL.Time, aRangeStart); + aRangeEnd = unwrapSingle(ICAL.Time, aRangeEnd); + + if (!aMaxCount && !aRangeEnd && this.count == 0 && this.until == null) { + throw Components.results.NS_ERROR_INVALID_ARG; + } + + let occurrences = []; + let rangeStart = aRangeStart.clone(); + rangeStart.isDate = false; + + let dtend = null; + + if (aRangeEnd) { + dtend = aRangeEnd.clone(); + dtend.isDate = false; + + // If the start of the recurrence is past the end, we have no dates + if (aStartTime.compare(dtend) >= 0) { + aCount.value = 0; + return []; + } + } + + let iter = this.innerObject.iterator(aStartTime); + + for (let next = iter.next(); next; next = iter.next()) { + let dtNext = next.clone(); + dtNext.isDate = false; + + if (dtNext.compare(rangeStart) < 0) { + continue; + } + + if (dtend && dtNext.compare(dtend) >= 0) { + break; + } + + next = next.clone(); + + if (aStartTime.zone) { + next.zone = aStartTime.zone; + } + + occurrences.push(new calDateTime(next)); + + if (aMaxCount && aMaxCount >= occurrences.length) { + break; + } + } + + aCount.value = occurrences.length; + return occurrences; + }, + + get icalString() { return "RRULE:" + this.innerObject.toString() + ICAL.newLineChar; }, + set icalString(val) { this.innerObject = ICAL.Recur.fromString(val.replace(/^RRULE:/i, "")); }, + + get icalProperty() { + let prop = new ICAL.Property("rrule"); + prop.setValue(this.innerObject); + return new calIcalProperty(prop); + }, + set icalProperty(rawval) { + unwrapSetter(ICAL.Property, rawval, function(val) { + this.innerObject = val.getFirstValue(); + }, this); + }, + + get type() { return this.innerObject.freq; }, + set type(val) { this.innerObject.freq = val; }, + + get interval() { return this.innerObject.interval; }, + set interval(val) { this.innerObject.interval = val; }, + + get count() { + if (!this.isByCount) { + throw Components.results.NS_ERROR_FAILURE; + } + return this.innerObject.count || -1; + }, + set count(val) { this.innerObject.count = (val && val > 0 ? val : null); }, + + get untilDate() { + if (this.innerObject.until) { + return new calDateTime(this.innerObject.until); + } else { + return null; + } + }, + set untilDate(rawval) { + unwrapSetter(ICAL.Time, rawval, function(val) { + if (val.timezone != ICAL.Timezone.utcTimezone && + val.timezone != ICAL.Timezone.localTimezone) { + val = val.convertToZone(ICAL.Timezone.utcTimezone); + } + + this.innerObject.until = val; + }, this); + }, + + get isByCount() { return this.innerObject.isByCount(); }, + + get weekStart() { return this.innerObject.wkst - 1; }, + set weekStart(val) { this.innerObject.wkst = val + 1; }, + + getComponent: function(aType, aCount) { + let values = this.innerObject.getComponent(aType); + if (aType == "BYDAY") { + // BYDAY values are alphanumeric: SU, MO, TU, etc.. + for (let i = 0; i < values.length; i++) { + let match = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/.exec(values[i]); + if (!match) { + cal.ERROR("Malformed BYDAY rule\n" + cal.STACK(10)); + return []; + } + values[i] = ICAL.Recur.icalDayToNumericDay(match[3]); + if (match[2]) { + // match[2] is the week number for this value. + values[i] += 8 * match[2]; + } + if (match[1] == "-") { + // Week numbers are counted back from the end of the period. + values[i] *= -1; + } + } + } + + if (aCount) { + aCount.value = values.length; + } + return values; + }, + + setComponent: function(aType, aCount, aValues) { + let values = aValues; + if (aType == "BYDAY") { + // BYDAY values are alphanumeric: SU, MO, TU, etc.. + for (let i = 0; i < values.length; i++) { + let absValue = Math.abs(values[i]); + if (absValue > 7) { + let ordinal = Math.trunc(values[i] / 8); + let day = ICAL.Recur.numericDayToIcalDay(absValue % 8); + values[i] = ordinal + day; + } else { + values[i] = ICAL.Recur.numericDayToIcalDay(values[i]); + } + } + } + this.innerObject.setComponent(aType, values); + } +}; diff --git a/calendar/base/backend/icaljs/icaljs-manifest b/calendar/base/backend/icaljs/icaljs-manifest new file mode 100644 index 00000000..e8535bb6 --- /dev/null +++ b/calendar/base/backend/icaljs/icaljs-manifest @@ -0,0 +1,17 @@ +component {36783242-ec94-4d8a-9248-d2679edd55b9} calICALJSComponents.js +contract @mozilla.org/calendar/datetime;1 {36783242-ec94-4d8a-9248-d2679edd55b9} + +component {7436f480-c6fc-4085-9655-330b1ee22288} calICALJSComponents.js +contract @mozilla.org/calendar/duration;1 {7436f480-c6fc-4085-9655-330b1ee22288} + +component {c61cb903-4408-41b3-bc22-da0b27efdfe1} calICALJSComponents.js +contract @mozilla.org/calendar/ics-service;1 {c61cb903-4408-41b3-bc22-da0b27efdfe1} + +component {394a281f-7299-45f7-8b1f-cce21258972f} calICALJSComponents.js +contract @mozilla.org/calendar/period;1 {394a281f-7299-45f7-8b1f-cce21258972f} + +component {df19281a-5389-4146-b941-798cb93a7f0d} calICALJSComponents.js +contract @mozilla.org/calendar/recurrence-rule;1 {df19281a-5389-4146-b941-798cb93a7f0d} + +component {6702eb17-a968-4b43-b562-0d0c5f8e9eb5} calICALJSComponents.js +contract @mozilla.org/calendar/timezone;1 {6702eb17-a968-4b43-b562-0d0c5f8e9eb5} diff --git a/calendar/base/backend/icaljs/moz.build b/calendar/base/backend/icaljs/moz.build new file mode 100644 index 00000000..fbf7e3f7 --- /dev/null +++ b/calendar/base/backend/icaljs/moz.build @@ -0,0 +1,28 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXTRA_COMPONENTS += [ + 'calICALJSComponents.js', +] + +FINAL_TARGET_FILES.components += [ + 'icaljs-manifest' +] + +FINAL_TARGET_FILES['calendar-js'] += [ + 'calDateTime.js', + 'calDuration.js', + 'calICSService-worker.js', + 'calICSService.js', + 'calPeriod.js', + 'calRecurrenceRule.js' +] + +NO_JS_MANIFEST = True + +with Files('**'): + BUG_COMPONENT = ('Calendar', 'ICAL.js Integration') + +NO_COMPONENTS_MANIFEST = True diff --git a/calendar/base/backend/libical/build/Makefile.in b/calendar/base/backend/libical/build/Makefile.in new file mode 100644 index 00000000..c4edb60d --- /dev/null +++ b/calendar/base/backend/libical/build/Makefile.in @@ -0,0 +1,8 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Ensure that we don't embed a manifest referencing the CRT +EMBED_MANIFEST_AT = + +DEFINES += -DSHARED_LIBRARY=$(SHARED_LIBRARY) diff --git a/calendar/base/backend/libical/build/calBaseModule.cpp b/calendar/base/backend/libical/build/calBaseModule.cpp new file mode 100644 index 00000000..f6ef859d --- /dev/null +++ b/calendar/base/backend/libical/build/calBaseModule.cpp @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "calDateTime.h" +#include "calDuration.h" +#include "calPeriod.h" +#include "calICSService.h" +#include "calRecurrenceRule.h" + +#include "calBaseCID.h" + +NS_GENERIC_FACTORY_CONSTRUCTOR(calDateTime) +NS_DEFINE_NAMED_CID(CAL_DATETIME_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(calDuration) +NS_DEFINE_NAMED_CID(CAL_DURATION_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(calICSService) +NS_DEFINE_NAMED_CID(CAL_ICSSERVICE_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(calPeriod) +NS_DEFINE_NAMED_CID(CAL_PERIOD_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(calRecurrenceRule) +NS_DEFINE_NAMED_CID(CAL_RECURRENCERULE_CID); + + +const mozilla::Module::CIDEntry kCalBaseCIDs[] = { + { &kCAL_DATETIME_CID, false, NULL, calDateTimeConstructor }, + { &kCAL_DURATION_CID, false, NULL, calDurationConstructor }, + { &kCAL_ICSSERVICE_CID, true, NULL, calICSServiceConstructor }, + { &kCAL_PERIOD_CID, false, NULL, calPeriodConstructor }, + { &kCAL_RECURRENCERULE_CID, false, NULL, calRecurrenceRuleConstructor }, + { NULL } +}; + +const mozilla::Module::ContractIDEntry kCalBaseContracts[] = { + { CAL_DATETIME_CONTRACTID, &kCAL_DATETIME_CID }, + { CAL_DURATION_CONTRACTID, &kCAL_DURATION_CID }, + { CAL_ICSSERVICE_CONTRACTID, &kCAL_ICSSERVICE_CID }, + { CAL_PERIOD_CONTRACTID, &kCAL_PERIOD_CID }, + { CAL_RECURRENCERULE_CONTRACTID, &kCAL_RECURRENCERULE_CID }, + { NULL } +}; + +static nsresult +nsInitBaseModule() +{ + // This needs to be done once in the application, we want to make + // sure that new parameters are not thrown away + ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN); + return NS_OK; +} + +static const mozilla::Module kCalBaseModule = { + mozilla::Module::kVersion, + kCalBaseCIDs, + kCalBaseContracts, + NULL, + NULL, + nsInitBaseModule +}; + +NSMODULE_DEFN(calBaseModule) = &kCalBaseModule; diff --git a/calendar/base/backend/libical/build/moz.build b/calendar/base/backend/libical/build/moz.build new file mode 100644 index 00000000..4439c641 --- /dev/null +++ b/calendar/base/backend/libical/build/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'calBaseModule.cpp', +] + +LOCAL_INCLUDES += [ + '..' +] + +FINAL_LIBRARY = "xul" diff --git a/calendar/base/backend/libical/calAttributeHelpers.h b/calendar/base/backend/libical/calAttributeHelpers.h new file mode 100644 index 00000000..6dda85dc --- /dev/null +++ b/calendar/base/backend/libical/calAttributeHelpers.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CALATTRIBUTEHELPERS_H_ +#define CALATTRIBUTEHELPERS_H_ + +#ifndef CAL_ATTR_SET_PRE +#define CAL_ATTR_SET_PRE /**/ +#endif + +#ifndef CAL_ATTR_SET_POST +#define CAL_ATTR_SET_POST /**/ +#endif + +/** + ** A few helpers for declaring simple attribute getters and setters in + ** calItemBase derivatives + **/ + +// helpers for string types +#define CAL_STRINGTYPE_ATTR_GETTER(cname,mtype,name) \ +NS_IMETHODIMP \ +cname::Get##name (mtype &_retval) { \ + _retval.Assign(m##name); \ + return NS_OK; \ +} + +#define CAL_STRINGTYPE_ATTR_SETTER(cname,mtype,name) \ +NS_IMETHODIMP \ +cname::Set##name (const mtype &aValue) { \ + CAL_ATTR_SET_PRE; \ + m##name.Assign(aValue); \ + CAL_ATTR_SET_POST; \ + return NS_OK; \ +} + +#define CAL_STRINGTYPE_ATTR(cname,mtype,name) \ + CAL_STRINGTYPE_ATTR_GETTER(cname,mtype,name) \ + CAL_STRINGTYPE_ATTR_SETTER(cname,mtype,name) + +// helpers for value types +#define CAL_VALUETYPE_ATTR_GETTER(cname,mtype,name) \ +NS_IMETHODIMP \ +cname::Get##name (mtype *_retval) { \ + NS_ENSURE_ARG_POINTER(_retval); \ + *_retval = m##name; \ + return NS_OK; \ +} + +#define CAL_VALUETYPE_ATTR_SETTER(cname,mtype,name) \ +NS_IMETHODIMP \ +cname::Set##name (mtype aValue) { \ + CAL_ATTR_SET_PRE; \ + if (m##name != aValue) { \ + m##name = aValue; \ + CAL_ATTR_SET_POST; \ + } \ + return NS_OK; \ +} + +#define CAL_VALUETYPE_ATTR(cname,mtype,name) \ + CAL_VALUETYPE_ATTR_GETTER(cname,mtype,name) \ + CAL_VALUETYPE_ATTR_SETTER(cname,mtype,name) + +// helpers for interface types +#define CAL_ISUPPORTS_ATTR_GETTER(cname,mtype,name) \ +NS_IMETHODIMP \ +cname::Get##name (mtype **_retval) { \ + NS_ENSURE_ARG_POINTER(_retval); \ + NS_IF_ADDREF (*_retval = m##name); \ + return NS_OK; \ +} + +#define CAL_ISUPPORTS_ATTR_SETTER(cname,mtype,name) \ +NS_IMETHODIMP \ +cname::Set##name (mtype *aValue) { \ + CAL_ATTR_SET_PRE; \ + if (m##name != aValue) { \ + m##name = aValue; \ + CAL_ATTR_SET_POST; \ + } \ + return NS_OK; \ +} + +#define CAL_ISUPPORTS_ATTR(cname,mtype,name) \ + CAL_ISUPPORTS_ATTR_GETTER(cname,mtype,name) \ + CAL_ISUPPORTS_ATTR_SETTER(cname,mtype,name) + + +#endif diff --git a/calendar/base/backend/libical/calDateTime.cpp b/calendar/base/backend/libical/calDateTime.cpp new file mode 100644 index 00000000..6b96d7a4 --- /dev/null +++ b/calendar/base/backend/libical/calDateTime.cpp @@ -0,0 +1,605 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "calDateTime.h" +#include "calBaseCID.h" + +#include "nsServiceManagerUtils.h" +#include "nsIClassInfoImpl.h" + +#include "calIErrors.h" +#include "calDuration.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jswrapper.h" +#include "prprf.h" + +extern "C" { +#include "ical.h" +} + +#define CAL_ATTR_SET_PRE NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE) +#define CAL_ATTR_SET_POST Normalize() +#include "calAttributeHelpers.h" + +NS_IMPL_CLASSINFO(calDateTime, nullptr, 0, CAL_DATETIME_CID) +NS_IMPL_ISUPPORTS_CI(calDateTime, calIDateTime, calIDateTimeLibical) + +calDateTime::calDateTime() + : mImmutable(false) +{ + Reset(); +} + +calDateTime::calDateTime(icaltimetype const* atimeptr, calITimezone *tz) + : mImmutable(false) +{ + FromIcalTime(atimeptr, tz); +} + +NS_IMETHODIMP +calDateTime::GetIsMutable(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = !mImmutable; + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::MakeImmutable() +{ + mImmutable = true; + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::Clone(calIDateTime **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + icaltimetype itt; + ToIcalTime(&itt); + calDateTime * const cdt = new calDateTime(&itt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::ResetTo(int16_t year, + int16_t month, + int16_t day, + int16_t hour, + int16_t minute, + int16_t second, + calITimezone * tz) +{ + NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE); + NS_ENSURE_ARG_POINTER(tz); + mYear = year; + mMonth = month; + mDay = day; + mHour = hour; + mMinute = minute; + mSecond = second; + mIsDate = false; + mTimezone = tz; + Normalize(); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::Reset() +{ + NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE); + mYear = 1970; + mMonth = 0; + mDay = 1; + mHour = 0; + mMinute = 0; + mSecond = 0; + mWeekday = 4; + mYearday = 1; + mIsDate = false; + mTimezone = nullptr; + mNativeTime = 0; + mIsValid = true; + return NS_OK; +} + +CAL_VALUETYPE_ATTR(calDateTime, int16_t, Year) +CAL_VALUETYPE_ATTR(calDateTime, int16_t, Month) +CAL_VALUETYPE_ATTR(calDateTime, int16_t, Day) +CAL_VALUETYPE_ATTR(calDateTime, int16_t, Hour) +CAL_VALUETYPE_ATTR(calDateTime, int16_t, Minute) +CAL_VALUETYPE_ATTR(calDateTime, int16_t, Second) +CAL_VALUETYPE_ATTR(calDateTime, bool, IsDate) +CAL_VALUETYPE_ATTR_GETTER(calDateTime, bool, IsValid) +CAL_VALUETYPE_ATTR_GETTER(calDateTime, PRTime, NativeTime) +CAL_VALUETYPE_ATTR_GETTER(calDateTime, int16_t, Weekday) +CAL_VALUETYPE_ATTR_GETTER(calDateTime, int16_t, Yearday) + + +NS_IMETHODIMP +calDateTime::GetTimezone(calITimezone **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + NS_IF_ADDREF(*aResult = mTimezone); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::SetTimezone(calITimezone *aValue) +{ + NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE); + NS_ENSURE_ARG_POINTER(aValue); + mTimezone = aValue; + CAL_ATTR_SET_POST; + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetTimezoneOffset(int32_t *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + icaltimetype icalt; + ToIcalTime(&icalt); + int dst; + *aResult = icaltimezone_get_utc_offset(const_cast(icalt.zone), &icalt, &dst); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::SetNativeTime(PRTime aNativeTime) +{ + icaltimetype icalt; + PRTimeToIcaltime(aNativeTime, false, icaltimezone_get_utc_timezone(), &icalt); + nsCOMPtr ctz = cal::UTC(); + FromIcalTime(&icalt, ctz); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::AddDuration(calIDuration *aDuration) +{ + NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE); + NS_ENSURE_ARG_POINTER(aDuration); + ensureTimezone(); + + nsresult rv; + nsCOMPtr icaldur = do_QueryInterface(aDuration, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + icaldurationtype idt; + icaldur->ToIcalDuration(&idt); + + icaltimetype itt; + ToIcalTime(&itt); + + icaltimetype const newitt = icaltime_add(itt, idt); + FromIcalTime(&newitt, mTimezone); + + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::SubtractDate(calIDateTime *aDate, calIDuration **aDuration) +{ + NS_ENSURE_ARG_POINTER(aDate); + NS_ENSURE_ARG_POINTER(aDuration); + + // same as icaltime_subtract(), but minding timezones: + PRTime t2t; + aDate->GetNativeTime(&t2t); + // for a duration, need to convert the difference in microseconds (prtime) + // to seconds (libical), so divide by one million. + icaldurationtype const idt = icaldurationtype_from_int( + static_cast((mNativeTime - t2t) / int64_t(PR_USEC_PER_SEC))); + + calDuration * const dur = new calDuration(&idt); + CAL_ENSURE_MEMORY(dur); + NS_ADDREF(*aDuration = dur); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::ToString(nsACString & aResult) +{ + nsAutoCString tzid; + char buffer[256]; + + ensureTimezone(); + mTimezone->GetTzid(tzid); + + uint32_t const length = PR_snprintf( + buffer, sizeof(buffer), "%04hd/%02hd/%02hd %02hd:%02hd:%02hd %s isDate=%01hd nativeTime=%lld", + mYear, mMonth + 1, mDay, mHour, mMinute, mSecond, + tzid.get(), static_cast(mIsDate), mNativeTime); + if (length != static_cast(-1)) + aResult.Assign(buffer, length); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetInTimezone(calITimezone * aTimezone, calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aTimezone); + NS_ENSURE_ARG_POINTER(aResult); + + if (mIsDate) { + // if it's a date, we really just want to make a copy of this + // and set the timezone. + nsresult rv = Clone(aResult); + if (NS_SUCCEEDED(rv)) { + rv = (*aResult)->SetTimezone(aTimezone); + } + return rv; + } else { + icaltimetype icalt; + ToIcalTime(&icalt); + + icaltimezone * tz = cal::getIcalTimezone(aTimezone); + if (icalt.zone == tz) { + return Clone(aResult); + } + + /* If there's a zone, we need to convert; otherwise, we just + * assign, since this item is floating */ + if (icalt.zone && tz) { + icaltimezone_convert_time(&icalt, const_cast(icalt.zone), tz); + } + icalt.zone = tz; + icalt.is_utc = (tz && tz == icaltimezone_get_utc_timezone()); + + calDateTime * cdt = new calDateTime(&icalt, aTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF (*aResult = cdt); + return NS_OK; + } +} + +NS_IMETHODIMP +calDateTime::GetStartOfWeek(calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + icaltimetype icalt; + ToIcalTime(&icalt); + int day_of_week = icaltime_day_of_week(icalt); + if (day_of_week > 1) + icaltime_adjust(&icalt, - (day_of_week - 1), 0, 0, 0); + icalt.is_date = 1; + + calDateTime * const cdt = new calDateTime(&icalt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetEndOfWeek(calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + icaltimetype icalt; + ToIcalTime(&icalt); + int day_of_week = icaltime_day_of_week(icalt); + if (day_of_week < 7) + icaltime_adjust(&icalt, 7 - day_of_week, 0, 0, 0); + icalt.is_date = 1; + + calDateTime * const cdt = new calDateTime(&icalt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetStartOfMonth(calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + icaltimetype icalt; + ToIcalTime(&icalt); + icalt.day = 1; + icalt.is_date = 1; + + calDateTime * const cdt = new calDateTime(&icalt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetEndOfMonth(calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + icaltimetype icalt; + ToIcalTime(&icalt); + icalt.day = icaltime_days_in_month(icalt.month, icalt.year); + icalt.is_date = 1; + + calDateTime * const cdt = new calDateTime(&icalt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetStartOfYear(calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + icaltimetype icalt; + ToIcalTime(&icalt); + icalt.month = 1; + icalt.day = 1; + icalt.is_date = 1; + + calDateTime * const cdt = new calDateTime(&icalt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetEndOfYear(calIDateTime ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + ensureTimezone(); + + icaltimetype icalt; + ToIcalTime(&icalt); + icalt.month = 12; + icalt.day = 31; + icalt.is_date = 1; + + calDateTime * const cdt = new calDateTime(&icalt, mTimezone); + CAL_ENSURE_MEMORY(cdt); + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::GetIcalString(nsACString& aResult) +{ + icaltimetype t; + ToIcalTime(&t); + + // note that ics is owned by libical, so we don't need to free + char const * const ics = icaltime_as_ical_string(t); + CAL_ENSURE_MEMORY(ics); + aResult.Assign(ics); + return NS_OK; +} + +NS_IMETHODIMP +calDateTime::SetIcalString(nsACString const& aIcalString) +{ + NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE); + icaltimetype icalt; + icalt = icaltime_from_string(PromiseFlatCString(aIcalString).get()); + if (icaltime_is_null_time(icalt)) { + return static_cast(calIErrors::ICS_ERROR_BASE + icalerrno); + } + FromIcalTime(&icalt, nullptr); + return NS_OK; +} + +/** + ** utility/protected methods + **/ + +// internal Normalize(): +void calDateTime::Normalize() +{ + icaltimetype icalt; + + ensureTimezone(); + ToIcalTime(&icalt); + FromIcalTime(&icalt, mTimezone); +} + +void +calDateTime::ensureTimezone() +{ + if (mTimezone == nullptr) { + mTimezone = cal::UTC(); + } +} + +NS_IMETHODIMP_(void) +calDateTime::ToIcalTime(struct icaltimetype * icalt) +{ + ensureTimezone(); + + icalt->year = mYear; + icalt->month = mMonth + 1; + icalt->day = mDay; + icalt->hour = mHour; + icalt->minute = mMinute; + icalt->second = mSecond; + + icalt->is_date = mIsDate ? 1 : 0; + icalt->is_daylight = 0; + + icaltimezone * tz = cal::getIcalTimezone(mTimezone); + icalt->zone = tz; + icalt->is_utc = (tz && tz == icaltimezone_get_utc_timezone()); + icalt->is_daylight = 0; + // xxx todo: discuss/investigate is_daylight +// if (tz) { +// icaltimezone_get_utc_offset(tz, icalt, &icalt->is_daylight); +// } +} + +void calDateTime::FromIcalTime(icaltimetype const* icalt, calITimezone * tz) +{ + icaltimetype t = *icalt; + mIsValid = (icaltime_is_null_time(t) || + icaltime_is_valid_time(t) ? true : false); + + mIsDate = t.is_date ? true : false; + if (mIsDate) { + t.hour = 0; + t.minute = 0; + t.second = 0; + } + + if (mIsValid) { + t = icaltime_normalize(t); + } + + mYear = static_cast(t.year); + mMonth = static_cast(t.month - 1); + mDay = static_cast(t.day); + mHour = static_cast(t.hour); + mMinute = static_cast(t.minute); + mSecond = static_cast(t.second); + + if (tz) { + mTimezone = tz; + } else { + mTimezone = cal::detectTimezone(t, nullptr); + } +#if defined(DEBUG) + if (mTimezone) { + if (t.is_utc) { + nsCOMPtr ctz = cal::UTC(); + NS_ASSERTION(SameCOMIdentity(mTimezone, ctz), "UTC mismatch!"); + } else if (!t.zone) { + nsAutoCString tzid; + mTimezone->GetTzid(tzid); + if (tzid.EqualsLiteral("floating")) { + nsCOMPtr ctz = cal::floating(); + NS_ASSERTION(SameCOMIdentity(mTimezone, ctz), "floating mismatch!"); + } + } else { + nsAutoCString tzid; + mTimezone->GetTzid(tzid); + NS_ASSERTION(tzid.Equals(icaltimezone_get_tzid(const_cast(t.zone))), + "tzid mismatch!"); + } + } +#endif + + mWeekday = static_cast(icaltime_day_of_week(t) - 1); + mYearday = static_cast(icaltime_day_of_year(t)); + + // mNativeTime: not moving the existing date to UTC, + // but merely representing it a UTC-based way. + t.is_date = 0; + mNativeTime = IcaltimeToPRTime(&t, icaltimezone_get_utc_timezone()); +} + +PRTime calDateTime::IcaltimeToPRTime(icaltimetype const* icalt, icaltimezone const* tz) +{ + icaltimetype tt; + PRExplodedTime et; + + /* If the time is the special null time, return 0. */ + if (icaltime_is_null_time(*icalt)) { + return 0; + } + + if (tz) { + // use libical for timezone conversion, as it can handle all ics + // timezones. having nspr do it is much harder. + tt = icaltime_convert_to_zone(*icalt, const_cast(tz)); + } else { + tt = *icalt; + } + + /* Empty the destination */ + memset(&et, 0, sizeof(struct PRExplodedTime)); + + /* Fill the fields */ + if (icaltime_is_date(tt)) { + et.tm_sec = et.tm_min = et.tm_hour = 0; + } else { + et.tm_sec = tt.second; + et.tm_min = tt.minute; + et.tm_hour = tt.hour; + } + et.tm_mday = static_cast(tt.day); + et.tm_month = static_cast(tt.month-1); + et.tm_year = static_cast(tt.year); + + return PR_ImplodeTime(&et); +} + +void calDateTime::PRTimeToIcaltime(PRTime time, bool isdate, + icaltimezone const* tz, + icaltimetype * icalt) +{ + PRExplodedTime et; + PR_ExplodeTime(time, PR_GMTParameters, &et); + + icalt->year = et.tm_year; + icalt->month = et.tm_month + 1; + icalt->day = et.tm_mday; + + if (isdate) { + icalt->hour = 0; + icalt->minute = 0; + icalt->second = 0; + icalt->is_date = 1; + } else { + icalt->hour = et.tm_hour; + icalt->minute = et.tm_min; + icalt->second = et.tm_sec; + icalt->is_date = 0; + } + + icalt->zone = tz; + icalt->is_utc = ((tz && tz == icaltimezone_get_utc_timezone()) ? 1 : 0); + icalt->is_daylight = 0; + // xxx todo: discuss/investigate is_daylight +// if (tz) { +// icaltimezone_get_utc_offset(tz, icalt, &icalt->is_daylight); +// } +} + +NS_IMETHODIMP +calDateTime::Compare(calIDateTime * aOther, int32_t * aResult) +{ + NS_ENSURE_ARG_POINTER(aOther); + NS_ENSURE_ARG_POINTER(aResult); + + nsresult rv; + nsCOMPtr icalother = do_QueryInterface(aOther, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool otherIsDate = false; + aOther->GetIsDate(&otherIsDate); + + icaltimetype a, b; + ToIcalTime(&a); + icalother->ToIcalTime(&b); + + // If either this or aOther is floating, both objects are treated + // as floating for the comparison. + if (!a.zone || !b.zone) { + a.zone = nullptr; + a.is_utc = 0; + b.zone = nullptr; + b.is_utc = 0; + } + + if (mIsDate || otherIsDate) { + *aResult = icaltime_compare_date_only_tz(a, b, cal::getIcalTimezone(mTimezone)); + } else { + *aResult = icaltime_compare(a, b); + } + + return NS_OK; +} diff --git a/calendar/base/backend/libical/calDateTime.h b/calendar/base/backend/libical/calDateTime.h new file mode 100644 index 00000000..267e285a --- /dev/null +++ b/calendar/base/backend/libical/calDateTime.h @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(INCLUDED_CALDATETIME_H) +#define INCLUDED_CALDATETIME_H + +#include "calIDateTime.h" +#include "calITimezoneProvider.h" +#include "calUtils.h" + +struct icaltimetype; +typedef struct _icaltimezone icaltimezone; + +class calDateTime : public calIDateTimeLibical, + public cal::XpcomBase +{ +public: + calDateTime(); + calDateTime(icaltimetype const* icalt, calITimezone * tz); + + NS_DECL_ISUPPORTS + NS_DECL_CALIDATETIME + NS_DECL_CALIDATETIMELIBICAL + +protected: + virtual ~calDateTime() {} + bool mImmutable; + bool mIsValid; + bool mIsDate; + + int16_t mYear; + int16_t mMonth; + int16_t mDay; + int16_t mHour; + int16_t mMinute; + int16_t mSecond; + int16_t mWeekday; + int16_t mYearday; + + PRTime mNativeTime; + nsCOMPtr mTimezone; + + void Normalize(); + void FromIcalTime(icaltimetype const* icalt, calITimezone *tz); + void ensureTimezone(); + + static PRTime IcaltimeToPRTime(icaltimetype const* icalt, icaltimezone const* tz); + static void PRTimeToIcaltime(PRTime time, bool isdate, + icaltimezone const* tz, icaltimetype *icalt); +}; + +#endif // INCLUDED_CALDATETIME_H diff --git a/calendar/base/backend/libical/calDuration.cpp b/calendar/base/backend/libical/calDuration.cpp new file mode 100644 index 00000000..cc82fadb --- /dev/null +++ b/calendar/base/backend/libical/calDuration.cpp @@ -0,0 +1,334 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "calDuration.h" +#include "calBaseCID.h" + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIClassInfoImpl.h" + +#include "calUtils.h" + +#define SECONDS_PER_WEEK 604800 +#define SECONDS_PER_DAY 86400 +#define SECONDS_PER_HOUR 3600 +#define SECONDS_PER_MINUTE 60 + +NS_IMPL_CLASSINFO(calDuration, nullptr, 0, CAL_DURATION_CID) +NS_IMPL_ISUPPORTS_CI(calDuration, calIDuration, calIDurationLibical) + +calDuration::calDuration() + : mImmutable(false) +{ + Reset(); +} + +calDuration::calDuration(const calDuration& cdt) +{ + mDuration.is_neg = cdt.mDuration.is_neg; + mDuration.weeks = cdt.mDuration.weeks; + mDuration.days = cdt.mDuration.days; + mDuration.hours = cdt.mDuration.hours; + mDuration.minutes = cdt.mDuration.minutes; + mDuration.seconds = cdt.mDuration.seconds; + + // copies are always mutable + mImmutable = false; +} + +calDuration::calDuration(const struct icaldurationtype * const aDurationPtr) + : mImmutable(false) +{ + FromIcalDuration(aDurationPtr); +} + +NS_IMETHODIMP +calDuration::GetIcalDuration(JS::MutableHandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calDuration::SetIcalDuration(JS::HandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calDuration::GetIsMutable(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = !mImmutable; + return NS_OK; +} + +NS_IMETHODIMP +calDuration::MakeImmutable() +{ + mImmutable = true; + return NS_OK; +} + +NS_IMETHODIMP +calDuration::Clone(calIDuration **aResult) +{ + calDuration *cdt = new calDuration(*this); + if (!cdt) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult = cdt); + return NS_OK; +} + +NS_IMETHODIMP +calDuration::Reset() +{ + if (mImmutable) + return NS_ERROR_FAILURE; + + mDuration.is_neg = 0; + mDuration.weeks = 0; + mDuration.days = 0; + mDuration.hours = 0; + mDuration.minutes = 0; + mDuration.seconds = 0; + + return NS_OK; +} + +NS_IMETHODIMP calDuration::GetIsNegative(bool *_retval) +{ + *_retval = mDuration.is_neg; + return NS_OK; +} +NS_IMETHODIMP calDuration::SetIsNegative(bool aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + mDuration.is_neg = aValue; + return NS_OK; +} + +NS_IMETHODIMP calDuration::GetWeeks(int16_t *_retval) +{ + *_retval = (int16_t)mDuration.weeks; + return NS_OK; +} +NS_IMETHODIMP calDuration::SetWeeks(int16_t aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + mDuration.weeks = aValue; + return NS_OK; +} + +NS_IMETHODIMP calDuration::GetDays(int16_t *_retval) +{ + *_retval = (int16_t)mDuration.days; + return NS_OK; +} +NS_IMETHODIMP calDuration::SetDays(int16_t aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + mDuration.days = aValue; + return NS_OK; +} + +NS_IMETHODIMP calDuration::GetHours(int16_t *_retval) +{ + *_retval = (int16_t)mDuration.hours; + return NS_OK; +} +NS_IMETHODIMP calDuration::SetHours(int16_t aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + mDuration.hours = aValue; + return NS_OK; +} + +NS_IMETHODIMP calDuration::GetMinutes(int16_t *_retval) +{ + *_retval = (int16_t)mDuration.minutes; + return NS_OK; +} +NS_IMETHODIMP calDuration::SetMinutes(int16_t aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + mDuration.minutes = aValue; + return NS_OK; +} + +NS_IMETHODIMP calDuration::GetSeconds(int16_t *_retval) +{ + *_retval = (int16_t)mDuration.seconds; + return NS_OK; +} +NS_IMETHODIMP calDuration::SetSeconds(int16_t aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + mDuration.seconds = aValue; + return NS_OK; +} + + +NS_IMETHODIMP calDuration::GetInSeconds(int32_t *_retval) +{ + int32_t retval = + (((int32_t)((int16_t)mDuration.weeks * SECONDS_PER_WEEK)) + + ((int32_t)((int16_t)mDuration.days * SECONDS_PER_DAY)) + + ((int32_t)((int16_t)mDuration.hours * SECONDS_PER_HOUR)) + + ((int32_t)((int16_t)mDuration.minutes * SECONDS_PER_MINUTE)) + + ((int32_t)((int16_t)mDuration.seconds))); + if (mDuration.is_neg) + retval=-retval; + *_retval = retval; + + return NS_OK; +} +NS_IMETHODIMP calDuration::SetInSeconds(int32_t aValue) +{ + if (mImmutable) return NS_ERROR_CALENDAR_IMMUTABLE; + + mDuration.is_neg = (aValue < 0); + if (mDuration.is_neg) + aValue = -aValue; + + // set weeks exOR days/hours/... + mDuration.weeks = ((aValue % SECONDS_PER_WEEK) == 0 ? aValue / SECONDS_PER_WEEK : 0); + aValue -= (mDuration.weeks * SECONDS_PER_WEEK); + + mDuration.days = aValue / SECONDS_PER_DAY; + aValue -= (mDuration.days * SECONDS_PER_DAY); + + mDuration.hours = aValue / SECONDS_PER_HOUR; + aValue -= (mDuration.hours * SECONDS_PER_HOUR); + + mDuration.minutes = aValue / SECONDS_PER_MINUTE; + aValue -= (mDuration.minutes * SECONDS_PER_MINUTE); + + mDuration.seconds = aValue; + + return NS_OK; +} + +NS_IMETHODIMP calDuration::AddDuration(calIDuration *aDuration) +{ + if (mImmutable) + return NS_ERROR_CALENDAR_IMMUTABLE; + + nsresult rv; + nsCOMPtr icaldur = do_QueryInterface(aDuration, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + struct icaldurationtype idt; + icaldur->ToIcalDuration(&idt); + + // Calculate the new absolute value of the duration + // For two negative durations, the abs. value will increase, + // so use + in that case. + // Of course, also use + when both durations are positive. + if (idt.is_neg == mDuration.is_neg) { + mDuration.weeks += idt.weeks; + mDuration.days += idt.days; + mDuration.hours += idt.hours; + mDuration.minutes += idt.minutes; + mDuration.seconds += idt.seconds; + } else { + mDuration.weeks -= idt.weeks; + mDuration.days -= idt.days; + mDuration.hours -= idt.hours; + mDuration.minutes -= idt.minutes; + mDuration.seconds -= idt.seconds; + } + + Normalize(); + + return NS_OK; +} + +NS_IMETHODIMP +calDuration::Normalize() +{ + if (mImmutable) + return NS_ERROR_CALENDAR_IMMUTABLE; + + int32_t totalInSeconds; + GetInSeconds(&totalInSeconds); + SetInSeconds(totalInSeconds); + + return NS_OK; +} + +NS_IMETHODIMP +calDuration::ToString(nsACString& aResult) +{ + return GetIcalString(aResult); +} + +NS_IMETHODIMP_(void) +calDuration::ToIcalDuration(struct icaldurationtype *icald) +{ + icald->is_neg = mDuration.is_neg; + icald->weeks = mDuration.weeks; + icald->days = mDuration.days; + icald->hours = mDuration.hours; + icald->minutes = mDuration.minutes; + icald->seconds = mDuration.seconds; + return; +} + +void +calDuration::FromIcalDuration(const struct icaldurationtype * const icald) +{ + mDuration.is_neg = icald->is_neg; + mDuration.weeks = icald->weeks; + mDuration.days = icald->days; + mDuration.hours = icald->hours; + mDuration.minutes = icald->minutes; + mDuration.seconds = icald->seconds; + return; +} + +NS_IMETHODIMP +calDuration::GetIcalString(nsACString& aResult) +{ + // note that ics is owned by libical, so we don't need to free + const char *ics = icaldurationtype_as_ical_string(mDuration); + + if (ics) { + aResult.Assign(ics); + return NS_OK; + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +calDuration::SetIcalString(const nsACString& aIcalString) +{ + mDuration = icaldurationtype_from_string(PromiseFlatCString(aIcalString).get()); + return NS_OK; +} + +NS_IMETHODIMP +calDuration::Compare(calIDuration *aOther, int32_t *aResult) +{ + int32_t thisInSeconds, otherInSeconds; + + // cast to void because these calls can't fail + (void)GetInSeconds(&thisInSeconds); + (void)aOther->GetInSeconds(&otherInSeconds); + + if ( thisInSeconds < otherInSeconds ) { + *aResult = -1; + } else if ( thisInSeconds > otherInSeconds ) { + *aResult = 1; + } else { + *aResult = 0; + } + + return NS_OK; +} diff --git a/calendar/base/backend/libical/calDuration.h b/calendar/base/backend/libical/calDuration.h new file mode 100644 index 00000000..d553b8b8 --- /dev/null +++ b/calendar/base/backend/libical/calDuration.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CALDURATION_H_ +#define CALDURATION_H_ + +#include "calIDuration.h" + +extern "C" { + #include "ical.h" +} + +class calDuration final : public calIDurationLibical +{ +public: + calDuration (); + calDuration (const calDuration& cdt); + calDuration (const struct icaldurationtype * const aDurationPtr); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // calIDateTime interface + NS_DECL_CALIDURATION + NS_DECL_CALIDURATIONLIBICAL + +protected: + ~calDuration() {} + bool mImmutable; + + struct icaldurationtype mDuration; + + void FromIcalDuration(const struct icaldurationtype * const icald); +}; + +#endif /* CALDURATION_H_ */ + diff --git a/calendar/base/backend/libical/calICSService.cpp b/calendar/base/backend/libical/calICSService.cpp new file mode 100644 index 00000000..a764c924 --- /dev/null +++ b/calendar/base/backend/libical/calICSService.cpp @@ -0,0 +1,1398 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsStringStream.h" +#include "nsComponentManagerUtils.h" + +#include "calICSService.h" +#include "calTimezone.h" +#include "calDateTime.h" +#include "calDuration.h" +#include "calIErrors.h" +#include "calUtils.h" + +extern "C" { +#include "ical.h" +} + +calIcalProperty::~calIcalProperty() +{ + if (!mParent) { + icalproperty_free(mProperty); + } +} + +NS_IMPL_CLASSINFO(calIcalProperty, nullptr, 0, CAL_ICALPROPERTY_CID) +NS_IMPL_ISUPPORTS_CI(calIcalProperty, calIIcalProperty, calIIcalPropertyLibical) + +NS_IMETHODIMP_(icalproperty *) +calIcalProperty::GetLibicalProperty() +{ + return mProperty; +} + +NS_IMETHODIMP_(icalcomponent *) +calIcalProperty::GetLibicalComponent() +{ + return mParent->GetLibicalComponent(); +} + +NS_IMETHODIMP +calIcalProperty::GetIcalString(nsACString &str) +{ + char const* icalstr = icalproperty_as_ical_string(mProperty); + if (icalstr == 0) { +#ifdef DEBUG + fprintf(stderr, "Error getting ical string: %d (%s)\n", + icalerrno, icalerror_strerror(icalerrno)); +#endif + return static_cast(calIErrors::ICS_ERROR_BASE + icalerrno); + } + str.Assign(icalstr); + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::ToString(nsACString& aResult) +{ + return GetIcalString(aResult); +} + +NS_IMETHODIMP +calIcalProperty::GetValue(nsACString &str) +{ + icalvalue *value = icalproperty_get_value(mProperty); + icalvalue_kind valuekind = icalvalue_isa(value); + + const char *icalstr; + if (valuekind == ICAL_TEXT_VALUE) { + icalstr = icalvalue_get_text(value); + } else if (valuekind == ICAL_X_VALUE) { + icalstr = icalvalue_get_x(value); + } else if (valuekind == ICAL_ATTACH_VALUE) { + icalattach *attach = icalvalue_get_attach(value); + if (icalattach_get_is_url(attach)) { + icalstr = icalattach_get_url(attach); + } else { + icalstr = (const char *)icalattach_get_data(attach); + } + } else { + icalstr = icalproperty_get_value_as_string(mProperty); + } + + if (!icalstr) { + if (icalerrno == ICAL_BADARG_ERROR) { + str.Truncate(); + // Set string to null, because we don't have a value + // (which is something different then an empty value) + str.SetIsVoid(true); + return NS_OK; + } + +#ifdef DEBUG + fprintf(stderr, "Error getting string value: %d (%s)\n", + icalerrno, icalerror_strerror(icalerrno)); +#endif + return NS_ERROR_FAILURE; + } + + str.Assign(icalstr); + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::SetValue(const nsACString &str) +{ + icalvalue_kind kind = icalproperty_kind_to_value_kind(icalproperty_isa(mProperty)); + if (kind == ICAL_TEXT_VALUE) { + icalvalue *v = icalvalue_new_text(PromiseFlatCString(str).get()); + icalproperty_set_value(mProperty, v); + } else if (kind == ICAL_X_VALUE) { + icalvalue *v = icalvalue_new_x(PromiseFlatCString(str).get()); + icalproperty_set_value(mProperty, v); + } else if (kind == ICAL_ATTACH_VALUE) { + icalattach *v = icalattach_new_from_data(PromiseFlatCString(str).get(), nullptr, nullptr); + icalproperty_set_attach(mProperty, v); + } else { + icalproperty_set_value_from_string(mProperty, + PromiseFlatCString(str).get(), + icalvalue_kind_to_string(kind)); + } + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::GetValueAsIcalString(nsACString &str) +{ + const char *icalstr = icalproperty_get_value_as_string(mProperty); + if (!icalstr) { + if (icalerrno == ICAL_BADARG_ERROR) { + str.Truncate(); + // Set string to null, because we don't have a value + // (which is something different then an empty value) + str.SetIsVoid(true); + return NS_OK; + } + +#ifdef DEBUG + fprintf(stderr, "Error getting string value: %d (%s)\n", + icalerrno, icalerror_strerror(icalerrno)); +#endif + return NS_ERROR_FAILURE; + } + + str.Assign(icalstr); + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::SetValueAsIcalString(const nsACString &str) +{ + const char *kindstr = + icalvalue_kind_to_string(icalproperty_kind_to_value_kind(icalproperty_isa(mProperty))); + icalproperty_set_value_from_string(mProperty, + PromiseFlatCString(str).get(), + kindstr); + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::GetPropertyName(nsACString &name) +{ + const char *icalstr = icalproperty_get_property_name(mProperty); + if (!icalstr) { +#ifdef DEBUG + fprintf(stderr, "Error getting property name: %d (%s)\n", + icalerrno, icalerror_strerror(icalerrno)); +#endif + return NS_ERROR_FAILURE; + } + name.Assign(icalstr); + return NS_OK; +} + +static icalparameter* +FindParameter(icalproperty *prop, const nsACString ¶m, icalparameter_kind kind) +{ + for (icalparameter *icalparam = + icalproperty_get_first_parameter(prop, kind); + icalparam; + icalparam = icalproperty_get_next_parameter(prop, kind)) { + if (param.Equals(icalparameter_get_xname(icalparam))) + return icalparam; + } + return nullptr; +} + +NS_IMETHODIMP +calIcalProperty::GetParameter(const nsACString ¶m, nsACString &value) +{ + // More ridiculous parameter/X-PARAMETER handling. + icalparameter_kind paramkind = + icalparameter_string_to_kind(PromiseFlatCString(param).get()); + + if (paramkind == ICAL_NO_PARAMETER) + return NS_ERROR_INVALID_ARG; + + const char *icalstr = nullptr; + if (paramkind == ICAL_X_PARAMETER) { + icalparameter *icalparam = FindParameter(mProperty, param, ICAL_X_PARAMETER); + if (icalparam) + icalstr = icalparameter_get_xvalue(icalparam); + } else if (paramkind == ICAL_IANA_PARAMETER) { + icalparameter *icalparam = FindParameter(mProperty, param, ICAL_IANA_PARAMETER); + if (icalparam) + icalstr = icalparameter_get_iana_value(icalparam); + } else { + icalstr = icalproperty_get_parameter_as_string(mProperty, + PromiseFlatCString(param).get()); + } + + if (!icalstr) { + value.Truncate(); + value.SetIsVoid(true); + } else { + value.Assign(icalstr); + } + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::SetParameter(const nsACString ¶m, const nsACString &value) +{ + icalparameter_kind paramkind = + icalparameter_string_to_kind(PromiseFlatCString(param).get()); + + if (paramkind == ICAL_NO_PARAMETER) + return NS_ERROR_INVALID_ARG; + + // Because libical's support for manipulating parameters is weak, and + // X-PARAMETERS doubly so, we walk the list looking for an existing one of + // that name, and reset its value if found. + if (paramkind == ICAL_X_PARAMETER) { + icalparameter *icalparam = FindParameter(mProperty, param, ICAL_X_PARAMETER); + if (icalparam) { + icalparameter_set_xvalue(icalparam, + PromiseFlatCString(value).get()); + return NS_OK; + } + // If not found, fall through to adding a new parameter below. + } else if (paramkind == ICAL_IANA_PARAMETER) { + icalparameter *icalparam = FindParameter(mProperty, param, ICAL_IANA_PARAMETER); + if (icalparam) { + icalparameter_set_iana_value(icalparam, + PromiseFlatCString(value).get()); + return NS_OK; + } + // If not found, fall through to adding a new parameter below. + } else { + // We could try getting an existing parameter here and resetting its + // value, but this is easier and I don't care that much about parameter + // performance at this point. + RemoveParameter(param); + } + + icalparameter *icalparam = + icalparameter_new_from_value_string(paramkind, + PromiseFlatCString(value).get()); + if (!icalparam) + return NS_ERROR_OUT_OF_MEMORY; + + // You might ask me "why does libical not do this for us?" and I would + // just nod knowingly but sadly at you in return. + // + // You might also, if you were not too distracted by the first question, + // ask why we have icalproperty_set_x_name but icalparameter_set_xname. + // More nodding would ensue. + if (paramkind == ICAL_X_PARAMETER) + icalparameter_set_xname(icalparam, PromiseFlatCString(param).get()); + else if (paramkind == ICAL_IANA_PARAMETER) + icalparameter_set_iana_name(icalparam, PromiseFlatCString(param).get()); + + icalproperty_add_parameter(mProperty, icalparam); + // XXX check ical errno + return NS_OK; +} + +static nsresult +FillParameterName(icalparameter *icalparam, nsACString &name) +{ + const char *propname = nullptr; + if (icalparam) { + icalparameter_kind paramkind = icalparameter_isa(icalparam); + if (paramkind == ICAL_X_PARAMETER) + propname = icalparameter_get_xname(icalparam); + else if (paramkind == ICAL_IANA_PARAMETER) + propname = icalparameter_get_iana_name(icalparam); + else if (paramkind != ICAL_NO_PARAMETER) + propname = icalparameter_kind_to_string(paramkind); + } + + if (propname) { + name.Assign(propname); + } else { + name.Truncate(); + name.SetIsVoid(true); + } + + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::GetFirstParameterName(nsACString &name) +{ + icalparameter *icalparam = + icalproperty_get_first_parameter(mProperty, + ICAL_ANY_PARAMETER); + return FillParameterName(icalparam, name); +} + +NS_IMETHODIMP +calIcalProperty::GetNextParameterName(nsACString &name) +{ + icalparameter *icalparam = + icalproperty_get_next_parameter(mProperty, + ICAL_ANY_PARAMETER); + return FillParameterName(icalparam, name); +} + +NS_IMETHODIMP +calIcalProperty::RemoveParameter(const nsACString ¶m) +{ + icalproperty_remove_parameter_by_name(mProperty, PromiseFlatCString(param).get()); + // XXX check ical errno + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::ClearXParameters() +{ + int oldcount, paramcount = 0; + do { + oldcount = paramcount; + icalproperty_remove_parameter(mProperty, ICAL_X_PARAMETER); + paramcount = icalproperty_count_parameters(mProperty); + } while (oldcount != paramcount); + return NS_OK; +} + + +NS_IMETHODIMP +calIcalProperty::GetValueAsDatetime(calIDateTime **dtp) +{ + NS_ENSURE_ARG_POINTER(dtp); + return getDatetime_(toIcalComponent(mParent), mProperty, dtp); +} + +nsresult calIcalProperty::getDatetime_(calIcalComponent * parent, + icalproperty * prop, + calIDateTime ** dtp) +{ + icalvalue * const val = icalproperty_get_value(prop); + icalvalue_kind const valkind = icalvalue_isa(val); + if (valkind != ICAL_DATETIME_VALUE && valkind != ICAL_DATE_VALUE) { + return NS_ERROR_UNEXPECTED; + } + icaltimetype itt = icalvalue_get_datetime(val); + + char const* tzid_ = nullptr; + if (!itt.is_utc) { + if (itt.zone) { + tzid_ = icaltimezone_get_tzid(const_cast(itt.zone)); + } else { + // Need to get the tzid param. Unfortunatly, libical tends to return raw + // ics strings, with quotes and everything. That's not what we want. Need + // to work around. + icalparameter * const tzparam = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER); + if (tzparam) { + tzid_ = icalparameter_get_xvalue(tzparam); + } + } + } + + nsCOMPtr tz; + if (tzid_) { + nsDependentCString const tzid(tzid_); + calIcalComponent * comp = nullptr; + if (parent) { + comp = parent->getParentVCalendarOrThis(); + } + // look up parent if timezone is already referenced: + if (comp) { + comp->mReferencedTimezones.Get(tzid, getter_AddRefs(tz)); + } + if (!tz) { + if (parent) { + // passed tz provider has precedence over timezone service: + calITimezoneProvider * const tzProvider = parent->getTzProvider(); + if (tzProvider) { + tzProvider->GetTimezone(tzid, getter_AddRefs(tz)); + NS_ASSERTION(tz, tzid_); + } + } + if (!tz) { + // look up tz in tz service. + // this hides errors from incorrect ics files, which could state + // a TZID that is not present in the ics file. + // The other way round, it makes this product more error tolerant. + nsresult rv = cal::getTimezoneService()->GetTimezone(tzid, getter_AddRefs(tz)); + + if (NS_FAILED(rv) || !tz) { + icaltimezone const* zone = itt.zone; + if (!zone && comp) { + // look up parent VCALENDAR for VTIMEZONE: + zone = icalcomponent_get_timezone(comp->mComponent, tzid_); + NS_ASSERTION(zone, tzid_); + } + if (zone) { + // We need to decouple this (inner) VTIMEZONE from the parent VCALENDAR to avoid + // running into circular references (referenced timezones): + icaltimezone * const clonedZone = icaltimezone_new(); + CAL_ENSURE_MEMORY(clonedZone); + icalcomponent * const clonedZoneComp = + icalcomponent_new_clone(icaltimezone_get_component(const_cast(zone))); + if (!clonedZoneComp) { + icaltimezone_free(clonedZone, 1 /* free struct */); + CAL_ENSURE_MEMORY(clonedZoneComp); + } + if (!icaltimezone_set_component(clonedZone, clonedZoneComp)) { + icaltimezone_free(clonedZone, 1 /* free struct */); + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr const tzComp(new calIcalComponent(clonedZone, clonedZoneComp)); + CAL_ENSURE_MEMORY(tzComp); + tz = new calTimezone(tzid, tzComp); + CAL_ENSURE_MEMORY(tz); + } else { // install phantom timezone, so the data could be repaired: + tz = new calTimezone(tzid, nullptr); + CAL_ENSURE_MEMORY(tz); + } + } + } + if (comp && tz) { + // assure timezone is known: + comp->AddTimezoneReference(tz); + } + } + if (tz) { + // correct itt which would else appear floating: + itt.zone = cal::getIcalTimezone(tz); + itt.is_utc = 0; + } else { + cal::logMissingTimezone(tzid_); + } + } + *dtp = new calDateTime(&itt, tz); + CAL_ENSURE_MEMORY(*dtp); + NS_ADDREF(*dtp); + return NS_OK; +} + + +calIcalComponent::~calIcalComponent() +{ + if (!mParent) { + // We free either a plain icalcomponent or a icaltimezone. + // In the latter case icaltimezone_free frees the VTIMEZONE component. + if (mTimezone) { + icaltimezone_free(mTimezone, 1 /* free struct */); + } else { + icalcomponent_free(mComponent); + } + } +} +NS_IMETHODIMP +calIcalComponent::GetIcalComponent(JS::MutableHandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calIcalComponent::SetIcalComponent(JS::HandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calIcalComponent::GetParent(calIIcalComponent** parent) +{ + NS_ENSURE_ARG_POINTER(parent); + NS_IF_ADDREF(*parent = mParent); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::GetIcalTimezone(JS::MutableHandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calIcalComponent::SetIcalTimezone(JS::HandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calIcalComponent::AddTimezoneReference(calITimezone *aTimezone) +{ + NS_ENSURE_ARG_POINTER(aTimezone); + nsAutoCString tzid; + nsresult rv = aTimezone->GetTzid(tzid); + NS_ENSURE_SUCCESS(rv, rv); + mReferencedTimezones.Put(tzid, aTimezone); + + return NS_OK; +} + + +NS_IMETHODIMP +calIcalComponent::GetReferencedTimezones(uint32_t * aCount, calITimezone *** aTimezones) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aTimezones); + + uint32_t const count = mReferencedTimezones.Count(); + if (count == 0) { + *aCount = 0; + *aTimezones = nullptr; + return NS_OK; + } + + calITimezone ** const timezones = static_cast( + moz_xmalloc(sizeof(calITimezone *) * count)); + CAL_ENSURE_MEMORY(timezones); + // tzptr will get used as an iterator by the enumerator function + calITimezone ** tzptr = timezones; + for (auto iter = mReferencedTimezones.ConstIter(); !iter.Done(); iter.Next() ) { + NS_ADDREF(*tzptr = iter.Data()); + ++tzptr; + } + + *aTimezones = timezones; + *aCount = count; + return NS_OK; +} + +nsresult +calIcalComponent::SetPropertyValue(icalproperty_kind kind, icalvalue *val) +{ + ClearAllProperties(kind); + if (!val) + return NS_OK; + + icalproperty *prop = icalproperty_new(kind); + if (!prop) { + icalvalue_free(val); + return NS_ERROR_OUT_OF_MEMORY; + } + + icalproperty_set_value(prop, val); + icalcomponent_add_property(mComponent, prop); + return NS_OK; +} + +nsresult +calIcalComponent::SetProperty(icalproperty_kind kind, icalproperty *prop) +{ + ClearAllProperties(kind); + if (!prop) + return NS_OK; + icalcomponent_add_property(mComponent, prop); + return NS_OK; +} + +#define COMP_STRING_TO_ENUM_ATTRIBUTE(Attrname, ICALNAME, lcname) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(nsACString &str) \ +{ \ + int32_t val; \ + nsresult rv = GetIntProperty(ICAL_##ICALNAME##_PROPERTY, &val); \ + if (NS_FAILED(rv)) \ + return rv; \ + if (val == -1) { \ + str.Truncate(); \ + str.SetIsVoid(true); \ + } else { \ + str.Assign(icalproperty_##lcname##_to_string((icalproperty_##lcname)val)); \ + } \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(const nsACString &str) \ +{ \ + icalproperty *prop = nullptr; \ + if (!str.IsVoid()) { \ + icalproperty_##lcname val = \ + icalproperty_string_to_##lcname(PromiseFlatCString(str).get()); \ + prop = icalproperty_new_##lcname(val); \ + if (!prop) \ + return NS_ERROR_OUT_OF_MEMORY; /* XXX map errno */ \ + } \ + return SetProperty(ICAL_##ICALNAME##_PROPERTY, prop); \ +} + +#define COMP_GENERAL_STRING_ATTRIBUTE(Attrname, ICALNAME) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(nsACString &str) \ +{ \ + return GetStringProperty(ICAL_##ICALNAME##_PROPERTY, str); \ +} \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(const nsACString &str) \ +{ \ + return SetStringProperty(ICAL_##ICALNAME##_PROPERTY, str); \ +} + +#define COMP_STRING_ATTRIBUTE(Attrname, ICALNAME, lcname) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(nsACString &str) \ +{ \ + return GetStringProperty(ICAL_##ICALNAME##_PROPERTY, str); \ +} \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(const nsACString &str) \ +{ \ + icalproperty *prop = \ + icalproperty_new_##lcname(PromiseFlatCString(str).get()); \ + return SetProperty(ICAL_##ICALNAME##_PROPERTY, prop); \ +} + +#define COMP_GENERAL_INT_ATTRIBUTE(Attrname, ICALNAME) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(int32_t *valp) \ +{ \ + return GetIntProperty(ICAL_##ICALNAME##_PROPERTY, valp); \ +} \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(int32_t val) \ +{ \ + return SetIntProperty(ICAL_##ICALNAME##_PROPERTY, val); \ +} + +#define COMP_ENUM_ATTRIBUTE(Attrname, ICALNAME, lcname) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(int32_t *valp) \ +{ \ + return GetIntProperty(ICAL_##ICALNAME##_PROPERTY, valp); \ +} \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(int32_t val) \ +{ \ + icalproperty *prop = \ + icalproperty_new_##lcname((icalproperty_##lcname)val); \ + return SetProperty(ICAL_##ICALNAME##_PROPERTY, prop); \ +} + +#define COMP_INT_ATTRIBUTE(Attrname, ICALNAME, lcname) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(int32_t *valp) \ +{ \ + return GetIntProperty(ICAL_##ICALNAME##_PROPERTY, valp); \ +} \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(int32_t val) \ +{ \ + icalproperty *prop = icalproperty_new_##lcname(val); \ + return SetProperty(ICAL_##ICALNAME##_PROPERTY, prop); \ +} + +nsresult calIcalComponent::GetStringProperty(icalproperty_kind kind, nsACString &str) +{ + icalproperty *prop = icalcomponent_get_first_property(mComponent, kind); + if (!prop) { + str.Truncate(); + str.SetIsVoid(true); + } else { + str.Assign(icalvalue_get_string(icalproperty_get_value(prop))); + } + return NS_OK; +} + +nsresult calIcalComponent::SetStringProperty(icalproperty_kind kind, + const nsACString &str) +{ + icalvalue *val = nullptr; + if (!str.IsVoid()) { + val = icalvalue_new_string(PromiseFlatCString(str).get()); + if (!val) + return NS_ERROR_OUT_OF_MEMORY; + } + return SetPropertyValue(kind, val); +} + +nsresult calIcalComponent::GetIntProperty(icalproperty_kind kind, int32_t *valp) +{ + icalproperty *prop = icalcomponent_get_first_property(mComponent, kind); + if (!prop) + *valp = calIIcalComponent::INVALID_VALUE; + else + *valp = (int32_t)icalvalue_get_integer(icalproperty_get_value(prop)); + return NS_OK; +} + +nsresult calIcalComponent::SetIntProperty(icalproperty_kind kind, int32_t i) +{ + icalvalue *val = icalvalue_new_integer(i); + if (!val) + return NS_ERROR_OUT_OF_MEMORY; + return SetPropertyValue(kind, val); +} + +nsresult calIcalComponent::GetDateTimeAttribute(icalproperty_kind kind, + calIDateTime ** dtp) +{ + NS_ENSURE_ARG_POINTER(dtp); + icalproperty *prop = icalcomponent_get_first_property(mComponent, kind); + if (!prop) { + *dtp = nullptr; /* invalid date */ + return NS_OK; + } + return calIcalProperty::getDatetime_(this, prop, dtp); +} + +nsresult calIcalComponent::SetDateTimeAttribute(icalproperty_kind kind, + calIDateTime * dt) +{ + ClearAllProperties(kind); + bool isValid; + if (!dt || NS_FAILED(dt->GetIsValid(&isValid)) || !isValid) { + return NS_OK; + } + icalproperty *prop = icalproperty_new(kind); + CAL_ENSURE_MEMORY(prop); + nsresult rc = calIcalProperty::setDatetime_(this, prop, dt); + if (NS_SUCCEEDED(rc)) + icalcomponent_add_property(mComponent, prop); + else + icalproperty_free(prop); + return rc; +} + +NS_IMETHODIMP +calIcalProperty::GetParent(calIIcalComponent** parent) +{ + NS_IF_ADDREF(*parent = mParent); + return NS_OK; +} + +NS_IMETHODIMP +calIcalProperty::GetIcalProperty(JS::MutableHandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calIcalProperty::SetIcalProperty(JS::HandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calIcalProperty::SetValueAsDatetime(calIDateTime *dt) +{ + NS_ENSURE_ARG_POINTER(dt); + return setDatetime_(toIcalComponent(mParent), mProperty, dt); +} + +nsresult calIcalProperty::setDatetime_(calIcalComponent * parent, + icalproperty * prop, + calIDateTime * dt) +{ + NS_ENSURE_ARG_POINTER(prop); + NS_ENSURE_ARG_POINTER(dt); + + nsresult rv; + nsCOMPtr icaldt = do_QueryInterface(dt, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + icaltimetype itt; + icaldt->ToIcalTime(&itt); + + if (parent) { + if (!itt.is_utc) { + nsCOMPtr tz; + rv = dt->GetTimezone(getter_AddRefs(tz)); + NS_ENSURE_SUCCESS(rv, rv); + if (itt.zone) { + rv = parent->getParentVCalendarOrThis()->AddTimezoneReference(tz); + NS_ENSURE_SUCCESS(rv, rv); + icalparameter * const param = icalparameter_new_from_value_string( + ICAL_TZID_PARAMETER, icaltimezone_get_tzid(const_cast(itt.zone))); + icalproperty_set_parameter(prop, param); + } else { // either floating or phantom: + bool b = false; + if (NS_FAILED(tz->GetIsFloating(&b)) || !b) { + // restore the same phantom TZID: + nsAutoCString tzid; + rv = tz->GetTzid(tzid); + NS_ENSURE_SUCCESS(rv, rv); + icalparameter * const param = icalparameter_new_from_value_string(ICAL_TZID_PARAMETER, + tzid.get()); + icalproperty_set_parameter(prop, param); + } + } + } + } else if (!itt.is_date && !itt.is_utc && itt.zone) { + // no parent to add the CTIMEZONE to: coerce DATETIMEs to UTC, DATEs to floating + icaltimezone_convert_time(&itt, + const_cast(itt.zone), + icaltimezone_get_utc_timezone()); + itt.zone = icaltimezone_get_utc_timezone(); + itt.is_utc = 1; + } + + icalvalue * const val = icalvalue_new_datetime(itt); + CAL_ENSURE_MEMORY(val); + icalproperty_set_value(prop, val); + return NS_OK; +} + +#define RO_COMP_DATE_ATTRIBUTE(Attrname, ICALNAME) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(calIDateTime **dtp) \ +{ \ + return GetDateTimeAttribute(ICAL_##ICALNAME##_PROPERTY, dtp); \ +} + +#define COMP_DATE_ATTRIBUTE(Attrname, ICALNAME) \ +RO_COMP_DATE_ATTRIBUTE(Attrname, ICALNAME) \ + \ +NS_IMETHODIMP \ +calIcalComponent::Set##Attrname(calIDateTime *dt) \ +{ \ + return SetDateTimeAttribute(ICAL_##ICALNAME##_PROPERTY, dt); \ +} + +#define RO_COMP_DURATION_ATTRIBUTE(Attrname, ICALNAME) \ +NS_IMETHODIMP \ +calIcalComponent::Get##Attrname(calIDuration **dtp) \ +{ \ + icalproperty *prop = \ + icalcomponent_get_first_property(mComponent, \ + ICAL_##ICALNAME##_PROPERTY); \ + if (!prop) { \ + *dtp = nullptr; /* invalid duration */ \ + return NS_OK; \ + } \ + struct icaldurationtype idt = \ + icalvalue_get_duration(icalproperty_get_value(prop)); \ + *dtp = new calDuration(&idt); \ + CAL_ENSURE_MEMORY(*dtp); \ + NS_ADDREF(*dtp); \ + return NS_OK; \ +} + + + +NS_IMPL_CLASSINFO(calIcalComponent, nullptr, nsIClassInfo::THREADSAFE, CAL_ICALCOMPONENT_CID) +NS_IMPL_ISUPPORTS_CI(calIcalComponent, calIIcalComponent, calIIcalComponentLibical) + +NS_IMETHODIMP_(icalcomponent *) +calIcalComponent::GetLibicalComponent() +{ + return mComponent; +} + +NS_IMETHODIMP_(icaltimezone *) +calIcalComponent::GetLibicalTimezone() +{ + NS_ASSERTION(icalcomponent_isa(mComponent) == ICAL_VTIMEZONE_COMPONENT, "no VTIMEZONE -- unexpected!"); + if (!mTimezone && (icalcomponent_isa(mComponent) == ICAL_VTIMEZONE_COMPONENT)) { + // xxx todo: libical needs a parent VCALENDAR to retrieve a icaltimezone + NS_ASSERTION(mParent, "VTIMEZONE has no parent!"); + if (mParent) { + icalproperty * const tzidProp = icalcomponent_get_first_property(mComponent, ICAL_TZID_PROPERTY); + NS_ASSERTION(tzidProp, "no TZID property in VTIMEZONE!?"); + if (tzidProp) { + mTimezone = icalcomponent_get_timezone(mParent->GetLibicalComponent(), + icalvalue_get_string(icalproperty_get_value(tzidProp))); + } + } + } + return mTimezone; +} + +NS_IMETHODIMP +calIcalComponent::GetFirstSubcomponent(const nsACString& kind, + calIIcalComponent **subcomp) +{ + NS_ENSURE_ARG_POINTER(subcomp); + + icalcomponent_kind compkind = + icalcomponent_string_to_kind(PromiseFlatCString(kind).get()); + + // Maybe someday I'll support X-COMPONENTs + if (compkind == ICAL_NO_COMPONENT || compkind == ICAL_X_COMPONENT) + return NS_ERROR_INVALID_ARG; + + icalcomponent *ical = + icalcomponent_get_first_component(mComponent, compkind); + if (!ical) { + *subcomp = nullptr; + return NS_OK; + } + + *subcomp = new calIcalComponent(ical, this); + CAL_ENSURE_MEMORY(*subcomp); + NS_ADDREF(*subcomp); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::GetNextSubcomponent(const nsACString& kind, + calIIcalComponent **subcomp) +{ + NS_ENSURE_ARG_POINTER(subcomp); + + icalcomponent_kind compkind = + icalcomponent_string_to_kind(PromiseFlatCString(kind).get()); + + // Maybe someday I'll support X-COMPONENTs + if (compkind == ICAL_NO_COMPONENT || compkind == ICAL_X_COMPONENT) + return NS_ERROR_INVALID_ARG; + + icalcomponent *ical = + icalcomponent_get_next_component(mComponent, compkind); + if (!ical) { + *subcomp = nullptr; + return NS_OK; + } + + *subcomp = new calIcalComponent(ical, this); + CAL_ENSURE_MEMORY(*subcomp); + NS_ADDREF(*subcomp); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::GetComponentType(nsACString &componentType) +{ + componentType.Assign(icalcomponent_kind_to_string(icalcomponent_isa(mComponent))); + return NS_OK; +} + + +COMP_STRING_ATTRIBUTE(Uid, UID, uid) +COMP_STRING_ATTRIBUTE(Prodid, PRODID, prodid) +COMP_STRING_ATTRIBUTE(Version, VERSION, version) +COMP_STRING_TO_ENUM_ATTRIBUTE(Method, METHOD, method) +COMP_STRING_TO_ENUM_ATTRIBUTE(Status, STATUS, status) +COMP_STRING_ATTRIBUTE(Summary, SUMMARY, summary) +COMP_STRING_ATTRIBUTE(Description, DESCRIPTION, description) +COMP_STRING_ATTRIBUTE(Location, LOCATION, location) +COMP_STRING_ATTRIBUTE(Categories, CATEGORIES, categories) +COMP_STRING_ATTRIBUTE(URL, URL, url) +COMP_INT_ATTRIBUTE(Priority, PRIORITY, priority) +RO_COMP_DURATION_ATTRIBUTE(Duration, DURATION) +COMP_DATE_ATTRIBUTE(StartTime, DTSTART) +COMP_DATE_ATTRIBUTE(EndTime, DTEND) +COMP_DATE_ATTRIBUTE(DueTime, DUE) +COMP_DATE_ATTRIBUTE(StampTime, DTSTAMP) +COMP_DATE_ATTRIBUTE(LastModified, LASTMODIFIED) +COMP_DATE_ATTRIBUTE(CreatedTime, CREATED) +COMP_DATE_ATTRIBUTE(CompletedTime, COMPLETED) +COMP_DATE_ATTRIBUTE(RecurrenceId, RECURRENCEID) + +void calIcalComponent::ClearAllProperties(icalproperty_kind kind) +{ + for (icalproperty *prop = icalcomponent_get_first_property(mComponent, kind), *next; + prop; prop = next) + { + next = icalcomponent_get_next_property(mComponent, kind); + icalcomponent_remove_property(mComponent, prop); + icalproperty_free(prop); + } +} + + +NS_IMETHODIMP +calIcalComponent::SerializeToICS(nsACString &serialized) +{ + char *icalstr; + + nsresult rv = Serialize(&icalstr); + if (NS_FAILED(rv)) { + return rv; + } + + serialized.Assign(icalstr); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::ToString(nsACString& aResult) +{ + return SerializeToICS(aResult); +} + +NS_IMETHODIMP +calIcalComponent::SerializeToICSStream(nsIInputStream **aStreamResult) +{ + NS_ENSURE_ARG_POINTER(aStreamResult); + + char *icalstr; + nsresult rv = Serialize(&icalstr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr const aStringStream( + do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + // copies the string into the input stream that's handed back. + // This copy is necessary because we don't really own icalstr; + // it's one of libical's ring buffers + rv = aStringStream->SetData(icalstr, -1); + NS_ENSURE_SUCCESS(rv, rv); + NS_ADDREF(*aStreamResult = aStringStream); + return rv; +} + +nsresult +calIcalComponent::Serialize(char **icalstr) +{ + NS_ENSURE_ARG_POINTER(icalstr); + + // add the timezone bits + if (icalcomponent_isa(mComponent) == ICAL_VCALENDAR_COMPONENT && mReferencedTimezones.Count() > 0) { + for (auto iter = mReferencedTimezones.ConstIter(); !iter.Done(); iter.Next() ) { + icaltimezone * icaltz = cal::getIcalTimezone(iter.Data()); + if (icaltz) { + icalcomponent * const tzcomp = icalcomponent_new_clone(icaltimezone_get_component(icaltz)); + icalcomponent_add_component(mComponent, tzcomp); + } + } + } + + *icalstr = icalcomponent_as_ical_string(mComponent); + if (!*icalstr) { + // xxx todo: what about NS_ERROR_OUT_OF_MEMORY? +#ifdef DEBUG + fprintf(stderr, "Error serializing: %d (%s)\n", + icalerrno, icalerror_strerror(icalerrno)); +#endif + // The return values in calIError match with libical errnos, + // so no need for a conversion table or anything. + return static_cast(calIErrors::ICS_ERROR_BASE + icalerrno); + } + + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::Clone(calIIcalComponent **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + icalcomponent * cloned = icalcomponent_new_clone(mComponent); + if (cloned == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + calIcalComponent * const comp = new calIcalComponent(cloned, nullptr, getTzProvider()); + if (comp == nullptr) { + icalcomponent_free(cloned); + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*_retval = comp); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::AddSubcomponent(calIIcalComponent *aComp) +{ + NS_ENSURE_ARG_POINTER(aComp); + + /* XXX mildly unsafe assumption here. + * To fix it, I will: + * - check the object's classinfo to find out if I have one of my + * own objects, and if not + * - use comp->serializeToICS and reparse to create a copy. + * + * I should probably also return the new/reused component so that the + * caller has something it can poke at all live-like. + */ + + nsresult rv; + nsCOMPtr icalcomp = do_QueryInterface(aComp, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + calIcalComponent * const ical = toIcalComponent(icalcomp); + + uint32_t tzCount = 0; + calITimezone ** timezones = nullptr; + rv = ical->GetReferencedTimezones(&tzCount, &timezones); + NS_ENSURE_SUCCESS(rv, rv); + + calIcalComponent * const vcal = getParentVCalendarOrThis(); + bool failed = false; + for (uint32_t i = 0; i < tzCount; i++) { + if (!failed) { + rv = vcal->AddTimezoneReference(timezones[i]); + if (NS_FAILED(rv)) + failed = true; + } + + NS_RELEASE(timezones[i]); + } + + free(timezones); + + if (failed) + return rv; + + if (ical->mParent) { + ical->mComponent = icalcomponent_new_clone(ical->mComponent); + } + ical->mParent = this; + icalcomponent_add_component(mComponent, ical->mComponent); + return NS_OK; +} + +// NS_IMETHODIMP +// IcalComponent::RemoveSubcomponent(calIIcalComponent *comp) +// { +// NS_ENSURE_ARG_POINTER(comp); +// calIcalComponent *ical = static_cast(comp); +// icalcomponent_remove_component(mComponent, ical->mComponent); +// ical->mParent = nullptr; +// return NS_OK; +// } + +NS_IMETHODIMP +calIcalComponent::GetFirstProperty(const nsACString &kind, + calIIcalProperty **prop) +{ + NS_ENSURE_ARG_POINTER(prop); + + icalproperty_kind propkind = + icalproperty_string_to_kind(PromiseFlatCString(kind).get()); + + if (propkind == ICAL_NO_PROPERTY) + return NS_ERROR_INVALID_ARG; + + icalproperty *icalprop = nullptr; + if (propkind == ICAL_X_PROPERTY) { + for (icalprop = + icalcomponent_get_first_property(mComponent, ICAL_X_PROPERTY); + icalprop; + icalprop = icalcomponent_get_next_property(mComponent, + ICAL_X_PROPERTY)) { + + if (kind.Equals(icalproperty_get_x_name(icalprop))) + break; + } + } else { + icalprop = icalcomponent_get_first_property(mComponent, propkind); + } + + if (!icalprop) { + *prop = nullptr; + return NS_OK; + } + + *prop = new calIcalProperty(icalprop, this); + CAL_ENSURE_MEMORY(*prop); + NS_ADDREF(*prop); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::GetNextProperty(const nsACString &kind, calIIcalProperty **prop) +{ + NS_ENSURE_ARG_POINTER(prop); + + icalproperty_kind propkind = + icalproperty_string_to_kind(PromiseFlatCString(kind).get()); + + if (propkind == ICAL_NO_PROPERTY) + return NS_ERROR_INVALID_ARG; + icalproperty *icalprop = nullptr; + if (propkind == ICAL_X_PROPERTY) { + for (icalprop = + icalcomponent_get_next_property(mComponent, ICAL_X_PROPERTY); + icalprop; + icalprop = icalcomponent_get_next_property(mComponent, + ICAL_X_PROPERTY)) { + + if (kind.Equals(icalproperty_get_x_name(icalprop))) + break; + } + } else { + icalprop = icalcomponent_get_next_property(mComponent, propkind); + } + + if (!icalprop) { + *prop = nullptr; + return NS_OK; + } + + *prop = new calIcalProperty(icalprop, this); + CAL_ENSURE_MEMORY(*prop); + NS_ADDREF(*prop); + return NS_OK; +} + +NS_IMETHODIMP +calIcalComponent::AddProperty(calIIcalProperty * aProp) +{ + NS_ENSURE_ARG_POINTER(aProp); + // We assume a calIcalProperty is passed in (else the cast wouldn't run and + // we are about to crash), so we assume that this ICS service code has created + // the property. + + nsresult rv; + nsCOMPtr icalprop = do_QueryInterface(aProp, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + calIcalProperty * const ical = toIcalProperty(icalprop); + if (ical->mParent) { + ical->mProperty = icalproperty_new_clone(ical->mProperty); + } + ical->mParent = this; + icalcomponent_add_property(mComponent, ical->mProperty); + + nsCOMPtr dt; + if (NS_SUCCEEDED(aProp->GetValueAsDatetime(getter_AddRefs(dt))) && dt) { + // make sure timezone definition will be included: + nsCOMPtr tz; + if (NS_SUCCEEDED(dt->GetTimezone(getter_AddRefs(tz))) && tz) { + getParentVCalendarOrThis()->AddTimezoneReference(tz); + } + } + return NS_OK; +} + +// If you add then remove a property/component, the referenced +// timezones won't get purged out. There's currently no client code. + +// NS_IMETHODIMP +// calIcalComponent::RemoveProperty(calIIcalProperty *prop) +// { +// NS_ENSURE_ARG_POINTER(prop); +// // XXX like AddSubcomponent, this is questionable +// calIcalProperty *ical = static_cast(prop); +// icalcomponent_remove_property(mComponent, ical->mProperty); +// ical->mParent = nullptr; +// return NS_OK; +// } + +NS_IMPL_CLASSINFO(calICSService, nullptr, nsIClassInfo::THREADSAFE, CAL_ICSSERVICE_CID) +NS_IMPL_ISUPPORTS_CI(calICSService, calIICSService) + +calICSService::calICSService() +{ +} + +NS_IMETHODIMP +calICSService::ParseICS(const nsACString& serialized, + calITimezoneProvider *tzProvider, + calIIcalComponent **component) +{ + NS_ENSURE_ARG_POINTER(component); + icalcomponent *ical = + icalparser_parse_string(PromiseFlatCString(serialized).get()); + if (!ical) { +#ifdef DEBUG + fprintf(stderr, "Error parsing: '%20s': %d (%s)\n", + PromiseFlatCString(serialized).get(), icalerrno, + icalerror_strerror(icalerrno)); +#endif + // The return values is calIError match with ical errors, + // so no need for a conversion table or anything. + return static_cast(calIErrors::ICS_ERROR_BASE + icalerrno); + } + calIcalComponent *comp = new calIcalComponent(ical, nullptr, tzProvider); + if (!comp) { + icalcomponent_free(ical); + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*component = comp); + return NS_OK; +} + +NS_IMETHODIMP +calICSService::ParserWorker::Run() +{ + icalcomponent *ical = icalparser_parse_string(mString.get()); + nsresult status = NS_OK; + calIIcalComponent *comp = nullptr; + + if (ical) { + comp = new calIcalComponent(ical, nullptr, mProvider); + if (!comp) { + icalcomponent_free(ical); + status = NS_ERROR_OUT_OF_MEMORY; + } + } else { + status = static_cast(calIErrors::ICS_ERROR_BASE + icalerrno); + } + + nsCOMPtr completer = new ParserWorkerCompleter(mWorkerThread, status, + comp, mListener); + mMainThread->Dispatch(completer, NS_DISPATCH_NORMAL); + + mWorkerThread = nullptr; + mMainThread = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +calICSService::ParserWorker::ParserWorkerCompleter::Run() +{ + mListener->OnParsingComplete(mStatus, mComp); + + nsresult rv = mWorkerThread->Shutdown(); + NS_ENSURE_SUCCESS(rv, rv); + + mWorkerThread = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +calICSService::ParseICSAsync(const nsACString& serialized, + calITimezoneProvider *tzProvider, + calIIcsComponentParsingListener *listener) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(listener); + + nsCOMPtr workerThread; + nsCOMPtr currentThread; + rv = NS_GetCurrentThread(getter_AddRefs(currentThread)); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewThread(getter_AddRefs(workerThread)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr worker = new ParserWorker(currentThread, workerThread, + serialized, tzProvider, listener); + NS_ENSURE_TRUE(worker, NS_ERROR_OUT_OF_MEMORY); + + rv = workerThread->Dispatch(worker, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +calICSService::CreateIcalComponent(const nsACString &kind, calIIcalComponent **comp) +{ + NS_ENSURE_ARG_POINTER(comp); + icalcomponent_kind compkind = + icalcomponent_string_to_kind(PromiseFlatCString(kind).get()); + + // Maybe someday I'll support X-COMPONENTs + if (compkind == ICAL_NO_COMPONENT || compkind == ICAL_X_COMPONENT) + return NS_ERROR_INVALID_ARG; + + icalcomponent *ical = icalcomponent_new(compkind); + if (!ical) + return NS_ERROR_OUT_OF_MEMORY; // XXX translate + + *comp = new calIcalComponent(ical, nullptr); + if (!*comp) { + icalcomponent_free(ical); + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(*comp); + return NS_OK; +} + +NS_IMETHODIMP +calICSService::CreateIcalProperty(const nsACString &kind, calIIcalProperty **prop) +{ + NS_ENSURE_ARG_POINTER(prop); + icalproperty_kind propkind = + icalproperty_string_to_kind(PromiseFlatCString(kind).get()); + + if (propkind == ICAL_NO_PROPERTY) + return NS_ERROR_INVALID_ARG; + + icalproperty *icalprop = icalproperty_new(propkind); + if (!icalprop) + return NS_ERROR_OUT_OF_MEMORY; // XXX translate + + if (propkind == ICAL_X_PROPERTY) + icalproperty_set_x_name(icalprop, PromiseFlatCString(kind).get()); + + *prop = new calIcalProperty(icalprop, nullptr); + CAL_ENSURE_MEMORY(*prop); + NS_ADDREF(*prop); + return NS_OK; +} + +NS_IMETHODIMP +calICSService::CreateIcalPropertyFromString(const nsACString &str, calIIcalProperty **prop) +{ + NS_ENSURE_ARG_POINTER(prop); + + icalproperty *icalprop = icalproperty_new_from_string(PromiseFlatCString(str).get()); + + *prop = new calIcalProperty(icalprop, nullptr); + CAL_ENSURE_MEMORY(*prop); + NS_ADDREF(*prop); + return NS_OK; +} diff --git a/calendar/base/backend/libical/calICSService.h b/calendar/base/backend/libical/calICSService.h new file mode 100644 index 00000000..16634394 --- /dev/null +++ b/calendar/base/backend/libical/calICSService.h @@ -0,0 +1,178 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(INCLUDED_CALICSSERVICE_H) +#define INCLUDED_CALICSSERVICE_H + +#include "nsCOMPtr.h" +#include "calIICSService.h" +#include "calITimezoneProvider.h" +#include "nsInterfaceHashtable.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "calUtils.h" + +extern "C" { +#include "ical.h" +} + +class calICSService : public calIICSService, + public cal::XpcomBase +{ +protected: + virtual ~calICSService() {} + class ParserWorker : public mozilla::Runnable { + public: + ParserWorker(nsIThread *mainThread, + nsIThread *workerThread, + const nsACString &icsString, + calITimezoneProvider *tzProvider, + calIIcsComponentParsingListener *listener) : + mString(icsString), mProvider(tzProvider), + mMainThread(mainThread), mWorkerThread(workerThread) + { + mListener = new nsMainThreadPtrHolder(listener); + } + + NS_DECL_NSIRUNNABLE + + protected: + nsCString mString; + nsCOMPtr mProvider; + nsMainThreadPtrHandle mListener; + nsCOMPtr mMainThread; + nsCOMPtr mWorkerThread; + + class ParserWorkerCompleter : public mozilla::Runnable { + public: + ParserWorkerCompleter(nsIThread *workerThread, + nsresult status, + calIIcalComponent *component, + const nsMainThreadPtrHandle &listener) : + mWorkerThread(workerThread), mListener(listener), + mComp(component), mStatus(status) + { + } + + NS_DECL_NSIRUNNABLE + protected: + nsCOMPtr mWorkerThread; + nsMainThreadPtrHandle mListener; + nsCOMPtr mComp; + nsresult mStatus; + }; + }; +public: + calICSService(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_CALIICSSERVICE +}; + +class calIcalComponent; + +class calIcalProperty : public calIIcalPropertyLibical, + public cal::XpcomBase +{ + friend class calIcalComponent; +public: + calIcalProperty(icalproperty * prop, calIIcalComponentLibical * parent) + : mProperty(prop), mParent(parent) {} + + NS_DECL_ISUPPORTS + NS_DECL_CALIICALPROPERTY + NS_DECL_CALIICALPROPERTYLIBICAL + +protected: + virtual ~calIcalProperty(); + + static nsresult getDatetime_(calIcalComponent *parent, + icalproperty *prop, + calIDateTime **dtp); + static nsresult setDatetime_(calIcalComponent *parent, + icalproperty *prop, + calIDateTime *dt); + + icalproperty * mProperty; + nsCOMPtr mParent; +}; + +class calIcalComponent : public calIIcalComponentLibical, + public cal::XpcomBase +{ + friend class calIcalProperty; +public: + calIcalComponent(icalcomponent *ical, calIIcalComponentLibical *parent, + calITimezoneProvider *tzProvider = nullptr) + : mComponent(ical), mTimezone(nullptr), mTzProvider(tzProvider), mParent(parent) + { + } + + // VTIMEZONE ctor + calIcalComponent(icaltimezone * icaltz, icalcomponent * ical) : mComponent(ical), mTimezone(icaltz) { + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_CALIICALCOMPONENT + NS_DECL_CALIICALCOMPONENTLIBICAL + +protected: + virtual ~calIcalComponent(); + + calITimezoneProvider * getTzProvider() const { + // walk up the parents to find a tz provider: + calIcalComponent const * that = this; + while (that) { + calITimezoneProvider * const ret = that->mTzProvider; + if (ret) { + return ret; + } + calIIcalComponentLibical * const p = that->mParent; + that = static_cast(p); + } + return nullptr; + } + + calIcalComponent * getParentVCalendarOrThis() { + // walk up the parents to find a VCALENDAR: + calIcalComponent * that = this; + while (that && icalcomponent_isa(that->mComponent) != ICAL_VCALENDAR_COMPONENT) { + calIIcalComponentLibical * const p = that->mParent; + that = static_cast(p); + } + if (!that) + that = this; + return that; + } + + nsresult GetDateTimeAttribute(icalproperty_kind kind, calIDateTime ** dtp); + nsresult SetDateTimeAttribute(icalproperty_kind kind, calIDateTime * dt); + + nsresult SetPropertyValue(icalproperty_kind kind, icalvalue *val); + nsresult SetProperty(icalproperty_kind kind, icalproperty *prop); + + nsresult GetStringProperty(icalproperty_kind kind, nsACString &str); + nsresult SetStringProperty(icalproperty_kind kind, const nsACString &str); + + nsresult GetIntProperty(icalproperty_kind kind, int32_t *valp); + nsresult SetIntProperty(icalproperty_kind kind, int32_t i); + + void ClearAllProperties(icalproperty_kind kind); + + nsresult Serialize(char ** icalstr); + + nsInterfaceHashtable mReferencedTimezones; + icalcomponent * mComponent; + icaltimezone * mTimezone; // set iff VTIMEZONE + nsCOMPtr const mTzProvider; + nsCOMPtr mParent; +}; + +inline calIcalProperty * toIcalProperty(calIIcalPropertyLibical * p) { + return static_cast(p); +} +inline calIcalComponent * toIcalComponent(calIIcalComponentLibical * p) { + return static_cast(p); +} + +#endif // INCLUDED_CALICSSERVICE_H diff --git a/calendar/base/backend/libical/calPeriod.cpp b/calendar/base/backend/libical/calPeriod.cpp new file mode 100644 index 00000000..e90935f4 --- /dev/null +++ b/calendar/base/backend/libical/calPeriod.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "calPeriod.h" +#include "calBaseCID.h" + +#include "nsIClassInfoImpl.h" + +#include "calUtils.h" + +NS_IMPL_CLASSINFO(calPeriod, nullptr, 0, CAL_PERIOD_CID) +NS_IMPL_ISUPPORTS_CI(calPeriod, calIPeriod, calIPeriodLibical) + +calPeriod::calPeriod() + : mImmutable(false) +{ +} + +calPeriod::calPeriod(const calPeriod& cpt) + : mImmutable(false) +{ + if (cpt.mStart) { + nsCOMPtr start; + cpt.mStart->Clone(getter_AddRefs(start)); + mStart = do_QueryInterface(start); + } + if (cpt.mEnd) { + nsCOMPtr end; + cpt.mEnd->Clone(getter_AddRefs(end)); + mEnd = do_QueryInterface(end); + } +} + +calPeriod::calPeriod(struct icalperiodtype const* aPeriodPtr) + : mImmutable(false) +{ + FromIcalPeriod(aPeriodPtr); +} + +NS_IMETHODIMP +calPeriod::GetIcalPeriod(JS::MutableHandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calPeriod::SetIcalPeriod(JS::HandleValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calPeriod::GetIsMutable(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = !mImmutable; + return NS_OK; +} + +NS_IMETHODIMP +calPeriod::MakeImmutable() +{ + mImmutable = true; + return NS_OK; +} + +NS_IMETHODIMP +calPeriod::Clone(calIPeriod **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + calPeriod *cpt = new calPeriod(*this); + if (!cpt) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult = cpt); + return NS_OK; +} + + +NS_IMETHODIMP calPeriod::GetStart(calIDateTime **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mStart; + NS_IF_ADDREF(*_retval); + return NS_OK; +} +NS_IMETHODIMP calPeriod::SetStart(calIDateTime *aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + if (mImmutable) + return NS_ERROR_OBJECT_IS_IMMUTABLE; + + mStart = do_QueryInterface(aValue); + return mStart->MakeImmutable(); +} + +NS_IMETHODIMP calPeriod::GetEnd(calIDateTime **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mEnd; + NS_IF_ADDREF(*_retval); + return NS_OK; +} +NS_IMETHODIMP calPeriod::SetEnd(calIDateTime *aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + if (mImmutable) + return NS_ERROR_OBJECT_IS_IMMUTABLE; + + mEnd = do_QueryInterface(aValue); + return mEnd->MakeImmutable(); +} + +NS_IMETHODIMP calPeriod::GetDuration(calIDuration **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + if (!mStart || !mEnd) + return NS_ERROR_UNEXPECTED; + return mEnd->SubtractDate(mStart, _retval); +} + +NS_IMETHODIMP +calPeriod::ToString(nsACString& aResult) +{ + return GetIcalString(aResult); +} + +NS_IMETHODIMP_(void) +calPeriod::ToIcalPeriod(struct icalperiodtype *icalp) +{ + // makes no sense to create a duration without bath a start and end + if (!mStart || !mEnd) { + *icalp = icalperiodtype_null_period(); + return; + } + + mStart->ToIcalTime(&icalp->start); + mEnd->ToIcalTime(&icalp->end); +} + +void +calPeriod::FromIcalPeriod(struct icalperiodtype const* icalp) +{ + mStart = new calDateTime(&(icalp->start), nullptr); + mStart->MakeImmutable(); + mEnd = new calDateTime(&(icalp->end), nullptr); + mEnd->MakeImmutable(); + return; +} + +NS_IMETHODIMP +calPeriod::GetIcalString(nsACString& aResult) +{ + struct icalperiodtype ip; + ToIcalPeriod(&ip); + + // note that ics is owned by libical, so we don't need to free + const char *ics = icalperiodtype_as_ical_string(ip); + + if (ics) { + aResult.Assign(ics); + return NS_OK; + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +calPeriod::SetIcalString(const nsACString& aIcalString) +{ + if (mImmutable) + return NS_ERROR_OBJECT_IS_IMMUTABLE; + struct icalperiodtype ip; + ip = icalperiodtype_from_string(PromiseFlatCString(aIcalString).get()); + //XXX Shortcut. Assumes nobody tried to overrule our impl. of calIDateTime + mStart = new calDateTime(&ip.start, nullptr); + if (icaltime_is_null_time(ip.end)) { + struct icaltimetype end; + end = icaltime_add(ip.start, ip.duration); + mEnd = new calDateTime(&end, nullptr); + } else { + mEnd = new calDateTime(&ip.end, nullptr); + } + return NS_OK; +} diff --git a/calendar/base/backend/libical/calPeriod.h b/calendar/base/backend/libical/calPeriod.h new file mode 100644 index 00000000..b53e7268 --- /dev/null +++ b/calendar/base/backend/libical/calPeriod.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CALPERIOD_H_ +#define CALPERIOD_H_ + +#include "nsCOMPtr.h" + +#include "calIPeriod.h" +#include "calDateTime.h" +#include "calIDuration.h" + +extern "C" { + #include "ical.h" +} + +class calPeriod final : public calIPeriodLibical +{ +public: + calPeriod (); + explicit calPeriod (const calPeriod& cpt); + explicit calPeriod (struct icalperiodtype const* aPeriodPtr); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // calIPeriod interface + NS_DECL_CALIPERIOD + NS_DECL_CALIPERIODLIBICAL + +protected: + ~calPeriod() {} + calPeriod const& operator=(calPeriod const&); + + bool mImmutable; + + //struct icaldurationtype mPeriod; + nsCOMPtr mStart; + nsCOMPtr mEnd; + + void FromIcalPeriod(struct icalperiodtype const* icalp); +}; + +#endif /* CALPERIOD_H_ */ + diff --git a/calendar/base/backend/libical/calRecurrenceRule.cpp b/calendar/base/backend/libical/calRecurrenceRule.cpp new file mode 100644 index 00000000..fe659b6e --- /dev/null +++ b/calendar/base/backend/libical/calRecurrenceRule.cpp @@ -0,0 +1,633 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMArray.h" + +#include "calRecurrenceRule.h" + +#include "calDateTime.h" +#include "calIItemBase.h" +#include "calIEvent.h" + +#include "calICSService.h" + +#include "nsIClassInfoImpl.h" + +#include + +NS_IMPL_CLASSINFO(calRecurrenceRule, NULL, 0, CAL_RECURRENCERULE_CID) +NS_IMPL_ISUPPORTS_CI(calRecurrenceRule, calIRecurrenceItem, calIRecurrenceRule) + +calRecurrenceRule::calRecurrenceRule() + : mImmutable(false), + mIsNegative(false), + mIsByCount(false) +{ + icalrecurrencetype_clear(&mIcalRecur); +} + +NS_IMETHODIMP +calRecurrenceRule::GetIsMutable(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = !mImmutable; + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::MakeImmutable() +{ + mImmutable = true; + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::Clone(calIRecurrenceItem **aResult) +{ + calRecurrenceRule * const crc = new calRecurrenceRule(); + CAL_ENSURE_MEMORY(crc); + + crc->mIsNegative = mIsNegative; + crc->mIsByCount = mIsByCount; + crc->mIcalRecur = mIcalRecur; + + NS_ADDREF(*aResult = crc); + return NS_OK; +} + +/* attribute boolean isNegative; */ +NS_IMETHODIMP +calRecurrenceRule::GetIsNegative(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mIsNegative; + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetIsNegative(bool aIsNegative) +{ + if (mImmutable) + return NS_ERROR_OBJECT_IS_IMMUTABLE; + mIsNegative = aIsNegative; + return NS_OK; +} + +/* readonly attribute boolean isFinite; */ +NS_IMETHODIMP +calRecurrenceRule::GetIsFinite(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + if ((mIsByCount && mIcalRecur.count == 0) || + (!mIsByCount && icaltime_is_null_time(mIcalRecur.until))) + { + *_retval = false; + } else { + *_retval = true; + } + return NS_OK; +} + +/* attribute long type; */ +NS_IMETHODIMP +calRecurrenceRule::GetType(nsACString &aType) +{ + switch (mIcalRecur.freq) { +#define RECUR_HELPER(x) \ + case ICAL_##x##_RECURRENCE: aType.AssignLiteral( #x ); break + RECUR_HELPER(SECONDLY); + RECUR_HELPER(MINUTELY); + RECUR_HELPER(HOURLY); + RECUR_HELPER(DAILY); + RECUR_HELPER(WEEKLY); + RECUR_HELPER(MONTHLY); + RECUR_HELPER(YEARLY); +#undef RECUR_HELPER + default: + aType.AssignLiteral(""); + } + + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetType(const nsACString &aType) +{ +#define RECUR_HELPER(x) \ + if (aType.EqualsLiteral( #x )) mIcalRecur.freq = ICAL_##x##_RECURRENCE + RECUR_HELPER(SECONDLY); + else RECUR_HELPER(MINUTELY); + else RECUR_HELPER(HOURLY); + else RECUR_HELPER(DAILY); + else RECUR_HELPER(WEEKLY); + else RECUR_HELPER(MONTHLY); + else RECUR_HELPER(YEARLY); +#undef RECUR_HELPER + else if (aType.IsEmpty() || aType.EqualsLiteral("")) + mIcalRecur.freq = ICAL_NO_RECURRENCE; + else + return NS_ERROR_FAILURE; + + return NS_OK; +} + +/* attribute long count; */ +NS_IMETHODIMP +calRecurrenceRule::GetCount(int32_t *aRecurCount) +{ + NS_ENSURE_ARG_POINTER(aRecurCount); + + if (!mIsByCount) + return NS_ERROR_FAILURE; + + if (mIcalRecur.count == 0 && icaltime_is_null_time(mIcalRecur.until)) { + *aRecurCount = -1; + } else if (mIcalRecur.count) { + *aRecurCount = mIcalRecur.count; + } else { + // count wasn't set, so we don't know + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetCount(int32_t aRecurCount) +{ + if (aRecurCount != -1) { + if (aRecurCount < 0 || aRecurCount > INT_MAX) + return NS_ERROR_ILLEGAL_VALUE; + mIcalRecur.count = static_cast(aRecurCount); + mIsByCount = true; + } else { + mIcalRecur.count = 0; + mIsByCount = false; + } + + mIcalRecur.until = icaltime_null_time(); + + + return NS_OK; +} + +/* attribute calIDateTime untilDate; */ +NS_IMETHODIMP +calRecurrenceRule::GetUntilDate(calIDateTime * *aRecurEnd) +{ + NS_ENSURE_ARG_POINTER(aRecurEnd); + + if (mIsByCount) + return NS_ERROR_FAILURE; + + if (!icaltime_is_null_time(mIcalRecur.until)) { + *aRecurEnd = new calDateTime(&mIcalRecur.until, nullptr); + CAL_ENSURE_MEMORY(*aRecurEnd); + NS_ADDREF(*aRecurEnd); + } else { + // infinite recurrence + *aRecurEnd = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetUntilDate(calIDateTime * aRecurEnd) +{ + if (aRecurEnd) { + nsresult rv; + bool b; + nsCOMPtr icaldt; + nsCOMPtr tz; + aRecurEnd->GetTimezone(getter_AddRefs(tz)); + + if (NS_SUCCEEDED(tz->GetIsUTC(&b)) && !b && + NS_SUCCEEDED(tz->GetIsFloating(&b)) && !b) { + // convert to UTC: + nsCOMPtr dt; + nsCOMPtr ctz = cal::UTC(); + aRecurEnd->GetInTimezone(ctz, getter_AddRefs(dt)); + icaldt = do_QueryInterface(dt, &rv); + } else { + icaldt = do_QueryInterface(aRecurEnd, &rv); + } + + NS_ENSURE_SUCCESS(rv, rv); + struct icaltimetype itt; + icaldt->ToIcalTime(&itt); + + mIcalRecur.until = itt; + } else { + mIcalRecur.until = icaltime_null_time(); + } + + mIcalRecur.count = 0; + + mIsByCount = false; + + return NS_OK; +} + +/* readonly attribute boolean isByCount; */ +NS_IMETHODIMP +calRecurrenceRule::GetIsByCount (bool *aIsByCount) +{ + *aIsByCount = mIsByCount; + return NS_OK; +} + +/* attribute long interval; */ +NS_IMETHODIMP +calRecurrenceRule::GetInterval(int32_t *aInterval) +{ + NS_ENSURE_ARG_POINTER(aInterval); + *aInterval = mIcalRecur.interval; + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetInterval(int32_t aInterval) +{ + if (aInterval < 0 || aInterval > SHRT_MAX) + return NS_ERROR_ILLEGAL_VALUE; + mIcalRecur.interval = static_cast(aInterval); + return NS_OK; +} + +/* void getComponent (in AUTF8String aComponentType, out unsigned long aCount, [array, size_is (aCount), retval] out long aValues); */ +NS_IMETHODIMP +calRecurrenceRule::GetComponent(const nsACString &aComponentType, uint32_t *aCount, int16_t **aValues) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aValues); + + // This little ugly macro counts the number of real entries + // we have in the relevant array, and then clones it to the result. +#define HANDLE_COMPONENT(_comptype,_icalvar,_icalmax) \ + if (aComponentType.EqualsLiteral( #_comptype )) { \ + int count; \ + for (count = 0; count < _icalmax; count++) { \ + if (mIcalRecur._icalvar[count] == ICAL_RECURRENCE_ARRAY_MAX) \ + break; \ + } \ + if (count) { \ + *aValues = (int16_t*) nsMemory::Clone(mIcalRecur._icalvar, \ + count * sizeof(int16_t)); \ + if (!*aValues) return NS_ERROR_OUT_OF_MEMORY; \ + } else { \ + *aValues = nullptr; \ + } \ + *aCount = count; \ + } + + HANDLE_COMPONENT(BYSECOND, by_second, ICAL_BY_SECOND_SIZE) + else HANDLE_COMPONENT(BYMINUTE, by_minute, ICAL_BY_MINUTE_SIZE) + else HANDLE_COMPONENT(BYHOUR, by_hour, ICAL_BY_HOUR_SIZE) + else HANDLE_COMPONENT(BYDAY, by_day, ICAL_BY_DAY_SIZE) // special + else HANDLE_COMPONENT(BYMONTHDAY, by_month_day, ICAL_BY_MONTHDAY_SIZE) + else HANDLE_COMPONENT(BYYEARDAY, by_year_day, ICAL_BY_YEARDAY_SIZE) + else HANDLE_COMPONENT(BYWEEKNO, by_week_no, ICAL_BY_WEEKNO_SIZE) + else HANDLE_COMPONENT(BYMONTH, by_month, ICAL_BY_MONTH_SIZE) + else HANDLE_COMPONENT(BYSETPOS, by_set_pos, ICAL_BY_SETPOS_SIZE) + else { + // invalid component; XXX - error code + return NS_ERROR_FAILURE; + } +#undef HANDLE_COMPONENT + + return NS_OK; +} + +/* void setComponent (in AUTF8String aComponentType, in unsigned long aCount, [array, size_is (aCount)] in long aValues); */ +NS_IMETHODIMP +calRecurrenceRule::SetComponent(const nsACString& aComponentType, uint32_t aCount, int16_t *aValues) +{ + NS_ENSURE_ARG_POINTER(aValues); + + // Copy the passed-in array into the ical structure array +#define HANDLE_COMPONENT(_comptype,_icalvar,_icalmax) \ + if (aComponentType.EqualsLiteral( #_comptype )) { \ + if (aCount > _icalmax) \ + return NS_ERROR_FAILURE; \ + memcpy(mIcalRecur._icalvar, aValues, aCount * sizeof(int16_t)); \ + if (aCount < _icalmax) \ + mIcalRecur._icalvar[aCount] = ICAL_RECURRENCE_ARRAY_MAX; \ + } + + HANDLE_COMPONENT(BYSECOND, by_second, ICAL_BY_SECOND_SIZE) + else HANDLE_COMPONENT(BYMINUTE, by_minute, ICAL_BY_MINUTE_SIZE) + else HANDLE_COMPONENT(BYHOUR, by_hour, ICAL_BY_HOUR_SIZE) + else HANDLE_COMPONENT(BYDAY, by_day, ICAL_BY_DAY_SIZE) // special + else HANDLE_COMPONENT(BYMONTHDAY, by_month_day, ICAL_BY_MONTHDAY_SIZE) + else HANDLE_COMPONENT(BYYEARDAY, by_year_day, ICAL_BY_YEARDAY_SIZE) + else HANDLE_COMPONENT(BYWEEKNO, by_week_no, ICAL_BY_WEEKNO_SIZE) + else HANDLE_COMPONENT(BYMONTH, by_month, ICAL_BY_MONTH_SIZE) + else HANDLE_COMPONENT(BYSETPOS, by_set_pos, ICAL_BY_SETPOS_SIZE) + else { + // invalid component; XXX - error code + return NS_ERROR_FAILURE; + } +#undef HANDLE_COMPONENT + + return NS_OK; +} + +/* calIDateTime getNextOccurrence (in calIDateTime aStartTime, in calIDateTime aOccurrenceTime); */ +NS_IMETHODIMP +calRecurrenceRule::GetNextOccurrence(calIDateTime *aStartTime, + calIDateTime *aOccurrenceTime, + calIDateTime **_retval) +{ + NS_ENSURE_ARG_POINTER(aStartTime); + NS_ENSURE_ARG_POINTER(aOccurrenceTime); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + + nsCOMPtr icaldtstart = do_QueryInterface(aStartTime, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr icaloccurtime = do_QueryInterface(aOccurrenceTime, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + struct icaltimetype dtstart; + icaldtstart->ToIcalTime(&dtstart); + + struct icaltimetype occurtime; + icaloccurtime->ToIcalTime(&occurtime); + + icalrecur_iterator* recur_iter; + recur_iter = icalrecur_iterator_new(mIcalRecur, dtstart); + if (!recur_iter) + return NS_ERROR_OUT_OF_MEMORY; + + struct icaltimetype next = icalrecur_iterator_next(recur_iter); + while (!icaltime_is_null_time(next)) { + if (icaltime_compare(next, occurtime) > 0) + break; + + next = icalrecur_iterator_next(recur_iter); + } + + icalrecur_iterator_free(recur_iter); + + if (icaltime_is_null_time(next)) { + *_retval = nullptr; + return NS_OK; + } + + nsCOMPtr tz; + aStartTime->GetTimezone(getter_AddRefs(tz)); + *_retval = new calDateTime(&next, tz); + CAL_ENSURE_MEMORY(*_retval); + NS_ADDREF(*_retval); + return NS_OK; +} + +static inline icaltimetype ensureDateTime(icaltimetype const& icalt) { + if (!icalt.is_date) { + return icalt; + } else { + icaltimetype ret = icalt; + ret.is_date = 0; + ret.hour = 0; + ret.minute = 0; + ret.second = 0; + return ret; + } +} + +NS_IMETHODIMP +calRecurrenceRule::GetOccurrences(calIDateTime *aStartTime, + calIDateTime *aRangeStart, + calIDateTime *aRangeEnd, + uint32_t aMaxCount, + uint32_t *aCount, calIDateTime ***aDates) +{ + NS_ENSURE_ARG_POINTER(aStartTime); + NS_ENSURE_ARG_POINTER(aRangeStart); + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aDates); + + // make sure the request is sane; infinite recurrence + // with no end time is bad times. + if (!aMaxCount && !aRangeEnd && mIcalRecur.count == 0 && icaltime_is_null_time(mIcalRecur.until)) + return NS_ERROR_INVALID_ARG; + + nsCOMArray dates; + +#ifdef DEBUG_dbo + { + char const * const ss = icalrecurrencetype_as_string(&mIcalRecur); + nsAutoCString tst, tend; + aRangeStart->ToString(tst); + aRangeEnd->ToString(tend); + printf("RULE: [%s -> %s, %d]: %s\n", tst.get(), tend.get(), mIcalRecur.count, ss); + } +#endif + + nsresult rv; + + nsCOMPtr icalrangestart = do_QueryInterface(aRangeStart, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr icaldtstart = do_QueryInterface(aStartTime, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + struct icaltimetype rangestart, dtstart, dtend; + icalrangestart->ToIcalTime(&rangestart); + rangestart = ensureDateTime(rangestart); + icaldtstart->ToIcalTime(&dtstart); + nsCOMPtr tz; + aStartTime->GetTimezone(getter_AddRefs(tz)); + + if (aRangeEnd) { + nsCOMPtr icalrangeend = do_QueryInterface(aRangeEnd, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + icalrangeend->ToIcalTime(&dtend); + dtend = ensureDateTime(dtend); + + // if the start of the recurrence is past the end, + // we have no dates + if (icaltime_compare (dtstart, dtend) >= 0) { + *aDates = nullptr; + *aCount = 0; + return NS_OK; + } + } + + icalrecur_iterator* recur_iter; + recur_iter = icalrecur_iterator_new(mIcalRecur, dtstart); + if (!recur_iter) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t count = 0; + + for (icaltimetype next = icalrecur_iterator_next(recur_iter); + !icaltime_is_null_time(next); + next = icalrecur_iterator_next(recur_iter)) + { + icaltimetype const dtNext(ensureDateTime(next)); + + // if this thing is before the range start + if (icaltime_compare(dtNext, rangestart) < 0) { + continue; + } + + if (aRangeEnd && icaltime_compare(dtNext, dtend) >= 0) + break; + + calIDateTime * const cdt = new calDateTime(&next, tz); + if (!cdt) { + icalrecur_iterator_free(recur_iter); + return NS_ERROR_OUT_OF_MEMORY; + } + + dates.AppendObject(cdt); +#ifdef DEBUG_dbo + { + nsAutoCString str; + cdt->ToString(str); + printf(" occ: %s\n", str.get()); + } +#endif + count++; + if (aMaxCount && aMaxCount <= count) + break; + } + + icalrecur_iterator_free(recur_iter); + + if (count) { + calIDateTime ** const dateArray = + static_cast(moz_xmalloc(sizeof(calIDateTime*) * count)); + CAL_ENSURE_MEMORY(dateArray); + for (uint32_t i = 0; i < count; ++i) { + NS_ADDREF(dateArray[i] = dates[i]); + } + *aDates = dateArray; + } else { + *aDates = nullptr; + } + + *aCount = count; + + return NS_OK; +} + +/** + ** ical property getting/setting + **/ +NS_IMETHODIMP +calRecurrenceRule::GetIcalProperty(calIIcalProperty **prop) +{ + icalproperty * const rrule = icalproperty_new_rrule(mIcalRecur); + CAL_ENSURE_MEMORY(rrule); + *prop = new calIcalProperty(rrule, nullptr); + if (!*prop) { + icalproperty_free(rrule); + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*prop); + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetIcalProperty(calIIcalProperty *aProp) +{ + NS_ENSURE_ARG_POINTER(aProp); + nsresult rv; + + nsCOMPtr icalprop = do_QueryInterface(aProp, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (mImmutable) + return NS_ERROR_OBJECT_IS_IMMUTABLE; + + nsAutoCString propname; + rv = aProp->GetPropertyName(propname); + NS_ENSURE_SUCCESS(rv, rv); + if (propname.EqualsLiteral("RRULE")) { + mIsNegative = false; + } else { + return NS_ERROR_INVALID_ARG; + } + + icalproperty *prop; + struct icalrecurrencetype icalrecur; + + prop = icalprop->GetLibicalProperty(); + + icalrecur = icalproperty_get_rrule(prop); + + // XXX Note that we ignore the dtstart and use the one from the + // event, though I realize now that we shouldn't. Ignoring + // dtstart makes it impossible to have multiple RRULEs on one + // event that start at different times (e.g. every day starting on + // jan 1 for 2 weeks, every other day starting on feb 1 for 2 + // weeks). Neither the server nor the UI supports this now, + // but we really ought to! + //struct icaltimetype icaldtstart; + //icaldtstrat = icalproperty_get_dtstart(prop); + + if (icalrecur.count != 0) + mIsByCount = true; + else + mIsByCount = false; + + mIcalRecur = icalrecur; + + return NS_OK; +} + +NS_IMETHODIMP +calRecurrenceRule::SetIcalString(const nsACString &str) +{ + nsresult rv = NS_OK; + nsAutoCString name; + nsCOMPtr icsSvc = cal::getICSService(); + nsCOMPtr prop; + + rv = icsSvc->CreateIcalPropertyFromString(str, getter_AddRefs(prop)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = prop->GetPropertyName(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (!name.EqualsLiteral("RRULE")) { + return NS_ERROR_ILLEGAL_VALUE; + } + + return SetIcalProperty(prop); +} + +NS_IMETHODIMP +calRecurrenceRule::GetIcalString(nsACString &str) +{ + nsresult rv = NS_OK; + + nsCOMPtr prop; + + rv = this->GetIcalProperty(getter_AddRefs(prop)); + + if (NS_SUCCEEDED(rv)) { + rv = prop->GetIcalString(str); + } + + return rv; +} + +NS_IMETHODIMP +calRecurrenceRule::GetWeekStart(short*) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +calRecurrenceRule::SetWeekStart(short) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/calendar/base/backend/libical/calRecurrenceRule.h b/calendar/base/backend/libical/calRecurrenceRule.h new file mode 100644 index 00000000..0e8c8edd --- /dev/null +++ b/calendar/base/backend/libical/calRecurrenceRule.h @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(INCLUDED_CAL_RECURRENCERULE_H) +#define INCLUDED_CAL_RECURRENCERULE_H + +#include "calIRecurrenceRule.h" +#include "calUtils.h" + +extern "C" { +#include "ical.h" +} + +class calRecurrenceRule : public calIRecurrenceRule, + public cal::XpcomBase +{ +public: + calRecurrenceRule(); + + NS_DECL_ISUPPORTS + NS_DECL_CALIRECURRENCEITEM + NS_DECL_CALIRECURRENCERULE +protected: + virtual ~calRecurrenceRule() {} + + icalrecurrencetype mIcalRecur; + + bool mImmutable; + bool mIsNegative; + bool mIsByCount; +}; + +#endif // INCLUDED_CAL_RECURRENCERULE_H diff --git a/calendar/base/backend/libical/calTimezone.cpp b/calendar/base/backend/libical/calTimezone.cpp new file mode 100644 index 00000000..bb48ccf4 --- /dev/null +++ b/calendar/base/backend/libical/calTimezone.cpp @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "calTimezone.h" +#include "calUtils.h" +#include "calAttributeHelpers.h" + +NS_IMPL_ISUPPORTS(calTimezone, calITimezone) + +CAL_ISUPPORTS_ATTR_GETTER(calTimezone, calIIcalComponent, IcalComponent) +CAL_STRINGTYPE_ATTR_GETTER(calTimezone, nsACString, Tzid) + +NS_IMETHODIMP +calTimezone::GetIsFloating(bool * _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +calTimezone::GetIsUTC(bool * _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +calTimezone::GetDisplayName(nsAString & _retval) { + _retval = NS_ConvertUTF8toUTF16(mTzid); + return NS_OK; +} + +NS_IMETHODIMP +calTimezone::GetLatitude(nsACString & _retval) { + _retval.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +calTimezone::GetLongitude(nsACString & _retval) { + _retval.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +calTimezone::GetProvider(calITimezoneProvider ** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +calTimezone::ToString(nsACString & aResult) { + if (mIcalComponent) { + return mIcalComponent->ToString(aResult); + } else { + return GetTzid(aResult); + } +} + diff --git a/calendar/base/backend/libical/calTimezone.h b/calendar/base/backend/libical/calTimezone.h new file mode 100644 index 00000000..6acb7f0b --- /dev/null +++ b/calendar/base/backend/libical/calTimezone.h @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(INCLUDED_CAL_TIMEZONE_H) +#define INCLUDED_CAL_TIMEZONE_H + +#include "nsCOMPtr.h" +#include "calITimezone.h" +#include "calUtils.h" + +class calTimezone : public calITimezone, + public cal::XpcomBase +{ +public: + calTimezone(nsCString const& tzid, calIIcalComponent * component) + : mTzid(tzid), + mIcalComponent(component) {} + + NS_DECL_ISUPPORTS + NS_DECL_CALITIMEZONE + +protected: + virtual ~calTimezone() {} + nsCString const mTzid; + nsCOMPtr const mIcalComponent; +}; + +#endif // INCLUDED_CAL_TIMEZONE_H diff --git a/calendar/base/backend/libical/calUtils.cpp b/calendar/base/backend/libical/calUtils.cpp new file mode 100644 index 00000000..47b5543d --- /dev/null +++ b/calendar/base/backend/libical/calUtils.cpp @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsComponentManagerUtils.h" + +#include "calUtils.h" +#include "nsIScriptError.h" + +extern "C" { +#include "ical.h" +} + +namespace cal { + +nsresult logError(const nsAString& msg) { + nsresult rc; + nsCOMPtr const scriptError(do_CreateInstance("@mozilla.org/scripterror;1", &rc)); + NS_ENSURE_SUCCESS(rc, rc); + rc = scriptError->Init(msg, EmptyString(), EmptyString(), 0, 0, nsIScriptError::errorFlag, "calendar"); + return getConsoleService()->LogMessage(scriptError); +} + +nsresult logWarning(const nsAString& msg) { + nsresult rc; + nsCOMPtr const scriptError(do_CreateInstance("@mozilla.org/scripterror;1", &rc)); + NS_ENSURE_SUCCESS(rc, rc); + rc = scriptError->Init(msg, EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag, "calendar"); + return getConsoleService()->LogMessage(scriptError); +} + +nsresult log(char16_t const* msg) { + return getConsoleService()->LogStringMessage(msg); +} + +nsCOMPtr detectTimezone(icaltimetype const& icalt, + calITimezoneProvider * tzProvider) +{ + if (icalt.is_utc) { + return UTC(); + } + if (icalt.zone) { + char const* const tzid = icaltimezone_get_tzid(const_cast(icalt.zone)); + if (tzid) { + nsCOMPtr tz; + if (tzProvider) { + tzProvider->GetTimezone(nsDependentCString(tzid), getter_AddRefs(tz)); + } else { + getTimezoneService()->GetTimezone(nsDependentCString(tzid), getter_AddRefs(tz)); + } + if (tz) { + return tz; + } + NS_ASSERTION(tz, "no timezone found, falling back to floating!"); + logMissingTimezone(tzid); + } + } + return floating(); +} + +void logMissingTimezone(char const* tzid) { + // xxx todo: needs l10n + nsString msg(NS_LITERAL_STRING("Timezone \"")); + msg += NS_ConvertUTF8toUTF16(tzid); + msg += NS_LITERAL_STRING("\" not found, falling back to floating!"); + logError(msg); +} + +icaltimezone * getIcalTimezone(calITimezone * tz) { + icaltimezone * icaltz = nullptr; + if (!tz) { + NS_ASSERTION(false, "No Timezone passed to getIcalTimezone"); + return nullptr; + } + + bool b; + tz->GetIsUTC(&b); + if (b) { + icaltz = icaltimezone_get_utc_timezone(); + } else { + nsCOMPtr tzComp; + tz->GetIcalComponent(getter_AddRefs(tzComp)); + if (tzComp) { + nsCOMPtr tzCompLibical = do_QueryInterface(tzComp); + icaltz = tzCompLibical->GetLibicalTimezone(); + } // else floating or phantom timezone + } + return icaltz; +} + +XpcomBase::~XpcomBase() { +} + +} diff --git a/calendar/base/backend/libical/calUtils.h b/calendar/base/backend/libical/calUtils.h new file mode 100644 index 00000000..ef9a153b --- /dev/null +++ b/calendar/base/backend/libical/calUtils.h @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(INCLUDED_CAL_UTILS_H) +#define INCLUDED_CAL_UTILS_H + +#if defined(MOZILLA_INTERNAL_API) +#include "nsCRT.h" +#include "nsString.h" +#else +#include "nsMemory.h" +#include "nsCRTGlue.h" +#include "nsStringAPI.h" +#endif + +#include "nsAutoPtr.h" +#include "nsIStringEnumerator.h" + +#include "calITimezone.h" +#include "calITimezoneProvider.h" +#include "calIICSService.h" +#include "nsIConsoleService.h" +#include "nsServiceManagerUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsIProgrammingLanguage.h" +#include "nsCOMPtr.h" + +#include "calBaseCID.h" + +#define CAL_STRLEN_ARGS(x) x, sizeof(x)-1 +#define CAL_ENSURE_MEMORY(p) NS_ENSURE_TRUE(p, NS_ERROR_OUT_OF_MEMORY) + +typedef struct _icaltimezone icaltimezone; +typedef struct icaltimetype icaltimetype; + +namespace cal { + +/** + * Gets the global console service. + */ +inline nsCOMPtr getConsoleService() { + return do_GetService("@mozilla.org/consoleservice;1"); +} + +/** + * Gets the global ICS service. + */ +inline nsCOMPtr getICSService() { + return do_GetService(CAL_ICSSERVICE_CONTRACTID); +} + +/** + * Gets the global timezone service. + */ +inline nsCOMPtr getTimezoneService() { + nsresult rv; + nsCOMPtr tzs; + + tzs = do_GetService(CAL_TIMEZONESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_RUNTIMEABORT("Could not load timezone service, brace yourself and prepare for crash"); + } + return tzs; +} + +/** + * Logs an error. + */ +nsresult logError(const nsAString& msg); +inline nsresult logError(char const* msg) { + return logError(NS_ConvertASCIItoUTF16(msg)); +} +inline nsresult logError(nsACString const& msg) { + return logError(NS_ConvertASCIItoUTF16(msg)); +} + +/** + * Logs a warning. + */ +nsresult logWarning(const nsAString& msg); +inline nsresult logWarning(char const* msg) { + return logWarning(NS_ConvertASCIItoUTF16(msg)); +} +inline nsresult logWarning(nsACString const& msg) { + return logWarning(NS_ConvertASCIItoUTF16(msg)); +} + +/** + * Just logs. + */ +nsresult log(char16_t const* msg); +inline nsresult log(char const* msg) { + return log(NS_ConvertASCIItoUTF16(msg).get()); +} +inline nsresult log(nsACString const& msg) { + return log(NS_ConvertASCIItoUTF16(msg).get()); +} + +// some timezone helpers + +/** + * Gets the "UTC" timezone. + */ +inline nsCOMPtr UTC() { + nsresult rv; + nsCOMPtr tz; + + rv = getTimezoneService()->GetUTC(getter_AddRefs(tz)); + if (NS_FAILED(rv)) { + NS_RUNTIMEABORT("Could not load UTC timezone, brace yourself and prepare for crash"); + } + + return tz; +} + +/** + * Gets the "floating" timezone + */ +inline nsCOMPtr floating() { + nsresult rv; + nsCOMPtr tz; + + rv = getTimezoneService()->GetFloating(getter_AddRefs(tz)); + if (NS_FAILED(rv)) { + NS_RUNTIMEABORT("Could not load floating timezone, brace yourself and prepare for crash"); + } + + return tz; +} + +/** + * Returns the libical VTIMEZONE component, null if floating. + * + * @attention + * Every timezone provider needs to use calICSService for + * creating its timezone components since we need to stick to the + * same libical. + */ +icaltimezone * getIcalTimezone(calITimezone * tz); + +/** + * Detects the timezone icalt refers to, either using the + * passed timezone provider or the global timezone service. + * + * @param icalt an icaltime + * @param tzProvider timezone provider or null which + * defaults to the timezone service + */ +nsCOMPtr detectTimezone(icaltimetype const& icalt, + calITimezoneProvider * tzProvider); + +/** + * Logs a missing timezone into the js console. + */ +void logMissingTimezone(char const* tzid); + +/** + * Common base class for XPCOM object implementations: + * - disallows public deletion (virtual protected dtor) + * - disallows copy semantics (no assignment, no copy ctor) + */ +class XpcomBase { +protected: + XpcomBase() {} + virtual ~XpcomBase(); +private: + XpcomBase(XpcomBase const&); // left unimplemented + XpcomBase const& operator=(XpcomBase const&); // left unimplemented +}; + +} // namespace cal + +#endif // !defined(INCLUDED_CAL_UTILS_H) diff --git a/calendar/base/backend/libical/moz.build b/calendar/base/backend/libical/moz.build new file mode 100644 index 00000000..05390f56 --- /dev/null +++ b/calendar/base/backend/libical/moz.build @@ -0,0 +1,20 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS = [ + 'build' +] + +SOURCES += [ + 'calDateTime.cpp', + 'calDuration.cpp', + 'calICSService.cpp', + 'calPeriod.cpp', + 'calRecurrenceRule.cpp', + 'calTimezone.cpp', + 'calUtils.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/calendar/base/backend/moz.build b/calendar/base/backend/moz.build new file mode 100644 index 00000000..e875d31b --- /dev/null +++ b/calendar/base/backend/moz.build @@ -0,0 +1,17 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS = [ + 'libical', + 'icaljs' +] + +EXTRA_COMPONENTS += [ + 'calBackendLoader.js', + 'calBackendLoader.manifest', +] + +with Files('**'): + BUG_COMPONENT = ('Calendar', 'Internal Components') diff --git a/calendar/base/content/agenda-listbox.js b/calendar/base/content/agenda-listbox.js new file mode 100644 index 00000000..ced5811e --- /dev/null +++ b/calendar/base/content/agenda-listbox.js @@ -0,0 +1,1130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Preferences.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function Synthetic(aOpen, aDuration, aMultiday) { + this.open = aOpen; + this.duration = aDuration; + this.multiday = aMultiday; +} + +var agendaListbox = { + agendaListboxControl: null, + mPendingRefreshJobs: null, + kDefaultTimezone: null, + showsToday: false, + soonDays: 5 +}; + +/** + * Initialize the agenda listbox, used on window load. + */ +agendaListbox.init = function() { + this.agendaListboxControl = document.getElementById("agenda-listbox"); + this.agendaListboxControl.removeAttribute("suppressonselect"); + let showTodayHeader = (document.getElementById("today-header-hidden").getAttribute("checked") == "true"); + let showTomorrowHeader = (document.getElementById("tomorrow-header-hidden").getAttribute("checked") == "true"); + let showSoonHeader = (document.getElementById("nextweek-header-hidden").getAttribute("checked") == "true"); + this.today = new Synthetic(showTodayHeader, 1, false); + this.addPeriodListItem(this.today, "today-header"); + this.tomorrow = new Synthetic(showTomorrowHeader, 1, false); + this.soonDays = getSoondaysPreference(); + this.soon = new Synthetic(showSoonHeader, this.soonDays, true); + this.periods = [this.today, this.tomorrow, this.soon]; + this.mPendingRefreshJobs = new Map(); + + let prefObserver = { + observe: function(aSubject, aTopic, aPrefName) { + switch (aPrefName) { + case "calendar.agendaListbox.soondays": + agendaListbox.soonDays = getSoondaysPreference(); + agendaListbox.updateSoonSection(); + break; + } + } + }; + Services.prefs.addObserver("calendar.agendaListbox", prefObserver, false); + + // Make sure the agenda listbox is unloaded + window.addEventListener("unload", () => { + Services.prefs.removeObserver("calendar.agendaListbox", prefObserver); + this.uninit(); + }, false); +}; + +/** + * Clean up the agenda listbox, used on window unload. + */ +agendaListbox.uninit = function() { + if (this.calendar) { + this.calendar.removeObserver(this.calendarObserver); + } + + for (let period of this.periods) { + if (period.listItem) { + period.listItem.getCheckbox() + .removeEventListener("CheckboxStateChange", + this.onCheckboxChange, + true); + } + } +}; + +/** + * Adds a period item to the listbox. This is a section of the today pane like + * "Today", "Tomorrow", and is usually a tag. A + * copy of the template node is made and added to the agenda listbox. + * + * @param aPeriod The period item to add. + * @param aItemId The id of an to add to, + * without the "-hidden" suffix. + */ +agendaListbox.addPeriodListItem = function(aPeriod, aItemId) { + aPeriod.listItem = document.getElementById(aItemId + "-hidden").cloneNode(true); + agendaListbox.agendaListboxControl.appendChild(aPeriod.listItem); + aPeriod.listItem.id = aItemId; + aPeriod.listItem.getCheckbox().setChecked(aPeriod.open); + aPeriod.listItem.getCheckbox().addEventListener("CheckboxStateChange", this.onCheckboxChange, true); +}; + +/** + * Remove a period item from the agenda listbox. + * @see agendaListbox::addPeriodListItem + */ +agendaListbox.removePeriodListItem = function(aPeriod) { + if (aPeriod.listItem) { + aPeriod.listItem.getCheckbox().removeEventListener("CheckboxStateChange", this.onCheckboxChange, true); + if (aPeriod.listItem) { + aPeriod.listItem.remove(); + aPeriod.listItem = null; + } + } +}; + +/** + * Handler function called when changing the checkbox state on period items. + * + * @param event The DOM event that triggered the checkbox state change. + */ +agendaListbox.onCheckboxChange = function(event) { + let periodCheckbox = event.target; + let lopen = (periodCheckbox.getAttribute("checked") == "true"); + let listItem = getParentNodeOrThis(periodCheckbox, "agenda-checkbox-richlist-item"); + let period = listItem.getItem(); + period.open = lopen; + // as the agenda-checkboxes are only transient we have to set the "checked" + // attribute at their hidden origins to make that attribute persistent. + document.getElementById(listItem.id + "-hidden").setAttribute("checked", + periodCheckbox.getAttribute("checked")); + if (lopen) { + agendaListbox.refreshCalendarQuery(period.start, period.end); + } else { + listItem = listItem.nextSibling; + let leaveloop; + do { + leaveloop = (listItem == null); + if (!leaveloop) { + let nextItemSibling = listItem.nextSibling; + leaveloop = !agendaListbox.isEventListItem(listItem); + if (!leaveloop) { + listItem.remove(); + listItem = nextItemSibling; + } + } + } while (!leaveloop); + } + calendarController.onSelectionChanged({ detail: [] }); +}; + +/** + * Handler function called when an agenda listbox item is selected + * + * @param aListItem The agenda-base-richlist-item that was selected. + */ +agendaListbox.onSelect = function(aListItem) { + let listbox = document.getElementById("agenda-listbox"); + let item = aListItem || listbox.selectedItem; + if (aListItem) { + listbox.selectedItem = item; + } + calendarController.onSelectionChanged({ detail: agendaListbox.getSelectedItems() }); +}; + +/** + * Handler function called when the agenda listbox becomes focused + */ +agendaListbox.onFocus = function() { + calendarController.onSelectionChanged({ detail: agendaListbox.getSelectedItems() }); +}; + +/** + * Handler function called when the agenda listbox loses focus. + */ +agendaListbox.onBlur = function() { + calendarController.onSelectionChanged({ detail: [] }); +}; + + +/** + * Handler function called when a key was pressed on the agenda listbox + */ +agendaListbox.onKeyPress = function(aEvent) { + let listItem = aEvent.target; + if (listItem.localName == "richlistbox") { + listItem = listItem.selectedItem; + } + switch (aEvent.keyCode) { + case aEvent.DOM_VK_RETURN: + document.getElementById("agenda_edit_event_command").doCommand(); + break; + case aEvent.DOM_VK_DELETE: + document.getElementById("agenda_delete_event_command").doCommand(); + aEvent.stopPropagation(); + aEvent.preventDefault(); + break; + case aEvent.DOM_VK_LEFT: + if (!this.isEventListItem(listItem)) { + listItem.getCheckbox().setChecked(false); + } + break; + case aEvent.DOM_VK_RIGHT: + if (!this.isEventListItem(listItem)) { + listItem.getCheckbox().setChecked(true); + } + break; + } +}; + +/** + * Calls the event dialog to edit the currently selected item + */ +agendaListbox.editSelectedItem = function() { + let listItem = document.getElementById("agenda-listbox").selectedItem; + if (listItem) { + modifyEventWithDialog(listItem.occurrence, null, true); + } +}; + +/** + * Finds the appropriate period for the given item, i.e finds "Tomorrow" if the + * item occurrs tomorrow. + * + * @param aItem The item to find the period for. + */ +agendaListbox.findPeriodsForItem = function(aItem) { + let retPeriods = []; + for (let i = 0; i < this.periods.length; i++) { + if (this.periods[i].open) { + if (checkIfInRange(aItem, this.periods[i].start, this.periods[i].end)) { + retPeriods.push(this.periods[i]); + } + } + } + return retPeriods; +}; + +/** + * Gets the start of the earliest period shown in the agenda listbox + */ +agendaListbox.getStart = function() { + let retStart = null; + for (let i = 0; i < this.periods.length; i++) { + if (this.periods[i].open) { + retStart = this.periods[i].start; + break; + } + } + return retStart; +}; + +/** + * Gets the end of the latest period shown in the agenda listbox + */ +agendaListbox.getEnd = function() { + let retEnd = null; + for (let i = this.periods.length - 1; i >= 0; i--) { + if (this.periods[i].open) { + retEnd = this.periods[i].end; + break; + } + } + return retEnd; +}; + +/** + * Adds an item to an agenda period before another existing item. + * + * @param aNewItem The calIItemBase to add. + * @param aAgendaItem The existing item to insert before. + * @param aPeriod The period to add the item to. + * @param visible If true, the item should be visible. + * @return The newly created XUL element. + */ +agendaListbox.addItemBefore = function(aNewItem, aAgendaItem, aPeriod, visible) { + let newelement = null; + if (aNewItem.startDate.isDate) { + newelement = createXULElement("agenda-allday-richlist-item"); + } else { + newelement = createXULElement("agenda-richlist-item"); + } + // set the item at the richlistItem. When the duration of the period + // is bigger than 1 (day) the starttime of the item has to include + // information about the day of the item + if (aAgendaItem == null) { + this.agendaListboxControl.appendChild(newelement); + } else { + this.agendaListboxControl.insertBefore(newelement, aAgendaItem); + } + newelement.setOccurrence(aNewItem, aPeriod); + newelement.removeAttribute("selected"); + return newelement; +}; + +/** + * Adds an item to the agenda listbox. This function finds the correct period + * for the item and inserts it correctly so the period stays sorted. + * + * @param aItem The calIItemBase to add. + * @return The newly created XUL element. + */ +agendaListbox.addItem = function(aItem) { + if (!isEvent(aItem)) { + return null; + } + let periods = this.findPeriodsForItem(aItem); + if (periods.length == 0) { + return null; + } + let newlistItem = null; + for (let i = 0; i < periods.length; i++) { + let period = periods[i]; + let complistItem = period.listItem; + let visible = complistItem.getCheckbox().checked; + if (aItem.startDate.isDate && period.duration == 1 && aItem.duration.days == 1) { + if (this.getListItems(aItem, period).length == 0) { + this.addItemBefore(aItem, period.listItem.nextSibling, period, visible); + } + } else { + do { + complistItem = complistItem.nextSibling; + if (this.isEventListItem(complistItem)) { + let compitem = complistItem.occurrence; + if (this.isSameEvent(aItem, compitem)) { + // The same event occurs on several calendars but we only + // display the first one. + // TODO: find a way to display this special circumstance + break; + } else if (this.isBefore(aItem, compitem, period)) { + if (this.isSameEvent(aItem, compitem)) { + newlistItem = this.addItemBefore(aItem, complistItem, period, visible); + break; + } else { + newlistItem = this.addItemBefore(aItem, complistItem, period, visible); + break; + } + } + } else { + newlistItem = this.addItemBefore(aItem, complistItem, period, visible); + break; + } + } while (complistItem); + } + } + return newlistItem; +}; + +/** + * Checks if the given item happens before the comparison item. + * + * @param aItem The item to compare. + * @param aCompItem The item to compare with. + * @param aPeriod The period where the items are inserted. + * @return True, if the aItem happens before aCompItem. + */ +agendaListbox.isBefore = function(aItem, aCompItem, aPeriod) { + let itemDate = this.comparisonDate(aItem, aPeriod); + let compItemDate = this.comparisonDate(aCompItem, aPeriod); + let itemDateEndDate = itemDate.clone(); + itemDateEndDate.day++; + + if (compItemDate.day == itemDate.day) { + // In the same day the order is: + // - all-day events (single day); + // - all-day events spanning multiple days: start, end, intermediate; + // - events and events spanning multiple days: start, end, (sorted by + // time) and intermediate. + if (itemDate.isDate && aItem.duration.days == 1) { + // all-day events with duration one day + return true; + } else if (itemDate.isDate) { + if (aItem.startDate.compare(itemDate) == 0) { + // starting day of an all-day events spannig multiple days + return !compItemDate.isDate || aCompItem.duration.days != 1; + } else if (aItem.endDate.compare(itemDateEndDate) == 0) { + // ending day of an all-day events spannig multiple days + return !compItemDate.isDate || + (aCompItem.duration.days != 1 && + aCompItem.startDate.compare(compItemDate) != 0); + } else { + // intermediate day of an all-day events spannig multiple days + return !compItemDate.isDate; + } + } else if (aCompItem.startDate.isDate) { + return false; + } + } + // Non all-day event sorted by date-time. When equal, sorted by start + // date-time then by end date-time. + let comp = itemDate.compare(compItemDate); + if (comp == 0) { + comp = aItem.startDate.compare(aCompItem.startDate); + if (comp == 0) { + comp = aItem.endDate.compare(aCompItem.endDate); + } + } + return (comp <= 0); +}; + +/** + * Returns the start or end date of an item according to which of them + * must be displayed in a given period of the agenda + * + * @param aItem The item to compare. + * @param aPeriod The period where the item is inserted. + * @return The start or end date of the item showed in the agenda. + */ +agendaListbox.comparisonDate = function(aItem, aPeriod) { + let periodStartDate = aPeriod.start.clone(); + periodStartDate.isDate = true; + let periodEndDate = aPeriod.end.clone(); + periodEndDate.day--; + let startDate = aItem.startDate.clone(); + startDate.isDate = true; + let endDate = aItem.endDate.clone(); + + let endDateToReturn = aItem.endDate.clone(); + if (aItem.startDate.isDate && aPeriod.duration == 1) { + endDateToReturn = periodEndDate.clone(); + } else if (endDate.isDate) { + endDateToReturn.day--; + } else if (endDate.hour == 0 && endDate.minute == 0) { + // End at midnight -> end date in the day where midnight occurs + endDateToReturn.day--; + endDateToReturn.hour = 23; + endDateToReturn.minute = 59; + endDateToReturn.second = 59; + } + endDate.isDate = true; + if (startDate.compare(endDate) != 0 && + startDate.compare(periodStartDate) < 0) { + // returns a end date when the item is a multiday event AND + // it starts before the given period + return endDateToReturn; + } + return aItem.startDate.clone(); +}; + +/** + * Gets the listitems for a given item, possibly in a given period. + * + * @param aItem The item to get the list items for. + * @param aPeriod (optional) the period to search in. + * @return An array of list items for the given item. + */ +agendaListbox.getListItems = function(aItem, aPeriod) { + let retlistItems = []; + let periods = [aPeriod]; + if (!aPeriod) { + periods = this.findPeriodsForItem(aItem); + } + if (periods.length > 0) { + for (let i = 0; i < periods.length; i++) { + let period = periods[i]; + let complistItem = period.listItem; + let leaveloop; + do { + complistItem = complistItem.nextSibling; + leaveloop = !this.isEventListItem(complistItem); + if (!leaveloop) { + if (this.isSameEvent(aItem, complistItem.occurrence)) { + retlistItems.push(complistItem); + break; + } + } + } while (!leaveloop); + } + } + return retlistItems; +}; + +/** + * Removes the given item from the agenda listbox + * + * @param aItem The item to remove. + * @param aMoveSelection If true, the selection will be moved to the next + * sibling that is not an period item. + * @return Returns true if the removed item was selected. + */ +agendaListbox.deleteItem = function(aItem, aMoveSelection) { + let isSelected = false; + let listItems = this.getListItems(aItem); + if (listItems.length > 0) { + for (let i = listItems.length - 1; i >= 0; i--) { + let listItem = listItems[i]; + let isSelected2 = listItem.selected; + if (isSelected2 && !isSelected) { + isSelected = true; + if (aMoveSelection) { + this.moveSelection(); + } + } + listItem.remove(); + } + } + return isSelected; +}; + +/** + * Remove all items belonging to the specified calendar. + * + * @param aCalendar The item to compare. + */ +agendaListbox.deleteItemsFromCalendar = function(aCalendar) { + let childNodes = Array.from(this.agendaListboxControl.childNodes); + for (let childNode of childNodes) { + if (childNode && childNode.occurrence && + childNode.occurrence.calendar.id == aCalendar.id) { + childNode.remove(); + } + } +}; + +/** + * Compares two items to see if they have the same id and their start date + * matches + * + * @param aItem The item to compare. + * @param aCompItem The item to compare with. + * @return True, if the items match with the above noted criteria. + */ +agendaListbox.isSameEvent = function(aItem, aCompItem) { + return aItem.id == aCompItem.id && + aItem[calGetStartDateProp(aItem)].compare(aCompItem[calGetStartDateProp(aCompItem)]) == 0; +}; + +/** + * Checks if the currently selected node in the listbox is an Event item (not a + * period item). + * + * @return True, if the node is not a period item. + */ +agendaListbox.isEventSelected = function() { + let listItem = this.agendaListboxControl.selectedItem; + if (listItem) { + return this.isEventListItem(listItem); + } + return false; +}; + +/** + * Delete the selected item from its calendar (if it is an event item) + * + * @param aDoNotConfirm If true, the user will not be prompted. + */ +agendaListbox.deleteSelectedItem = function(aDoNotConfirm) { + let listItem = this.agendaListboxControl.selectedItem; + if (this.isEventListItem(listItem)) { + let selectedItems = [listItem.occurrence]; + calendarViewController.deleteOccurrences(selectedItems.length, + selectedItems, + false, + aDoNotConfirm); + } +}; + +/** + * If a Period item is targeted by the passed DOM event, opens the event dialog + * with the period's start date prefilled. + * + * @param aEvent The DOM event that targets the period. + */ +agendaListbox.createNewEvent = function(aEvent) { + if (!this.isEventListItem(aEvent.target)) { + // Create new event for the date currently displayed in the agenda. Setting + // isDate = true automatically makes the start time be the next full hour. + let eventStart = agendaListbox.today.start.clone(); + eventStart.isDate = true; + if (calendarController.isCommandEnabled("calendar_new_event_command")) { + createEventWithDialog(getSelectedCalendar(), eventStart); + } + } +}; + +/** + * Sets up the context menu for the agenda listbox + * + * @param popup The element to set up. + */ +agendaListbox.setupContextMenu = function(popup) { + let listItem = this.agendaListboxControl.selectedItem; + let enabled = this.isEventListItem(listItem); + let menuitems = popup.childNodes; + for (let i = 0; i < menuitems.length; i++) { + setBooleanAttribute(menuitems[i], "disabled", !enabled); + } + + let menu = document.getElementById("calendar-today-pane-menu-attendance-menu"); + setupAttendanceMenu(menu, agendaListbox.getSelectedItems({})); +}; + + +/** + * Refreshes the agenda listbox. If aStart or aEnd is not passed, the agenda + * listbox's limiting dates will be used. + * + * @param aStart (optional) The start date for the item query. + * @param aEnd (optional) The end date for the item query. + * @param aCalendar (optional) If specified, the single calendar from + * which the refresh will occur. + */ +agendaListbox.refreshCalendarQuery = function(aStart, aEnd, aCalendar) { + let refreshJob = { + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calIOperationListener]), + agendaListbox: this, + calendar: null, + calId: null, + operation: null, + cancelled: false, + + onOperationComplete: function(aOpCalendar, aStatus, aOperationType, aId, aDateTime) { + if (this.agendaListbox.mPendingRefreshJobs.has(this.calId)) { + this.agendaListbox.mPendingRefreshJobs.delete(this.calId); + } + + if (!this.cancelled) { + setCurrentEvent(); + } + }, + + onGetResult: function(aOpCalendar, aStatus, aItemType, aDetail, aCount, aItems) { + if (this.cancelled || !Components.isSuccessCode(aStatus)) { + return; + } + for (let item of aItems) { + this.agendaListbox.addItem(item); + } + }, + + cancel: function() { + this.cancelled = true; + let operation = cal.wrapInstance(this.operation, Components.interfaces.calIOperation); + if (operation && operation.isPending) { + operation.cancel(); + this.operation = null; + } + }, + + execute: function() { + if (!(aStart || aEnd || aCalendar)) { + this.agendaListbox.removeListItems(); + } + + if (!aCalendar) { + aCalendar = this.agendaListbox.calendar; + } + if (!aStart) { + aStart = this.agendaListbox.getStart(); + } + if (!aEnd) { + aEnd = this.agendaListbox.getEnd(); + } + if (!(aStart || aEnd || aCalendar)) { + return; + } + + if (aCalendar.type == "composite") { + // we're refreshing from the composite calendar, so we can cancel + // all other pending refresh jobs. + this.calId = "composite"; + for (let job of this.agendaListbox.mPendingRefreshJobs.values()) { + job.cancel(); + } + this.agendaListbox.mPendingRefreshJobs.clear(); + } else { + this.calId = aCalendar.id; + if (this.agendaListbox.mPendingRefreshJobs.has(this.calId)) { + this.agendaListbox.mPendingRefreshJobs.get(this.calId).cancel(); + this.agendaListbox.mPendingRefreshJobs.delete(this.calId); + } + } + this.calendar = aCalendar; + + let filter = this.calendar.ITEM_FILTER_CLASS_OCCURRENCES | + this.calendar.ITEM_FILTER_TYPE_EVENT; + let operation = this.calendar.getItems(filter, 0, aStart, aEnd, this); + operation = cal.wrapInstance(operation, Components.interfaces.calIOperation); + if (operation && operation.isPending) { + this.operation = operation; + this.agendaListbox.mPendingRefreshJobs.set(this.calId, this); + } + } + }; + + refreshJob.execute(); +}; + +/** + * Sets up the calendar for the agenda listbox. + */ +agendaListbox.setupCalendar = function() { + this.init(); + if (this.calendar == null) { + this.calendar = getCompositeCalendar(); + } + if (this.calendar) { + // XXX This always gets called, does that happen on purpose? + this.calendar.removeObserver(this.calendarObserver); + } + this.calendar.addObserver(this.calendarObserver); + if (this.mListener) { + this.mListener.updatePeriod(); + } +}; + +/** + * Refreshes the period dates, especially when a period is showing "today". + * Usually called at midnight to update the agenda pane. Also retrieves the + * items from the calendar. + * + * @see #refreshCalendarQuery + * @param newDate The first date to show if the agenda pane doesn't show + * today. + */ +agendaListbox.refreshPeriodDates = function(newDate) { + this.kDefaultTimezone = calendarDefaultTimezone(); + // Today: now until midnight of tonight + let oldshowstoday = this.showstoday; + this.showstoday = this.showsToday(newDate); + if ((this.showstoday) && (!oldshowstoday)) { + this.addPeriodListItem(this.tomorrow, "tomorrow-header"); + this.addPeriodListItem(this.soon, "nextweek-header"); + } else if (!this.showstoday) { + this.removePeriodListItem(this.tomorrow); + this.removePeriodListItem(this.soon); + } + newDate.isDate = true; + for (let i = 0; i < this.periods.length; i++) { + let curPeriod = this.periods[i]; + newDate.hour = newDate.minute = newDate.second = 0; + if (i == 0 && this.showstoday) { + curPeriod.start = now(); + } else { + curPeriod.start = newDate.clone(); + } + newDate.day += curPeriod.duration; + curPeriod.end = newDate.clone(); + curPeriod.listItem.setItem(curPeriod, this.showstoday); + } + this.refreshCalendarQuery(); +}; + +/** + * Adds a listener to this agenda listbox. + * + * @param aListener The listener to add. + */ +agendaListbox.addListener = function(aListener) { + this.mListener = aListener; +}; + +/** + * Checks if the agenda listbox is showing "today". Without arguments, this + * function assumes the today attribute of the agenda listbox. + * + * @param aStartDate (optional) The day to check if its "today". + * @return Returns true if today is shown. + */ +agendaListbox.showsToday = function(aStartDate) { + let lstart = aStartDate; + if (!lstart) { + lstart = this.today.start; + } + let lshowsToday = sameDay(now(), lstart); + if (lshowsToday) { + this.periods = [this.today, this.tomorrow, this.soon]; + } else { + this.periods = [this.today]; + } + return lshowsToday; +}; + +/** + * Moves the selection. Moves down unless the next item is a period item, in + * which case the selection moves up. + */ +agendaListbox.moveSelection = function() { + if (this.isEventListItem(this.agendaListboxControl.selectedItem.nextSibling)) { + this.agendaListboxControl.goDown(); + } else { + this.agendaListboxControl.goUp(); + } +}; + +/** + * Gets an array of selected items. If a period node is selected, it is not + * included. + * + * @return An array with all selected items. + */ +agendaListbox.getSelectedItems = function() { + let items = []; + if (this.isEventListItem(this.agendaListboxControl.selectedItem)) { + // If at some point we support selecting multiple items, this array can + // be expanded. + items = [this.agendaListboxControl.selectedItem.occurrence]; + } + return items; +}; + +/** + * Checks if the passed node in the listbox is an Event item (not a + * period item). + * + * @param aListItem The node to check for. + * @return True, if the node is not a period item. + */ +agendaListbox.isEventListItem = function(aListItem) { + let isListItem = (aListItem != null); + if (isListItem) { + let localName = aListItem.localName; + isListItem = (localName == "agenda-richlist-item" || + localName == "agenda-allday-richlist-item"); + } + return isListItem; +}; + +/** + * Removes all Event items, keeping the period items intact. + */ +agendaListbox.removeListItems = function() { + let listItem = this.agendaListboxControl.lastChild; + if (listItem) { + let leaveloop = false; + do { + let newlistItem = null; + if (listItem) { + newlistItem = listItem.previousSibling; + } else { + leaveloop = true; + } + if (this.isEventListItem(listItem)) { + if (listItem == this.agendaListboxControl.firstChild) { + leaveloop = true; + } else { + listItem.remove(); + } + } + listItem = newlistItem; + } while (!leaveloop); + } +}; + +/** + * Gets the list item node by its associated event's hashId. + * + * @return The XUL node if successful, otherwise null. + */ +agendaListbox.getListItemByHashId = function(ahashId) { + let listItem = this.agendaListboxControl.firstChild; + let leaveloop = false; + do { + if (this.isEventListItem(listItem)) { + if (listItem.occurrence.hashId == ahashId) { + return listItem; + } + } + listItem = listItem.nextSibling; + leaveloop = (listItem == null); + } while (!leaveloop); + return null; +}; + +/** + * The operation listener used for calendar queries. + * Implements calIOperationListener. + */ +agendaListbox.calendarOpListener = { agendaListbox: agendaListbox }; + +/** + * Calendar and composite observer, used to keep agenda listbox up to date. + * @see calIObserver + * @see calICompositeObserver + */ +agendaListbox.calendarObserver = { agendaListbox: agendaListbox }; + +agendaListbox.calendarObserver.QueryInterface = function(aIID) { + if (!aIID.equals(Components.interfaces.calIObserver) && + !aIID.equals(Components.interfaces.calICompositeObserver) && + !aIID.equals(Components.interfaces.nsISupports)) { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + return this; +}; + +// calIObserver: +agendaListbox.calendarObserver.onStartBatch = function() { +}; + +agendaListbox.calendarObserver.onEndBatch = function() { +}; + +agendaListbox.calendarObserver.onLoad = function() { + this.agendaListbox.refreshCalendarQuery(); +}; + +agendaListbox.calendarObserver.onAddItem = function(item) { + if (!isEvent(item)) { + return; + } + // get all sub items if it is a recurring item + let occs = this.getOccurrencesBetween(item); + occs.forEach(this.agendaListbox.addItem, this.agendaListbox); + setCurrentEvent(); +}; + +agendaListbox.calendarObserver.getOccurrencesBetween = function(aItem) { + let occs = []; + let start = this.agendaListbox.getStart(); + let end = this.agendaListbox.getEnd(); + if (start && end) { + occs = aItem.getOccurrencesBetween(start, end, {}); + } + return occs; +}; + +agendaListbox.calendarObserver.onDeleteItem = function(item, rebuildFlag) { + this.onLocalDeleteItem(item, true); +}; + +agendaListbox.calendarObserver.onLocalDeleteItem = function(item, moveSelection) { + if (!isEvent(item)) { + return false; + } + let selectedItemHashId = -1; + // get all sub items if it is a recurring item + let occs = this.getOccurrencesBetween(item); + for (let i = 0; i < occs.length; i++) { + let isSelected = this.agendaListbox.deleteItem(occs[i], moveSelection); + if (isSelected) { + selectedItemHashId = occs[i].hashId; + } + } + return selectedItemHashId; +}; + +agendaListbox.calendarObserver.onModifyItem = function(newItem, oldItem) { + let selectedItemHashId = this.onLocalDeleteItem(oldItem, false); + if (!isEvent(newItem)) { + return; + } + this.onAddItem(newItem); + if (selectedItemHashId != -1) { + let listItem = agendaListbox.getListItemByHashId(selectedItemHashId); + if (listItem) { + agendaListbox.agendaListboxControl.clearSelection(); + agendaListbox.agendaListboxControl.ensureElementIsVisible(listItem); + agendaListbox.agendaListboxControl.selectedItem = listItem; + } + } + setCurrentEvent(); +}; + +agendaListbox.calendarObserver.onError = function(cal, errno, msg) {}; + +agendaListbox.calendarObserver.onPropertyChanged = function(aCalendar, aName, aValue, aOldValue) { + switch (aName) { + case "disabled": + this.agendaListbox.refreshCalendarQuery(); + break; + case "color": + for (let node = agendaListbox.agendaListboxControl.firstChild; + node; + node = node.nextSibling) { + // Change color on all nodes that don't do so themselves, which + // is currently only he agenda-richlist-item + if (node.localName != "agenda-richlist-item") { + continue; + } + node.refreshColor(); + } + break; + } +}; + +agendaListbox.calendarObserver.onPropertyDeleting = function(aCalendar, aName) { + this.onPropertyChanged(aCalendar, aName, null, null); +}; + + +agendaListbox.calendarObserver.onCalendarRemoved = function(aCalendar) { + if (!aCalendar.getProperty("disabled")) { + this.agendaListbox.deleteItemsFromCalendar(aCalendar); + } +}; + +agendaListbox.calendarObserver.onCalendarAdded = function(aCalendar) { + if (!aCalendar.getProperty("disabled")) { + this.agendaListbox.refreshCalendarQuery(null, null, aCalendar); + } +}; + +agendaListbox.calendarObserver.onDefaultCalendarChanged = function(aCalendar) { +}; + +/** + * Updates the "Upcoming" section of today pane when preference soondays changes + **/ +agendaListbox.updateSoonSection = function() { + this.soon.duration = this.soonDays; + this.soon.open = true; + let soonHeader = document.getElementById("nextweek-header"); + if (soonHeader) { + soonHeader.setItem(this.soon, true); + agendaListbox.refreshPeriodDates(now()); + } +}; + +/** + * Updates the event considered "current". This goes through all "today" items + * and sets the "current" attribute on all list items that are currently + * occurring. + * + * @see scheduleNextCurrentEventUpdate + */ +function setCurrentEvent() { + if (!agendaListbox.showsToday() || !agendaListbox.today.open) { + return; + } + + let msScheduleTime = -1; + let complistItem = agendaListbox.tomorrow.listItem.previousSibling; + let removelist = []; + let anow = now(); + let msuntillend = 0; + let msuntillstart = 0; + let leaveloop; + do { + leaveloop = !agendaListbox.isEventListItem(complistItem); + if (!leaveloop) { + msuntillstart = complistItem.occurrence.startDate + .getInTimezone(agendaListbox.kDefaultTimezone) + .subtractDate(anow).inSeconds; + if (msuntillstart <= 0) { + msuntillend = complistItem.occurrence.endDate + .getInTimezone(agendaListbox.kDefaultTimezone) + .subtractDate(anow).inSeconds; + if (msuntillend > 0) { + complistItem.setAttribute("current", "true"); + if (msuntillend < msScheduleTime || msScheduleTime == -1) { + msScheduleTime = msuntillend; + } + } else { + removelist.push(complistItem); + } + } else { + complistItem.removeAttribute("current"); + } + if (msScheduleTime == -1 || msuntillstart < msScheduleTime) { + if (msuntillstart > 0) { + msScheduleTime = msuntillstart; + } + } + } + if (!leaveloop) { + complistItem = complistItem.previousSibling; + } + } while (!leaveloop); + + if (msScheduleTime > -1) { + scheduleNextCurrentEventUpdate(setCurrentEvent, msScheduleTime * 1000); + } + + if (removelist) { + if (removelist.length > 0) { + for (let i = 0; i < removelist.length; i++) { + removelist[i].remove(); + } + } + } +} + +var gEventTimer; + +/** + * Creates a timer that will fire after the next event is current. + * Pass in a function as aRefreshCallback that should be called at that time. + * + * @param aRefreshCallback The function to call when the next event is + * current. + * @param aMsUntil The number of milliseconds until the next event + * is current. + */ +function scheduleNextCurrentEventUpdate(aRefreshCallback, aMsUntil) { + // Is an nsITimer/callback extreme overkill here? Yes, but it's necessary to + // workaround bug 291386. If we don't, we stand a decent chance of getting + // stuck in an infinite loop. + let udCallback = { + notify: function(timer) { + aRefreshCallback(); + } + }; + + if (gEventTimer) { + gEventTimer.cancel(); + } else { + // Observer for wake after sleep/hibernate/standby to create new timers and refresh UI + let wakeObserver = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "wake_notification") { + aRefreshCallback(); + } + } + }; + // Add observer + Services.obs.addObserver(wakeObserver, "wake_notification", false); + + // Remove observer on unload + window.addEventListener("unload", () => { + Services.obs.removeObserver(wakeObserver, "wake_notification"); + }, false); + + gEventTimer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); + } + gEventTimer.initWithCallback(udCallback, aMsUntil, gEventTimer.TYPE_ONE_SHOT); +} + +/** + * Gets a right value for calendar.agendaListbox.soondays preference, avoid + * erroneus values edited in the lightning.js preference file + **/ +function getSoondaysPreference() { + let prefName = "calendar.agendaListbox.soondays"; + let soonpref = Preferences.get(prefName, 5); + + if (soonpref > 0 && soonpref <= 28) { + if (soonpref % 7 != 0) { + let intSoonpref = Math.floor(soonpref / 7) * 7; + soonpref = (intSoonpref == 0 ? soonpref : intSoonpref); + Preferences.set(prefName, soonpref, "INT"); + } + } else { + soonpref = soonpref > 28 ? 28 : 1; + Preferences.set(prefName, soonpref, "INT"); + } + return soonpref; +} diff --git a/calendar/base/content/agenda-listbox.xml b/calendar/base/content/agenda-listbox.xml new file mode 100644 index 00000000..bc205b7a --- /dev/null +++ b/calendar/base/content/agenda-listbox.xml @@ -0,0 +1,289 @@ + + + + + + + + null; + + + + + + + + + + + + + + + + + null; + + + + + + + + + + 7) { + this.kCheckbox.label += " (" + unitPluralForm(duration / 7, "weeks") + ")"; + } else { + this.kCheckbox.label += " (" + unitPluralForm(duration, "days") + ")"; + } + } + } else if (synthetic.duration == 1) { + this.kCheckbox.label = getDateFormatter().formatDate(synthetic.start); + } else { + this.kCheckbox.label = getDateFormatter().formatInterval(synthetic.start, synthetic.end); + } + ]]> + + + + + + + + + + + + + + + + + null; + + + + + + + 1; + + let date = ""; + let iconType = ""; + let allDayDateLabel = document.getAnonymousElementByAttribute(this, "anonid", "agenda-allDayEvent-date"); + setBooleanAttribute(allDayDateLabel, "hidden", !showDate); + if (startDate.compare(endPreviousDay) == 0) { + // All day event one day duration. + date = dateFormatter.formatDate(startDate); + } else if (startDate.compare(periodStartDate) >= 0 && + startDate.compare(periodEndDate) <= 0) { + // All day event spanning multiple days. + iconType = "start"; + date = dateFormatter.formatDate(startDate); + } else if (endDate.compare(periodStartDate) >= 0 && + endDate.compare(periodEndDate) <= 0) { + iconType = "end"; + date = dateFormatter.formatDate(endPreviousDay); + } else { + iconType = "continue"; + hideElement(allDayDateLabel); + } + + let multiDayImage = document.getAnonymousElementByAttribute(this, "anonid", "agenda-multiDayEvent-image"); + multiDayImage.setAttribute("type", iconType); + // class wrap causes allday items to wrap its text in today-pane + let addWrap = document.getAnonymousElementByAttribute(this.mAllDayItem, "anonid", "eventbox"); + addWrap.classList.add("wrap"); + addWrap = document.getAnonymousElementByAttribute(this.mAllDayItem, "anonid", "event-detail-box"); + addWrap.classList.add("wrap"); + allDayDateLabel.value = date; + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1; + + let duration = ""; + let iconType = ""; + if (startDate.compare(endDate) == 0) { + // event that starts and ends in the same day, midnight included + duration = longFormat ? dateFormatter.formatDateTime(start) + : dateFormatter.formatTime(start); + } else if (startDate.compare(periodStartDate) >= 0 && + startDate.compare(periodEndDate) <= 0) { + // event spanning multiple days, start date within period + iconType = "start"; + duration = longFormat ? dateFormatter.formatDateTime(start) + : dateFormatter.formatTime(start); + } else if (endDate.compare(periodStartDate) >= 0 && + endDate.compare(periodEndDate) <= 0) { + // event spanning multiple days, end date within period + iconType = "end"; + if (endAtMidnight) { + duration = dateFormatter.formatDate(endDate) + " "; + duration = longFormat ? duration + calGetString("dateFormat", "midnight") + : calGetString("dateFormat", "midnight"); + } else { + duration = longFormat ? dateFormatter.formatDateTime(end) + : dateFormatter.formatTime(end); + } + } else { + iconType = "continue"; + } + let multiDayImage = document.getAnonymousElementByAttribute(this, "anonid", "agenda-multiDayEvent-image"); + multiDayImage.setAttribute("type", iconType); + let durationbox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-event-start"); + durationbox.textContent = duration; + + // show items with time only (today & tomorrow) as one line. + if (longFormat) { + let titlebox = document.getAnonymousElementByAttribute(this, "anonid", "agenda-event-title"); + titlebox.textContent = aItem.title; + } else { + durationbox.textContent += " " + aItem.title; + } + this.refreshColor(); + ]]> + + + + + + + + + + + + diff --git a/calendar/base/content/calendar-base-view.xml b/calendar/base/content/calendar-base-view.xml new file mode 100644 index 00000000..bd7e72d0 --- /dev/null +++ b/calendar/base/content/calendar-base-view.xml @@ -0,0 +1,924 @@ + + + + + + + + + + + 0 + null; + null; + false + null + null + null + null + null + false + false + true + [0, 6] + null + null + [] + -1 + null + null + null + null + 0 + 0 + null + null + 0 + null + + + + + + + { + this.onResize(this); + }; + this.viewBroadcaster.addEventListener(this.getAttribute("type") + "viewresized", this.mResizeHandler, true); + // add a preference observer to monitor changes + Services.prefs.addObserver("calendar.", this.mPrefObserver, false); + this.weekStartOffset = Preferences.get("calendar.week.start", 0); + this.updateDaysOffPrefs(); + this.mPendingRefreshJobs = new Map(); + this.mLog = Log4Moz.getConfiguredLogger("calBaseView"); + this.mFlashingEvents = {}; + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.95 * clientWidth) { + useShortNames = true; + } + } + for (let i = 0; i < labeldayboxkids.length; i++) { + labeldayboxkids[i].shortWeekNames = useShortNames; + } + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0) { + this.mLongWeekdayTotalPixels = (maxDayWidth * this.labeldaybox.childNodes.length) + 10; + } + } + return this.mLongWeekdayTotalPixels; + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pixelThreshold) { + deltaView = 1; + this.mPixelScrollDelta = 0; + } else if (this.mPixelScrollDelta < -pixelThreshold) { + deltaView = -1; + this.mPixelScrollDelta = 0; + } + } + + if (deltaView != 0) { + this.moveView(deltaView); + } + } + + // Prevent default scroll handling + event.preventDefault(); + ]]> + = MIN_ROTATE_ANGLE && + absval < MAX_ROTATE_ANGLE) { + toggleOrientation(); + event.preventDefault(); + } + ]]> + + THRESHOLD) { + this.zoomOut(); + this.mMagnifyAmount = 0; + } else if (this.mMagnifyAmount < -THRESHOLD) { + this.zoomIn(); + this.mMagnifyAmount = 0; + } + event.preventDefault(); + } + ]]> + + + + + + + + + + -1 + 0 + null + + + + return this.mWeekday; + + + + + + + + + + + + + + + 0) { + this.longWeekdayPixels = longNameWidth + + getSummarizedStyleValues(this.longName, ["margin-left", "margin-right"]); + this.longWeekdayPixels += getSummarizedStyleValues(this, ["border-left-width", + "padding-left", "padding-right"]); + return this.longWeekdayPixels; + } else { + // in this case the weekdaypixels have not yet been layouted; + // by definition 0 is returned + return 0; + } + ]]> + + + + diff --git a/calendar/base/content/calendar-bindings.css b/calendar/base/content/calendar-bindings.css new file mode 100644 index 00000000..e34071c9 --- /dev/null +++ b/calendar/base/content/calendar-bindings.css @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); + + +calendar-day-view { + -moz-binding: url(chrome://calendar/content/calendar-views.xml#calendar-day-view); +} + +calendar-week-view { + -moz-binding: url(chrome://calendar/content/calendar-views.xml#calendar-week-view); +} + +calendar-multiweek-view { + -moz-binding: url(chrome://calendar/content/calendar-views.xml#calendar-multiweek-view); +} + +calendar-month-view { + -moz-binding: url(chrome://calendar/content/calendar-views.xml#calendar-month-view); +} + +calendar-task-tree { + -moz-binding: url(chrome://calendar/content/calendar-task-tree.xml#calendar-task-tree); +} + +menupopup[type="task-progress"] > arrowscrollbox { + -moz-binding: url(chrome://calendar/content/calendar-menus.xml#task-progress-menupopup); +} + +menupopup[type="task-priority"] > arrowscrollbox { + -moz-binding: url(chrome://calendar/content/calendar-menus.xml#task-priority-menupopup); +} + +task-menupopup { + -moz-binding: url(chrome://calendar/content/calendar-menus.xml#task-menupopup); +} + +calendar-caption { + -moz-binding: url("chrome://calendar/content/calendar-item-bindings.xml#calendar-caption"); +} + +.item-date-row { + -moz-binding: url(chrome://calendar/content/calendar-item-bindings.xml#item-date-row); +} diff --git a/calendar/base/content/calendar-calendars-list.xul b/calendar/base/content/calendar-calendars-list.xul new file mode 100644 index 00000000..fcca18e9 --- /dev/null +++ b/calendar/base/content/calendar-calendars-list.xul @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/calendar/base/content/calendar-chrome-startup.js b/calendar/base/content/calendar-chrome-startup.js new file mode 100644 index 00000000..71528b79 --- /dev/null +++ b/calendar/base/content/calendar-chrome-startup.js @@ -0,0 +1,220 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/iteratorUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Preferences.jsm"); + +/* exported commonInitCalendar, commonFinishCalendar */ + +/** + * Common initialization steps for calendar chrome windows. + */ +function commonInitCalendar() { + // Move around toolbarbuttons and whatever is needed in the UI. + migrateCalendarUI(); + + // Load the Calendar Manager + loadCalendarManager(); + + // Restore the last shown calendar view + switchCalendarView(getLastCalendarView(), false); + + // set up the unifinder + prepareCalendarToDoUnifinder(); + + // Make sure we update ourselves if the program stays open over midnight + scheduleMidnightUpdate(refreshUIBits); + + // Set up the command controller from calendar-common-sets.js + injectCalendarCommandController(); + + // Set up item and day selection listeners + getViewDeck().addEventListener("dayselect", observeViewDaySelect, false); + getViewDeck().addEventListener("itemselect", calendarController.onSelectionChanged, true); + + // Start alarm service + Components.classes["@mozilla.org/calendar/alarm-service;1"] + .getService(Components.interfaces.calIAlarmService) + .startup(); + document.getElementById("calsidebar_splitter").addEventListener("command", onCalendarViewResize, false); + window.addEventListener("resize", onCalendarViewResize, true); + + // Set up the category colors + categoryManagement.initCategories(); + + // Set up window pref observers + calendarWindowPrefs.init(); + + /* Ensure the new items commands state can be setup properly even when no + * calendar support refreshes (i.e. the "onLoad" notification) or when none + * are active. In specific cases such as for file-based ICS calendars can + * happen, the initial "onLoad" will already have been triggered at this + * point (see bug 714431 comment 29). We thus inconditionnally invoke + * calendarUpdateNewItemsCommand until somebody writes code that enables the + * checking of the calendar readiness (getProperty("ready") ?). + */ + calendarUpdateNewItemsCommand(); +} + +/** + * Common unload steps for calendar chrome windows. + */ +function commonFinishCalendar() { + // Unload the calendar manager + unloadCalendarManager(); + + // clean up the unifinder + finishCalendarToDoUnifinder(); + + // Remove the command controller + removeCalendarCommandController(); + + document.getElementById("calsidebar_splitter").removeEventListener("command", onCalendarViewResize, false); + window.removeEventListener("resize", onCalendarViewResize, true); + + // Clean up the category colors + categoryManagement.cleanupCategories(); + + // Clean up window pref observers + calendarWindowPrefs.cleanup(); +} + +/** + * Handler function to create |viewtype + "viewresized"| events that are + * dispatched through the calendarviewBroadcaster. + * + * XXX this has nothing to do with startup, needs to go somewhere else. + */ +function onCalendarViewResize(aEvent) { + let event = document.createEvent("Events"); + event.initEvent(currentView().type + "viewresized", true, false); + document.getElementById("calendarviewBroadcaster").dispatchEvent(event); +} + +/** + * TODO: The systemcolors pref observer really only needs to be set up once, so + * ideally this code should go into a component. This should be taken care of when + * there are more prefs that need to be observed on a global basis that don't fit + * into the calendar manager. + */ +var calendarWindowPrefs = { + + /** nsISupports QueryInterface */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]), + + /** Initialize the preference observers */ + init: function() { + Services.prefs.addObserver("calendar.view.useSystemColors", this, false); + Services.ww.registerNotification(this); + + // Trigger setting pref on all open windows + this.observe(null, "nsPref:changed", "calendar.view.useSystemColors"); + }, + + /** Cleanup the preference observers */ + cleanup: function() { + Services.prefs.removeObserver("calendar.view.useSystemColors", this); + Services.ww.unregisterNotification(this); + }, + + /** + * Observer function called when a pref has changed + * + * @see nsIObserver + */ + observe: function(aSubject, aTopic, aData) { + if (aTopic == "nsPref:changed") { + switch (aData) { + case "calendar.view.useSystemColors": { + let attributeValue = Preferences.get("calendar.view.useSystemColors", false) && "true"; + for (let win in fixIterator(Services.ww.getWindowEnumerator())) { + setElementValue(win.document.documentElement, attributeValue, "systemcolors"); + } + break; + } + } + } else if (aTopic == "domwindowopened") { + let win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow); + win.addEventListener("load", () => { + let attributeValue = Preferences.get("calendar.view.useSystemColors", false) && "true"; + setElementValue(win.document.documentElement, attributeValue, "systemcolors"); + }, false); + } + } +}; + +/** + * Migrate calendar UI. This function is called at each startup and can be used + * to change UI items that require js code intervention + */ +function migrateCalendarUI() { + const UI_VERSION = 3; + let currentUIVersion = Preferences.get("calendar.ui.version"); + if (currentUIVersion >= UI_VERSION) { + return; + } + + try { + if (currentUIVersion < 1) { + let calbar = document.getElementById("calendar-toolbar2"); + calbar.insertItem("calendar-appmenu-button"); + let taskbar = document.getElementById("task-toolbar2"); + taskbar.insertItem("task-appmenu-button"); + } + if (currentUIVersion < 2) { + // If the user has customized the event/task window dialog toolbar, + // we copy that custom set of toolbar items to the event/task tab + // toolbar and add the app menu button and a spring for alignment. + let xulStore = Components.classes["@mozilla.org/xul/xulstore;1"] + .getService(Components.interfaces.nsIXULStore); + let uri = "chrome://calendar/content/calendar-event-dialog.xul"; + + if (xulStore.hasValue(uri, "event-toolbar", "currentset")) { + let windowSet = xulStore.getValue(uri, "event-toolbar", "currentset"); + let items = "calendar-item-appmenu-button"; + if (!windowSet.includes("spring")) { + items = "spring," + items; + } + let previousSet = windowSet == "__empty" ? "" : windowSet + ","; + let tabSet = previousSet + items; + let tabBar = document.getElementById("event-tab-toolbar"); + + tabBar.currentSet = tabSet; + // For some reason we also have to do the following, + // presumably because the toolbar has already been + // loaded into the DOM so the toolbar's currentset + // attribute does not yet match the new currentSet. + tabBar.setAttribute("currentset", tabSet); + } + } + if (currentUIVersion < 3) { + // Rename toolbar button id "button-save" to + // "button-saveandclose" in customized toolbars + let xulStore = Components.classes["@mozilla.org/xul/xulstore;1"] + .getService(Components.interfaces.nsIXULStore); + let windowUri = "chrome://calendar/content/calendar-event-dialog.xul"; + let tabUri = "chrome://messenger/content/messenger.xul"; + + if (xulStore.hasValue(windowUri, "event-toolbar", "currentset")) { + let windowSet = xulStore.getValue(windowUri, "event-toolbar", "currentset"); + let newSet = windowSet.replace("button-save", "button-saveandclose"); + xulStore.setValue(windowUri, "event-toolbar", "currentset", newSet); + } + if (xulStore.hasValue(tabUri, "event-tab-toolbar", "currentset")) { + let tabSet = xulStore.getValue(tabUri, "event-tab-toolbar", "currentset"); + let newSet = tabSet.replace("button-save", "button-saveandclose"); + xulStore.setValue(tabUri, "event-tab-toolbar", "currentset", newSet); + + let tabBar = document.getElementById("event-tab-toolbar"); + tabBar.currentSet = newSet; + tabBar.setAttribute("currentset", newSet); + } + } + Preferences.set("calendar.ui.version", UI_VERSION); + } catch (e) { + cal.ERROR("Error upgrading UI from " + currentUIVersion + " to " + + UI_VERSION + ": " + e); + } +} diff --git a/calendar/base/content/calendar-clipboard.js b/calendar/base/content/calendar-clipboard.js new file mode 100644 index 00000000..20011ab9 --- /dev/null +++ b/calendar/base/content/calendar-clipboard.js @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://calendar/modules/calUtils.jsm"); + +/* exported cutToClipboard, pasteFromClipboard */ + +/** + * Test if a writable calendar is selected, and if the clipboard has items that + * can be pasted into Calendar. The data must be of type "text/calendar" or + * "text/unicode". + * + * @return If true, pasting is currently possible. + */ +function canPaste() { + let selectedCal = getSelectedCalendar(); + if (!selectedCal || !cal.isCalendarWritable(selectedCal)) { + return false; + } + + const flavors = ["text/calendar", "text/unicode"]; + return Services.clipboard.hasDataMatchingFlavors(flavors, + flavors.length, + Components.interfaces.nsIClipboard.kGlobalClipboard); +} + +/** + * Copy the ics data of the current view's selected events to the clipboard and + * deletes the events on success + */ +function cutToClipboard() { + if (copyToClipboard()) { + deleteSelectedItems(); + } +} + +/** + * Copy the ics data of the items in calendarItemArray to the clipboard. Fills + * both text/unicode and text/calendar mime types. + * + * @param calendarItemArray (optional) an array of items to copy. If not + * passed, the current view's selected items will + * be used. + * @return A boolean indicating if the operation succeeded. + */ +function copyToClipboard(calendarItemArray) { + if (!calendarItemArray) { + calendarItemArray = getSelectedItems(); + } + + if (!calendarItemArray.length) { + cal.LOG("[calendar-clipboard] No items to copy."); + return false; + } + + let icsSerializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"] + .createInstance(Components.interfaces.calIIcsSerializer); + icsSerializer.addItems(calendarItemArray, calendarItemArray.length); + let icsString = icsSerializer.serializeToString(); + + let clipboard = Services.clipboard; + let trans = Components.classes["@mozilla.org/widget/transferable;1"] + .createInstance(Components.interfaces.nsITransferable); + + if (trans && clipboard) { + // Register supported data flavors + trans.init(null); + trans.addDataFlavor("text/calendar"); + trans.addDataFlavor("text/unicode"); + + // Create the data objects + let icsWrapper = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString); + icsWrapper.data = icsString; + + // Add data objects to transferable + // Both Outlook 2000 client and Lotus Organizer use text/unicode + // when pasting iCalendar data. + trans.setTransferData("text/calendar", + icsWrapper, + icsWrapper.data.length * 2); // double byte data + trans.setTransferData("text/unicode", + icsWrapper, + icsWrapper.data.length * 2); + + clipboard.setData(trans, + null, + Components.interfaces.nsIClipboard.kGlobalClipboard); + + return true; + } + return false; +} + +/** + * Reads ics data from the clipboard, parses it into items and inserts the items + * into the currently selected calendar. + */ +function pasteFromClipboard() { + if (!canPaste()) { + return; + } + + let clipboard = Services.clipboard; + let trans = Components.classes["@mozilla.org/widget/transferable;1"] + .createInstance(Components.interfaces.nsITransferable); + + if (!trans || !clipboard) { + return; + } + + // Register the wanted data flavors (highest fidelity first!) + trans.init(null); + trans.addDataFlavor("text/calendar"); + trans.addDataFlavor("text/unicode"); + + // Get transferable from clipboard + clipboard.getData(trans, Components.interfaces.nsIClipboard.kGlobalClipboard); + + // Ask transferable for the best flavor. + let flavor = {}; + let data = {}; + trans.getAnyTransferData(flavor, data, {}); + data = data.value.QueryInterface(Components.interfaces.nsISupportsString).data; + switch (flavor.value) { + case "text/calendar": + case "text/unicode": { + let destCal = getSelectedCalendar(); + if (!destCal) { + return; + } + + let icsParser = Components.classes["@mozilla.org/calendar/ics-parser;1"] + .createInstance(Components.interfaces.calIIcsParser); + try { + icsParser.parseString(data); + } catch (e) { + // Ignore parser errors from the clipboard data, if it fails + // there will just be 0 items. + } + + let items = icsParser.getItems({}); + if (items.length == 0) { + return; + } + + // If there are multiple items on the clipboard, the earliest + // should be set to the selected day and the rest adjusted. + let earliestDate = null; + for (let item of items) { + let date = null; + if (item.startDate) { + date = item.startDate.clone(); + } else if (item.entryDate) { + date = item.entryDate.clone(); + } else if (item.dueDate) { + date = item.dueDate.clone(); + } + + if (!date) { + continue; + } + + if (!earliestDate || date.compare(earliestDate) < 0) { + earliestDate = date; + } + } + let firstDate = currentView().selectedDay; + + let offset = null; + if (earliestDate) { + // Timezones and DT/DST time may differ between the earliest item + // and the selected day. Determine the offset between the + // earliestDate in local time and the selected day in whole days. + earliestDate = earliestDate.getInTimezone(calendarDefaultTimezone()); + earliestDate.isDate = true; + offset = firstDate.subtractDate(earliestDate); + let deltaDST = firstDate.timezoneOffset - earliestDate.timezoneOffset; + offset.inSeconds += deltaDST; + } + + startBatchTransaction(); + for (let item of items) { + let newItem = item.clone(); + // Set new UID to allow multiple paste actions of the same + // clipboard content. + newItem.id = cal.getUUID(); + if (offset) { + cal.shiftItem(newItem, offset); + } + doTransaction("add", newItem, destCal, null, null); + } + endBatchTransaction(); + break; + } + default: + break; + } +} diff --git a/calendar/base/content/calendar-common-sets.js b/calendar/base/content/calendar-common-sets.js new file mode 100644 index 00000000..55736ca7 --- /dev/null +++ b/calendar/base/content/calendar-common-sets.js @@ -0,0 +1,950 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +/* exported injectCalendarCommandController, removeCalendarCommandController, + * setupContextItemType, minimonthPick, getSelectedItems, + * deleteSelectedItems, calendarUpdateNewItemsCommand + */ + +var CalendarDeleteCommandEnabled = false; +var CalendarNewEventsCommandEnabled = false; +var CalendarNewTasksCommandEnabled = false; + +/** + * Command controller to execute calendar specific commands + * @see nsICommandController + */ +var calendarController = { + defaultController: null, + + commands: { + // Common commands + "calendar_new_event_command": true, + "calendar_new_event_context_command": true, + "calendar_modify_event_command": true, + "calendar_delete_event_command": true, + + "calendar_modify_focused_item_command": true, + "calendar_delete_focused_item_command": true, + + "calendar_new_todo_command": true, + "calendar_new_todo_context_command": true, + "calendar_new_todo_todaypane_command": true, + "calendar_modify_todo_command": true, + "calendar_modify_todo_todaypane_command": true, + "calendar_delete_todo_command": true, + + "calendar_new_calendar_command": true, + "calendar_edit_calendar_command": true, + "calendar_delete_calendar_command": true, + + "calendar_import_command": true, + "calendar_export_command": true, + "calendar_export_selection_command": true, + + "calendar_publish_selected_calendar_command": true, + "calendar_publish_calendar_command": true, + "calendar_publish_selected_events_command": true, + + "calendar_view_next_command": true, + "calendar_view_prev_command": true, + + "calendar_toggle_orientation_command": true, + "calendar_toggle_workdays_only_command": true, + + "calendar_day-view_command": true, + "calendar_week-view_command": true, + "calendar_multiweek-view_command": true, + "calendar_month-view_command": true, + + "calendar_task_filter_command": true, + "calendar_task_filter_todaypane_command": true, + "calendar_reload_remote_calendars": true, + "calendar_show_unifinder_command": true, + "calendar_toggle_completed_command": true, + "calendar_percentComplete-0_command": true, + "calendar_percentComplete-25_command": true, + "calendar_percentComplete-50_command": true, + "calendar_percentComplete-75_command": true, + "calendar_percentComplete-100_command": true, + "calendar_priority-0_command": true, + "calendar_priority-9_command": true, + "calendar_priority-5_command": true, + "calendar_priority-1_command": true, + "calendar_general-priority_command": true, + "calendar_general-progress_command": true, + "calendar_general-postpone_command": true, + "calendar_postpone-1hour_command": true, + "calendar_postpone-1day_command": true, + "calendar_postpone-1week_command": true, + "calendar_task_category_command": true, + + "calendar_attendance_command": true, + + // for events/tasks in a tab + "cmd_save": true, + "cmd_accept": true, + + // Pseudo commands + "calendar_in_foreground": true, + "calendar_in_background": true, + "calendar_mode_calendar": true, + "calendar_mode_task": true, + + "cmd_selectAll": true + }, + + updateCommands: function() { + for (let command in this.commands) { + goUpdateCommand(command); + } + }, + + supportsCommand: function(aCommand) { + if (aCommand in this.commands) { + return true; + } + if (this.defaultContoller) { + return this.defaultContoller.supportsCommand(aCommand); + } + return false; + }, + + isCommandEnabled: function(aCommand) { + switch (aCommand) { + case "calendar_new_event_command": + case "calendar_new_event_context_command": + return CalendarNewEventsCommandEnabled; + case "calendar_modify_focused_item_command": + return this.item_selected; + case "calendar_modify_event_command": + return this.item_selected; + case "calendar_delete_focused_item_command": + return CalendarDeleteCommandEnabled && this.selected_items_writable; + case "calendar_delete_event_command": + return CalendarDeleteCommandEnabled && this.selected_items_writable; + case "calendar_new_todo_command": + case "calendar_new_todo_context_command": + case "calendar_new_todo_todaypane_command": + return CalendarNewTasksCommandEnabled; + case "calendar_modify_todo_command": + case "calendar_modify_todo_todaypane_command": + return this.todo_items_selected; + // This code is temporarily commented out due to + // bug 469684 Unifinder-todo: raising of the context menu fires blur-event + // this.todo_tasktree_focused; + case "calendar_edit_calendar_command": + return this.isCalendarInForeground(); + case "calendar_task_filter_command": + return true; + case "calendar_delete_todo_command": + if (!CalendarDeleteCommandEnabled) { + return false; + } + // falls through otherwise + case "calendar_toggle_completed_command": + case "calendar_percentComplete-0_command": + case "calendar_percentComplete-25_command": + case "calendar_percentComplete-50_command": + case "calendar_percentComplete-75_command": + case "calendar_percentComplete-100_command": + case "calendar_priority-0_command": + case "calendar_priority-9_command": + case "calendar_priority-5_command": + case "calendar_priority-1_command": + case "calendar_task_category_command": + case "calendar_general-progress_command": + case "calendar_general-priority_command": + case "calendar_general-postpone_command": + case "calendar_postpone-1hour_command": + case "calendar_postpone-1day_command": + case "calendar_postpone-1week_command": + return ((this.isCalendarInForeground() || this.todo_tasktree_focused) && + this.writable && + this.todo_items_selected && + this.todo_items_writable) || + document.getElementById("tabmail").currentTabInfo.mode.type == "calendarTask"; + case "calendar_delete_calendar_command": + return this.isCalendarInForeground() && !this.last_calendar; + case "calendar_import_command": + return this.writable; + case "calendar_export_selection_command": + return this.item_selected; + case "calendar_toggle_orientation_command": + return this.isInMode("calendar") && + currentView().supportsRotation; + case "calendar_toggle_workdays_only_command": + return this.isInMode("calendar") && + currentView().supportsWorkdaysOnly; + case "calendar_publish_selected_events_command": + return this.item_selected; + + case "calendar_reload_remote_calendar": + return !this.no_network_calendars && !this.offline; + case "calendar_attendance_command": { + let attendSel = false; + if (this.todo_tasktree_focused) { + attendSel = this.writable && + this.todo_items_invitation && + this.todo_items_selected && + this.todo_items_writable; + } else { + attendSel = this.item_selected && + this.selected_events_invitation && + this.selected_items_writable; + } + + // Small hack, we want to hide instead of disable. + setBooleanAttribute("calendar_attendance_command", "hidden", !attendSel); + return attendSel; + } + + // The following commands all just need the calendar in foreground, + // make sure you take care when changing things here. + case "calendar_view_next_command": + case "calendar_view_prev_command": + case "calendar_in_foreground": + return this.isCalendarInForeground(); + case "calendar_in_background": + return !this.isCalendarInForeground(); + + // The following commands need calendar mode, be careful when + // changing things. + case "calendar_day-view_command": + case "calendar_week-view_command": + case "calendar_multiweek-view_command": + case "calendar_month-view_command": + case "calendar_show_unifinder_command": + case "calendar_mode_calendar": + return this.isInMode("calendar"); + + case "calendar_mode_task": + return this.isInMode("task"); + + case "cmd_selectAll": + if (this.todo_tasktree_focused || this.isInMode("calendar")) { + return true; + } else if (this.defaultController.supportsCommand(aCommand)) { + return this.defaultController.isCommandEnabled(aCommand); + } + break; + + // for events/tasks in a tab + case "cmd_save": + // falls through + case "cmd_accept": { + let tabType = document.getElementById("tabmail").currentTabInfo.mode.type; + return tabType == "calendarTask" || tabType == "calendarEvent"; + } + + default: + if (this.defaultController && !this.isCalendarInForeground()) { + // The delete-button demands a special handling in mail-mode + // as it is supposed to delete an element of the focused pane + if (aCommand == "cmd_delete" || aCommand == "button_delete") { + let focusedElement = document.commandDispatcher.focusedElement; + if (focusedElement) { + if (focusedElement.getAttribute("id") == "agenda-listbox") { + return agendaListbox.isEventSelected(); + } else if (focusedElement.className == "calendar-task-tree") { + return this.writable && + this.todo_items_selected && + this.todo_items_writable; + } + } + } + + if (this.defaultController.supportsCommand(aCommand)) { + return this.defaultController.isCommandEnabled(aCommand); + } + } + if (aCommand in this.commands) { + // All other commands we support should be enabled by default + return true; + } + } + return false; + }, + + doCommand: function(aCommand) { + switch (aCommand) { + // Common Commands + case "calendar_new_event_command": + createEventWithDialog(getSelectedCalendar(), + getDefaultStartDate(currentView().selectedDay)); + break; + case "calendar_new_event_context_command": { + let newStart = currentView().selectedDateTime; + if (!newStart) { + newStart = getDefaultStartDate(currentView().selectedDay); + } + createEventWithDialog(getSelectedCalendar(), newStart, + null, null, null, + newStart.isDate == true); + break; + } + case "calendar_modify_event_command": + editSelectedEvents(); + break; + case "calendar_modify_focused_item_command": { + let focusedElement = document.commandDispatcher.focusedElement; + if (!focusedElement && this.defaultController && !this.isCalendarInForeground()) { + this.defaultController.doCommand(aCommand); + } else { + let focusedRichListbox = getParentNodeOrThis(focusedElement, "richlistbox"); + if (focusedRichListbox && focusedRichListbox.id == "agenda-listbox") { + agendaListbox.editSelectedItem(); + } else if (focusedElement && focusedElement.className == "calendar-task-tree") { + modifyTaskFromContext(); + } else if (this.isInMode("calendar")) { + editSelectedEvents(); + } + } + break; + } + case "calendar_delete_event_command": + deleteSelectedEvents(); + break; + case "calendar_delete_focused_item_command": { + let focusedElement = document.commandDispatcher.focusedElement; + if (!focusedElement && this.defaultController && !this.isCalendarInForeground()) { + this.defaultController.doCommand(aCommand); + } else { + let focusedRichListbox = getParentNodeOrThis(focusedElement, "richlistbox"); + if (focusedRichListbox && focusedRichListbox.id == "agenda-listbox") { + agendaListbox.deleteSelectedItem(false); + } else if (focusedElement && focusedElement.className == "calendar-task-tree") { + deleteToDoCommand(null, false); + } else if (this.isInMode("calendar")) { + deleteSelectedEvents(); + } + } + break; + } + case "calendar_new_todo_command": + createTodoWithDialog(getSelectedCalendar(), + null, null, null, + getDefaultStartDate(currentView().selectedDay)); + break; + case "calendar_new_todo_context_command": { + let initialDate = currentView().selectedDateTime; + if (!initialDate || initialDate.isDate) { + initialDate = getDefaultStartDate(currentView().selectedDay); + } + createTodoWithDialog(getSelectedCalendar(), + null, null, null, + initialDate); + break; + } + case "calendar_new_todo_todaypane_command": + createTodoWithDialog(getSelectedCalendar(), + null, null, null, + getDefaultStartDate(agendaListbox.today.start)); + break; + case "calendar_delete_todo_command": + deleteToDoCommand(); + break; + case "calendar_modify_todo_command": + modifyTaskFromContext(null, getDefaultStartDate(currentView().selectedDay)); + break; + case "calendar_modify_todo_todaypane_command": + modifyTaskFromContext(null, getDefaultStartDate(agendaListbox.today.start)); + break; + + case "calendar_new_calendar_command": + openCalendarWizard(); + break; + case "calendar_edit_calendar_command": + openCalendarProperties(getSelectedCalendar()); + break; + case "calendar_delete_calendar_command": + promptDeleteCalendar(getSelectedCalendar()); + break; + + case "calendar_import_command": + loadEventsFromFile(); + break; + case "calendar_export_command": + exportEntireCalendar(); + break; + case "calendar_export_selection_command": + saveEventsToFile(currentView().getSelectedItems({})); + break; + + case "calendar_publish_selected_calendar_command": + publishEntireCalendar(getSelectedCalendar()); + break; + case "calendar_publish_calendar_command": + publishEntireCalendar(); + break; + case "calendar_publish_selected_events_command": + publishCalendarData(); + break; + + case "calendar_reload_remote_calendars": + getCompositeCalendar().refresh(); + break; + case "calendar_show_unifinder_command": + toggleUnifinder(); + break; + case "calendar_view_next_command": + currentView().moveView(1); + break; + case "calendar_view_prev_command": + currentView().moveView(-1); + break; + case "calendar_toggle_orientation_command": + toggleOrientation(); + break; + case "calendar_toggle_workdays_only_command": + toggleWorkdaysOnly(); + break; + + case "calendar_day-view_command": + switchCalendarView("day", true); + break; + case "calendar_week-view_command": + switchCalendarView("week", true); + break; + case "calendar_multiweek-view_command": + switchCalendarView("multiweek", true); + break; + case "calendar_month-view_command": + switchCalendarView("month", true); + break; + case "calendar_attendance_command": + // This command is actually handled inline, since it takes a value + break; + + case "cmd_selectAll": + if (!this.todo_tasktree_focused && + this.defaultController && !this.isCalendarInForeground()) { + // Unless a task tree is focused, make the default controller + // take care. + this.defaultController.doCommand(aCommand); + } else { + selectAllItems(); + } + break; + + default: + if (this.defaultController && !this.isCalendarInForeground()) { + // If calendar is not in foreground, let the default controller take + // care. If we don't have a default controller, just continue. + this.defaultController.doCommand(aCommand); + return; + } + + } + return; + }, + + onEvent: function(aEvent) { + }, + + isCalendarInForeground: function() { + return gCurrentMode && gCurrentMode != "mail"; + }, + + isInMode: function(mode) { + switch (mode) { + case "mail": + return !isCalendarInForeground(); + case "calendar": + return gCurrentMode && gCurrentMode == "calendar"; + case "task": + return gCurrentMode && gCurrentMode == "task"; + } + return false; + }, + + onSelectionChanged: function(aEvent) { + let selectedItems = aEvent.detail; + + calendarUpdateDeleteCommand(selectedItems); + calendarController.item_selected = selectedItems && (selectedItems.length > 0); + + let selLength = (selectedItems === undefined ? 0 : selectedItems.length); + let selected_events_readonly = 0; + let selected_events_requires_network = 0; + let selected_events_invitation = 0; + + if (selLength > 0) { + for (let item of selectedItems) { + if (item.calendar.readOnly) { + selected_events_readonly++; + } + if (item.calendar.getProperty("requiresNetwork") && + !item.calendar.getProperty("cache.enabled") && + !item.calendar.getProperty("cache.always")) { + selected_events_requires_network++; + } + + if (cal.isInvitation(item)) { + selected_events_invitation++; + } else if (item.organizer) { + // If we are the organizer and there are attendees, then + // this is likely also an invitation. + let calOrgId = item.calendar.getProperty("organizerId"); + if (item.organizer.id == calOrgId && item.getAttendees({}).length) { + selected_events_invitation++; + } + } + } + } + + calendarController.selected_events_readonly = + (selected_events_readonly == selLength); + + calendarController.selected_events_requires_network = + (selected_events_requires_network == selLength); + calendarController.selected_events_invitation = + (selected_events_invitation == selLength); + + calendarController.updateCommands(); + calendarController2.updateCommands(); + document.commandDispatcher.updateCommands("mail-toolbar"); + }, + + /** + * Condition Helpers + */ + + // These attributes will be set up manually. + item_selected: false, + selected_events_readonly: false, + selected_events_requires_network: false, + selected_events_invitation: false, + + /** + * Returns a boolean indicating if its possible to write items to any + * calendar. + */ + get writable() { + return cal.getCalendarManager().getCalendars({}).some(cal.isCalendarWritable); + }, + + /** + * Returns a boolean indicating if the application is currently in offline + * mode. + */ + get offline() { + return Services.io.offline; + }, + + /** + * Returns a boolean indicating if all calendars are readonly. + */ + get all_readonly() { + let calMgr = getCalendarManager(); + return (calMgr.readOnlyCalendarCount == calMgr.calendarCount); + }, + + /** + * Returns a boolean indicating if all calendars are local + */ + get no_network_calendars() { + return (getCalendarManager().networkCalendarCount == 0); + }, + + /** + * Returns a boolean indicating if there are calendars that don't require + * network access. + */ + get has_local_calendars() { + let calMgr = getCalendarManager(); + return (calMgr.networkCalendarCount < calMgr.calendarCount); + }, + + /** + * Returns a boolean indicating if there are cached calendars and thus that don't require + * network access. + */ + get has_cached_calendars() { + let calMgr = getCalendarManager(); + let calendars = calMgr.getCalendars({}); + for (let calendar of calendars) { + if (calendar.getProperty("cache.enabled") || calendar.getProperty("cache.always")) { + return true; + } + } + return false; + }, + + /** + * Returns a boolean indicating that there is only one calendar left. + */ + get last_calendar() { + return (getCalendarManager().calendarCount < 2); + }, + + /** + * Returns a boolean indicating that all local calendars are readonly + */ + get all_local_calendars_readonly() { + // We might want to speed this part up by keeping track of this in the + // calendar manager. + let calendars = getCalendarManager().getCalendars({}); + let count = calendars.length; + for (let calendar of calendars) { + if (!isCalendarWritable(calendar)) { + count--; + } + } + return (count == 0); + }, + + /** + * Returns a boolean indicating that at least one of the items selected + * in the current view has a writable calendar. + */ + get selected_items_writable() { + return this.writable && + this.item_selected && + !this.selected_events_readonly && + (!this.offline || !this.selected_events_requires_network); + }, + + /** + * Returns a boolean indicating that tasks are selected. + */ + get todo_items_selected() { + let selectedTasks = getSelectedTasks(); + return (selectedTasks.length > 0); + }, + + + get todo_items_invitation() { + let selectedTasks = getSelectedTasks(); + let selected_tasks_invitation = 0; + + for (let item of selectedTasks) { + if (cal.isInvitation(item)) { + selected_tasks_invitation++; + } else if (item.organizer) { + // If we are the organizer and there are attendees, then + // this is likely also an invitation. + let calOrgId = item.calendar.getProperty("organizerId"); + if (item.organizer.id == calOrgId && item.getAttendees({}).length) { + selected_tasks_invitation++; + } + } + } + + return (selectedTasks.length == selected_tasks_invitation); + }, + + /** + * Returns a boolean indicating that at least one task in the selection is + * on a calendar that is writable. + */ + get todo_items_writable() { + let selectedTasks = getSelectedTasks(); + for (let task of selectedTasks) { + if (isCalendarWritable(task.calendar)) { + return true; + } + } + return false; + } +}; + +/** + * XXX This is a temporary hack so we can release 1.0b2. This will soon be + * superceeded by a new command controller architecture. + */ +var calendarController2 = { + defaultController: null, + + commands: { + cmd_cut: true, + cmd_copy: true, + cmd_paste: true, + cmd_undo: true, + cmd_redo: true, + cmd_print: true, + cmd_pageSetup: true, + + cmd_printpreview: true, + button_print: true, + button_delete: true, + cmd_delete: true, + cmd_properties: true, + cmd_goForward: true, + cmd_goBack: true, + cmd_fullZoomReduce: true, + cmd_fullZoomEnlarge: true, + cmd_fullZoomReset: true, + cmd_showQuickFilterBar: true + }, + + // These functions can use the same from the calendar controller for now. + updateCommands: calendarController.updateCommands, + supportsCommand: calendarController.supportsCommand, + onEvent: calendarController.onEvent, + + isCommandEnabled: function(aCommand) { + switch (aCommand) { + // Thunderbird Commands + case "cmd_cut": + return calendarController.selected_items_writable; + case "cmd_copy": + return calendarController.item_selected; + case "cmd_paste": + return canPaste(); + case "cmd_undo": + goSetMenuValue(aCommand, "valueDefault"); + return canUndo(); + case "cmd_redo": + goSetMenuValue(aCommand, "valueDefault"); + return canRedo(); + case "button_delete": + case "cmd_delete": + return calendarController.isCommandEnabled("calendar_delete_focused_item_command"); + case "cmd_fullZoomReduce": + case "cmd_fullZoomEnlarge": + case "cmd_fullZoomReset": + return calendarController.isInMode("calendar") && + currentView().supportsZoom; + case "cmd_properties": + case "cmd_printpreview": + return false; + case "cmd_showQuickFilterBar": + return calendarController.isInMode("task"); + default: + return true; + } + }, + + doCommand: function(aCommand) { + switch (aCommand) { + case "cmd_cut": + cutToClipboard(); + break; + case "cmd_copy": + copyToClipboard(); + break; + case "cmd_paste": + pasteFromClipboard(); + break; + case "cmd_undo": + undo(); + break; + case "cmd_redo": + redo(); + break; + case "cmd_pageSetup": + PrintUtils.showPageSetup(); + break; + case "button_print": + case "cmd_print": + calPrint(); + break; + + // Thunderbird commands + case "cmd_goForward": + currentView().moveView(1); + break; + case "cmd_goBack": + currentView().moveView(-1); + break; + case "cmd_fullZoomReduce": + currentView().zoomIn(); + break; + case "cmd_fullZoomEnlarge": + currentView().zoomOut(); + break; + case "cmd_fullZoomReset": + currentView().zoomReset(); + break; + case "cmd_showQuickFilterBar": + document.getElementById("task-text-filter-field").select(); + break; + + case "button_delete": + case "cmd_delete": + calendarController.doCommand("calendar_delete_focused_item_command"); + break; + } + } +}; + +/** + * Inserts the command controller into the document. On Lightning, also make + * sure that it is inserted before the conflicting thunderbird command + * controller. + */ +function injectCalendarCommandController() { + // We need to put our new command controller *before* the one that + // gets installed by thunderbird. Since we get called pretty early + // during startup we need to install the function below as a callback + // that periodically checks when the original thunderbird controller + // gets alive. Please note that setTimeout with a value of 0 means that + // we leave the current thread in order to re-enter the message loop. + + let tbController = top.controllers.getControllerForCommand("cmd_runJunkControls"); + if (tbController) { + calendarController.defaultController = tbController; + top.controllers.insertControllerAt(0, calendarController); + document.commandDispatcher.updateCommands("calendar_commands"); + } else { + setTimeout(injectCalendarCommandController, 0); + } +} + +/** + * Remove the calendar command controller from the document. + */ +function removeCalendarCommandController() { + top.controllers.removeController(calendarController); +} + +/** + * Handler function to set up the item context menu, depending on the given + * items. Changes the delete menuitem to fit the passed items. + * + * @param event The DOM popupshowing event that is triggered by opening + * the context menu. + * @param items An array of items (usually the selected items) to adapt + * the context menu for. + * @return True, to show the popup menu. + */ +function setupContextItemType(event, items) { + function adaptModificationMenuItem(aMenuItemId, aItemType) { + let menuItem = document.getElementById(aMenuItemId); + if (menuItem) { + menuItem.setAttribute("label", calGetString("calendar", "delete" + aItemType + "Label")); + menuItem.setAttribute("accesskey", calGetString("calendar", "delete" + aItemType + "Accesskey")); + } + } + if (items.some(isEvent) && items.some(isToDo)) { + event.target.setAttribute("type", "mixed"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Item"); + } else if (items.length && isEvent(items[0])) { + event.target.setAttribute("type", "event"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Event"); + } else if (items.length && isToDo(items[0])) { + event.target.setAttribute("type", "todo"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Task"); + } else { + event.target.removeAttribute("type"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Item"); + } + + let menu = document.getElementById("calendar-item-context-menu-attendance-menu"); + setupAttendanceMenu(menu, items); + + return true; +} + +/** + * Shows the given date in the current view, if in calendar mode. + * + * XXX This function is misplaced, should go to calendar-views.js or a minimonth + * specific js file. + * + * @param aNewDate The new date as a JSDate. + */ +function minimonthPick(aNewDate) { + if (gCurrentMode == "calendar" || gCurrentMode == "task") { + let cdt = cal.jsDateToDateTime(aNewDate, currentView().timezone); + cdt.isDate = true; + currentView().goToDay(cdt); + + // update date filter for task tree + let tree = document.getElementById("calendar-task-tree"); + tree.updateFilter(); + } +} + +/** + * Selects all items, based on which mode we are currently in and what task tree is focused + */ +function selectAllItems() { + if (calendarController.todo_tasktree_focused) { + getTaskTree().selectAll(); + } else if (calendarController.isInMode("calendar")) { + selectAllEvents(); + } +} + +/** + * Returns the selected items, based on which mode we are currently in and what task tree is focused + */ +function getSelectedItems() { + if (calendarController.todo_tasktree_focused) { + return getSelectedTasks(); + } + + return currentView().getSelectedItems({}); +} + +/** + * Deletes the selected items, based on which mode we are currently in and what task tree is focused + */ +function deleteSelectedItems() { + if (calendarController.todo_tasktree_focused) { + deleteToDoCommand(); + } else if (calendarController.isInMode("calendar")) { + deleteSelectedEvents(); + } +} + +function calendarUpdateNewItemsCommand() { + // keep current current status + let oldEventValue = CalendarNewEventsCommandEnabled; + let oldTaskValue = CalendarNewTasksCommandEnabled; + + // define command set to update + let eventCommands = ["calendar_new_event_command", + "calendar_new_event_context_command"]; + let taskCommands = ["calendar_new_todo_command", + "calendar_new_todo_context_command", + "calendar_new_todo_todaypane_command"]; + + // re-calculate command status + CalendarNewEventsCommandEnabled = false; + CalendarNewTasksCommandEnabled = false; + let calendars = cal.getCalendarManager().getCalendars({}).filter(cal.isCalendarWritable).filter(userCanAddItemsToCalendar); + if (calendars.some(cal.isEventCalendar)) { + CalendarNewEventsCommandEnabled = true; + } + if (calendars.some(cal.isTaskCalendar)) { + CalendarNewTasksCommandEnabled = true; + } + + // update command status if required + if (CalendarNewEventsCommandEnabled != oldEventValue) { + eventCommands.forEach(goUpdateCommand); + } + if (CalendarNewTasksCommandEnabled != oldTaskValue) { + taskCommands.forEach(goUpdateCommand); + } +} + +function calendarUpdateDeleteCommand(selectedItems) { + let oldValue = CalendarDeleteCommandEnabled; + CalendarDeleteCommandEnabled = (selectedItems.length > 0); + + /* we must disable "delete" when at least one item cannot be deleted */ + for (let item of selectedItems) { + if (!userCanDeleteItemsFromCalendar(item.calendar)) { + CalendarDeleteCommandEnabled = false; + break; + } + } + + if (CalendarDeleteCommandEnabled != oldValue) { + let commands = ["calendar_delete_event_command", + "calendar_delete_todo_command", + "calendar_delete_focused_item_command", + "button_delete", + "cmd_delete"]; + for (let command of commands) { + goUpdateCommand(command); + } + } +} diff --git a/calendar/base/content/calendar-common-sets.xul b/calendar/base/content/calendar-common-sets.xul new file mode 100644 index 00000000..df70d3dd --- /dev/null +++ b/calendar/base/content/calendar-common-sets.xul @@ -0,0 +1,582 @@ + + + + %calendarDTD; + %eventDialogDTD; + %menuOverlayDTD; +]> + + + + + +