cozy/cozy-mobile

View on GitHub
src/app/replicator/transformer/cozy_to_android_event.coffee

Summary

Maintainability
Test Coverage
AndroidAccount = require '../fromDevice/android_account'
random = require '../../lib/random'
log = require('../../lib/persistent_log')
    prefix: "CozyToAndroidEvent"
    date: true

module.exports = class CozyToAndroidEvent

    # Tools to convert contact from cordova to cozy, and from cozy to cordova.
    DATE_FORMAT = 'YYYY-MM-DD'
    AMBIGUOUS_DT_FORMAT = 'YYYY-MM-DD[T]HH:mm:00.000'
    UTC_DT_FORMAT = 'YYYY-MM-DD[T]HH:mm:00.000[Z]'

    ATTENDEE_STATUS_2_ANDROID =
        'ACCEPTED':            1 # ATTENDEE_STATUS_ACCEPTED
        'DECLINED':            2 # ATTENDEE_STATUS_DECLINED
        'INVITATION-NOT-SENT': 4 # ATTENDEE_STATUS_TENTATIVE
        'NEEDS-ACTION':        3 # ATTENDEE_STATUS_INVITED
        #                      0 # ATTENDEE_STATUS_NONE

    ATTENDEE_STATUS_2_COZY = _.invert ATTENDEE_STATUS_2_ANDROID

    REMINDERS_METHOD_2_ANDROID =
        EMAIL:   2 # METHOD_EMAIL
        DISPLAY: 1 # METHOD_ALERT

    REMINDERS_METHOD_2_COZY = _.invert REMINDERS_METHOD_2_ANDROID

    transform: (cozyEvent, androidCalendar, androidTimezone, androidEvent) ->
        log.debug "transform"

        allDay = if cozyEvent.start.length is 10 then 1 else 0 # 1 == allday
        rrule = undefined
        dtstart = undefined # Unix millisecond timestamp
        dtend = undefined # Unix millisecond timestamp
        eventTimezone = 'UTC'

        if cozyEvent.rrule? and cozyEvent.rrule isnt ''
            rules = cozyEvent.rrule.split ';'
            rruless = []
            for rule in rules
                if rule[0..6] isnt 'DTSTART' and rule[0..4] isnt 'DTEND'
                    rruless.push rule
            rrule = rruless.join ';'
            log.debug "rrule: #{rrule}"
            # Europe/Paris is a stub for buggy docs.
            eventTimezone = cozyEvent.timezone or 'Europe/Paris' unless allDay
            duration = moment(cozyEvent.end).diff cozyEvent.start
            duration = moment.duration duration
            duration = JSON.stringify duration
            duration = duration.replace /"/g, ''
            dtstart = parseInt \
                    moment.tz(cozyEvent.start, cozyEvent.timezone).format 'x'
        else # Punctual, datetime are in UTC timezone.
            dtstart = parseInt moment.tz(cozyEvent.start, 'UTC').format 'x'
            dtend = parseInt moment.tz(cozyEvent.end, 'UTC').format 'x'
            eventTimezone = androidTimezone

        # attendees


        attendees = cozyEvent.attendees.map (attendee) ->
            android =
                #_id: automatically set by the android plugin
                #event_id: automatically set by the android plugin
                #attendeeName: # not in cozy's format
                attendeeEmail: attendee.email
                attendeeRelationship: 0 # deactivated
                attendeeType: 0 # deactivated
                attendeeStatus: ATTENDEE_STATUS_2_ANDROID[attendee.status] or 0
                # Z: won't keep link to contact id (?)
            return android

        # reminders



        iCalDuration2Minutes = (s) ->
            # TODO use moment.duration (but look sensitive.)
            minutes = 0
            parts = s.match /(\d+)[WDHMS]/g
            for part in parts
                number = parseInt part.slice 0, part.length - 1
                switch part[part.length - 1]
                    when 'M' then minutes += number
                    when 'H' then minutes += number * 60
                    when 'D' then minutes += number * 60 * 24
                    when 'W' then minutes += number * 60 * 24 * 7

            return minutes

        reminders = cozyEvent.alarms.map (alarm) ->
            return {
                minutes: iCalDuration2Minutes alarm.trigg
                method: REMINDERS_METHOD_2_ANDROID[alarm.action] or 0 # DEFAULT
            }

        return {
            _id: if androidEvent then androidEvent._id else undefined
            calendar_id: androidCalendar._id
            # organizer: undefined
            title: cozyEvent.description
            eventLocation: cozyEvent.place
            description: cozyEvent.details
            dtstart: dtstart
            dtend: dtend
            duration: duration
            eventTimezone: eventTimezone
            allDay: allDay
            rrule: rrule
            # "accessLevel": 0,
            # "availability": 0,
            # "guestsCanModify": 0,
            # "guestsCanInviteOthers": 1,
            # "guestsCanSeeGuests": 1,
            # "dirty": 0,
            _sync_id: cozyEvent._id
            sync_data2: cozyEvent._rev
            sync_data5: cozyEvent.lastModification
            attendees: attendees
            reminders: reminders
        }

    reverseTransform: (androidEvent, androidCalendar, cozyEvent = undefined) ->
        log.debug "reverseTransform"

        start = undefined
        end = undefined
        timezone = undefined

        if androidEvent.rrule? and androidEvent.rrule isnt ''
            startMoment = moment.tz androidEvent.dtstart, \
                    androidEvent.eventTimezone
            duration = @android2Duration androidEvent.duration
            endMoment = moment startMoment
            endMoment = endMoment.add duration
        else
            startMoment = moment.tz androidEvent.dtstart, \
                    androidEvent.eventTimezone
            endMoment = moment.tz androidEvent.dtend, androidEvent.eventTimezone

        if androidEvent.allDay
            start = startMoment.format DATE_FORMAT
            # # Hack to help moving to the next day.
            # endMoment.add 10, 'seconds'
            end = endMoment.format DATE_FORMAT

        else if androidEvent.rrule
            timezone = androidEvent.eventTimezone
            start = startMoment.format AMBIGUOUS_DT_FORMAT
            end = endMoment.format AMBIGUOUS_DT_FORMAT

        else
            start = startMoment.toISOString()
            end = endMoment.toISOString()


        attendees = androidEvent.attendees.map (attendee) ->
            if cozyEvent? and cozyEvent.attendees? and \
                    cozyEvent.attendees.length isnt 0
                cozyAttendee = cozyEvent.attendees.filter (cozyAttendee) ->
                    cozyAttendee.email is attendee.attendeeEmail
                cozyAttendee = cozyAttendee[0]

                status = ATTENDEE_STATUS_2_COZY[attendee.attendeeStatus]
                if status
                    cozyAttendee.status = status

                return cozyAttendee

            else
                transcripted =
                    key: random.randomString()
                    status: ATTENDEE_STATUS_2_COZY[attendee.attendeeStatus] or \
                        'NEEDS-ACTION'
                    email: attendee.attendeeEmail
                return transcripted

        if attendees
            attendees = attendees.filter (attendee) ->
                return attendee.email isnt AndroidAccount.NAME


        alarms = androidEvent.reminders.map (reminder) ->
            action = REMINDERS_METHOD_2_COZY[reminder.method] or 'DISPLAY'
            trigg = moment.duration reminder.minutes * -1, 'minutes'
            trigg = JSON.stringify trigg
            trigg = trigg.replace /"/g, ''

            return { action, trigg }

        event =
            docType: 'event'
            start: start
            end: end
            place: androidEvent.eventLocation
            description: androidEvent.title
            details: androidEvent.description
            rrule: androidEvent.rrule
            tags: [androidCalendar.calendar_displayName]
            attendees: attendees
            timezone: timezone
            alarms: alarms

            created: new Date().toISOString()
            lastModification: new Date().toISOString()

        # Update scenario.
        if cozyEvent?
            tags = cozyEvent.tags
            tags[0] = event.tags[0]

            created = cozyEvent.created
            event = _.extend cozyEvent, event

            event.tags = tags
            event.created = created

        return event

    # Android may ommit the 'T' in PT3600S, which is required by momentjs
    # Return a moment.duration
    android2Duration: (duration) ->
        if 'T' not in duration and not /[YMD]/.test(duration)
            duration = 'PT' + duration.slice 1

        return moment.duration duration