cozy/cozy-mobile

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

Summary

Maintainability
Test Coverage
log = require('../../lib/persistent_log')
    prefix: "CozyToAndroidContact"
    date: true


# Helpers


################################################################################
# Convert a cozy contact object in cordova contact object.
#######################################

# Convert 'n' field to cordova ContactName object.
# @param the cozy's n field.
_n2ContactName = (n) ->
    return undefined unless n?

    parts =  n.split ';'

    # Cf cozy-vCard.nToFN :
    validParts = parts.filter (part) -> part? and part isnt ''
    formatted = validParts.join ' '

    contactName = new ContactName formatted, parts...

    contactName.formatted ?= ""
    contactName.familyName ?= ""
    contactName.givenName ?= ""
    contactName.middleName ?= ""
    contactName.honorificPrefix ?= ""
    contactName.honorificSuffix ?= ""

    return contactName


# Build cordova's ContactOrganization list from a cozy contact.
_cozyContact2ContactOrganizations = (contact) ->
    if contact.org
        return [
            new ContactOrganization false, null, contact.org
            , contact.department, contact.title
        ]
    else
        return []


# Initialize a url's ContactFields list with url field of cozy contact.
_cozyContact2URLs = (contact) ->
    callback = (dp) ->
        dp.type is "url" and dp.value is contact.url

    contactFields = []
    # Avoid duplication of url in datapoints.
    if contact.url and not contact.datapoints.some callback
        contactFields.push new ContactField 'other', contact.url, false

    return contactFields


# Build categories list with cozy's tags.
_tags2Categories = (tags) ->
    if tags
        return tags.map (tag) ->
            return new ContactField 'categories', tag, false
    else
        return []


# Build pohto (list) field from contact's photo.
_attachments2Photos = (contact) ->
    if contact._attachments? and 'picture' of contact._attachments
        try
            data = contact._attachments.picture.data
            # atob allow to ensure this data was a valid base64,
            # if is not valid and save in Android our app is killed...
            window.atob data
            photo = new ContactField 'base64', contact._attachments.picture.data
            return [photo]
        catch e
            if e.code is DOMException.INVALID_CHARACTER_ERR
                log.warn 'base64 invalid.'
    return []

_adr2ContactAddress = (datapoint) ->
    if datapoint.value instanceof Array
        structuredToFlat = (t) ->
            t = t.filter (part) -> return part? and part isnt ''
            return t.join ', '
        street = structuredToFlat datapoint.value[0..2]
        countryPart = structuredToFlat datapoint.value[3..6]
        formatted = street
        formatted += '\n' + countryPart if countryPart isnt ''

        return new ContactAddress undefined
        , datapoint.type
        , formatted, street, datapoint.value[3], datapoint.value[4]
        , datapoint.value[5], datapoint.value[6]

    else if typeof(datapoint.value) is 'string'
        return new ContactAddress undefined
        , datapoint.type, datapoint.value, datapoint.value

    else
        log.warning 'adr datapoint has bad type'
        return new ContactAddress undefined, datapoint.type, ''


# loop trought the cozy's datapoints list and fill up the cordovaContact
# with
_dataPoints2Cordova = (cozyContact, cordovaContact) ->
    addContactField = (cordovaField, datapoint) ->
        cordovaContact[cordovaField] ?= []

        fieldsJson = []
        for field in cordovaContact[cordovaField]
            fieldsJson.push JSON.stringify field

        field = new ContactField datapoint.type, datapoint.value
        fieldJson = JSON.stringify field

        if fieldsJson.indexOf(fieldJson) is -1
            cordovaContact[cordovaField].push field

    for i, datapoint of cozyContact.datapoints
        name = datapoint.name.toUpperCase()
        switch name
            when 'TEL'
                addContactField 'phoneNumbers', datapoint

            when 'EMAIL'
                addContactField 'emails', datapoint
            when 'ADR'
                cordovaContact.addresses ?= []
                addressesJson = []
                for address in cordovaContact.addresses
                    addressesJson.push JSON.stringify address
                address = _adr2ContactAddress datapoint
                addressJson = JSON.stringify address
                if addressesJson.indexOf(addressJson) is -1
                    cordovaContact.addresses.push address
            when 'CHAT'
                addContactField 'ims', datapoint


            when 'SOCIAL', 'URL'
                addContactField 'urls', datapoint

            when 'ABOUT'
                addContactField 'about', datapoint

            when 'RELATION'
                addContactField 'relations', datapoint




################################################################################
# Convert a cordova contact to cozy contact (asynchronous).
#######################################



_contactName2N = (contactName) ->
    return undefined unless contactName?

    parts = []
    fields = [ 'familyName', 'givenName', 'middleName', 'honorificPrefix',
        'honorificSuffix' ]

    for field in fields
        parts.push contactName[field] or ''

    n = parts.join ';'
    return n if n isnt ';;;;'


_categories2Tags = (categories) ->
    if categories?
        return categories.map (category) -> return category.value

# Pick the first organisation in cordova's organizations field, and put it
# in cozyContact fields.
_organizations2Cozy = (organizations, cozyContact) ->
    if organizations?.length > 0
        organization = organizations[0]
        cozyContact.org = organization.name
        cozyContact.department = organization.department
        cozyContact.title = organization.title

# Fill datapoints from cordova data.
_cordova2Datapoints = (cordovaContact, cozyContact) ->
    datapoints = []
    field2Name =
        'phoneNumbers': 'tel'
        'emails': 'email'
        'ims': 'chat'
        'about': 'about'
        'relations': 'relation'

    for fieldName, name of field2Name
        fields = cordovaContact[fieldName]
        if fields?.length > 0
            fieldsDatapoints = fields.map (contactField) ->
                name: name
                type: contactField.type
                value: contactField.value

            datapoints = datapoints.concat fieldsDatapoints

    if cordovaContact.addresses?.length > 0
        fieldsDatapoints = cordovaContact.addresses.map (contactAddress) ->
            name: 'adr'
            type: contactAddress.type
            value: ['', '', contactAddress.formatted, '', '', '', '']

        datapoints = datapoints.concat fieldsDatapoints

    if cordovaContact.urls?.length > 0
        fieldsDatapoints = cordovaContact.urls.map (contactField) ->
            name: 'url'
            type: contactField.type
            value: contactField.value

        datapoints = datapoints.concat fieldsDatapoints


    cozyContact.datapoints = datapoints


module.exports = class CozyToAndroidContact


    transform: (cozyContact) ->
        # Build cordova contact.
        cordovaContact =
            # vCard FullName = display name
            # (Prefix Given Middle Familly Suffix), or something else.
            displayName: cozyContact.fn
            # vCard Name = splitted
            # (Familly;Given;Middle;Prefix;Suffix)
            name: _n2ContactName cozyContact.n
            nickname: cozyContact.nickname
            organizations: _cozyContact2ContactOrganizations cozyContact
            birthday: cozyContact.bday
            urls: _cozyContact2URLs cozyContact
            note: cozyContact.note
            categories: _tags2Categories cozyContact.tags #
            photos: _attachments2Photos cozyContact

            sourceId: cozyContact._id
            sync2: cozyContact._rev
            # sync3: cozyContact.revision
            dirty: false
            deleted: false

        _dataPoints2Cordova cozyContact, cordovaContact

        # Defensive, unnamed contact are hard to use...
        unless cordovaContact.displayName
            cordovaContact.displayName = "--"

        return cordovaContact


    # Convert a cordova contact to cozy contact (asynchronous).
    reverseTransform: (cordovaContact, callback) ->
        return callback new Error 'No cordova contact' unless cordovaContact?
        cozyContact =
            docType: 'contact'
            _id: cordovaContact.sourceId
            id: cordovaContact.sourceId
            _rev: cordovaContact.sync2
            # vCard FullName = display name
            # (Prefix Given Middle Familly Suffix), or something else.
            fn: cordovaContact.displayName
            # vCard Name = splitted
            # (Familly;Given;Middle;Prefix;Suffix)
            n: _contactName2N cordovaContact.name
            bday: cordovaContact.birthday
            nickname: cordovaContact.nickname
            revision: new Date().toISOString()
            note: cordovaContact.note
            tags: _categories2Tags cordovaContact.categories

        _organizations2Cozy cordovaContact.organizations, cozyContact

        _cordova2Datapoints cordovaContact, cozyContact

        unless cordovaContact.photos?.length > 0
            return callback null, cozyContact

        photo = cordovaContact.photos[0]

        if photo.type is 'base64'
            cozyContact._attachments =
                    picture:
                        content_type: 'application/octet-stream'
                        data: photo.value

            callback null, cozyContact

        else if photo.type is 'url'
            img = new Image()

            img.onerror = -> callback new Error 'While resizing avatar.'

            img.onload = ->
                IMAGE_DIMENSION = 600
                ratiodim = if img.width > img.height then 'height' else 'width'
                ratio = IMAGE_DIMENSION / img[ratiodim]

                # use canvas to resize the image
                canvas = document.createElement 'canvas'
                canvas.height = canvas.width = IMAGE_DIMENSION
                ctx = canvas.getContext '2d'
                ctx.drawImage img, 0, 0, ratio * img.width, ratio * img.height
                dataUrl = canvas.toDataURL 'image/jpeg'

                cozyContact._attachments =
                    picture:
                        content_type: 'application/octet-stream'
                        data: dataUrl.split(',')[1]

                callback null, cozyContact

            img.src = photo.value