<commit_before>async  = require 'async'
device = require('cozy-device-sdk').device
log    = require('printit')
    prefix: 'Synchronize   '
    date: true


# Sync listens to PouchDB about the metadata changes, and calls local and
# remote sides to apply the changes on the filesystem and remote CouchDB
# respectively.
class Sync

    constructor: (@pouch, @local, @remote, @ignore, @events) ->
        @local.other = @remote
        @remote.other = @local

    # Start to synchronize the remote cozy with the local filesystem
    # First, start metadata synchronization in pouch, with the watchers
    # Then, when a stable state is reached, start applying changes from pouch
    #
    # The mode can be:
    # - pull if only changes from the remote cozy are applied to the fs
    # - push if only changes from the fs are applied to the remote cozy
    # - full for the full synchronization of the both sides
    #
    # The callback is called only for an error
    start: (mode, callback) =>
        @stopped = false
        tasks = [
            (next) => @pouch.addAllViews next
        ]
        tasks.push @local.start  unless mode is 'pull'
        tasks.push @remote.start unless mode is 'push'
        async.waterfall tasks, (err) =>
            if err
                callback err
            else
                async.forever @sync, callback

    # Stop the synchronization
    stop: (callback) =>
        @stopped = true
        if @changes
            @changes.cancel()
            @changes = null
        async.parallel [
            (done) => @local.stop done
            (done) => @remote.stop done
        ], callback

    # Start taking changes from pouch and applying them
    sync: (callback) =>
        @pop (err, change) =>
            return if @stopped
            if err
                log.error err
                callback err
            else
                @apply change, (err) ->
                    err = null if @stopped
                    callback err

    # Take the next change from pouch
    # We filter with the byPath view to reject design documents
    #
    # Note: it is difficult to pick only one change at a time because pouch can
    # emit several docs in a row, and `limit: 1` seems to be not effective!
    pop: (callback) =>
        @pouch.getLocalSeq (err, seq) =>
            return callback err if err
            opts =
                limit: 1
                since: seq
                include_docs: true
                filter: '_view'
                view: 'byPath'
            @pouch.db.changes(opts)
                .on 'change', (info) ->
                    callback null, info
                .on 'error', (err) ->
                    callback err
                .on 'complete', (info) =>
                    return if info.results?.length
                    @events.emit 'up-to-date'
                    log.info 'Your cozy is up to date!'
                    opts.live = true
                    opts.returnDocs = false
                    @changes = @pouch.db.changes(opts)
                        .on 'change', (info) =>
                            if @changes
                                @changes.cancel()
                                @changes = null
                                callback null, info
                        .on 'error', (err) =>
                            if @changes
                                @changes = null
                                callback err, null

    # Apply a change to both local and remote
    # At least one side should say it has already this change
    # In some cases, both sides have the change
    apply: (change, callback) =>
        log.info 'apply', change
        doc = change.doc

        if @ignore.isIgnored doc
            @pouch.setLocalSeq change.seq, (err) ->
                callback()
            return

        [side, sideName, rev] = @selectSide doc
        done = @applied(change, sideName, callback)

        switch
            when not side
                @pouch.setLocalSeq change.seq, callback
            when doc.docType is 'file'
                @fileChanged doc, side, rev, done
            when doc.docType is 'folder'
                @folderChanged doc, side, rev, done
            else
                callback new Error "Unknown doctype: #{doc.docType}"

    # Select which side will apply the change
    # It returns the side, its name, and also the last rev applied by this side
    selectSide: (doc) =>
        localRev  = doc.sides.local  or 0
        remoteRev = doc.sides.remote or 0
        if localRev > remoteRev
            return [@remote, 'remote', remoteRev]
        else if remoteRev > localRev
            return [@local, 'local', localRev]
        else
            log.info 'Nothing to do'
            return []

    # Keep track of the sequence number, save side rev, and log errors
    applied: (change, side, callback) =>
        (err) =>
            if err?.code is 'ENOSPC'
                log.error err
                callback new Error 'The disk space on your computer is full!'
            else if err
                log.error err
                change.doc.errors or= 0
                @isCozyFull (err, full) =>
                    if err
                        @remote.couch.ping (available) =>
                            if available
                                @updateErrors change, callback
                            else
                                @remote.couch.whenAvailable callback
                    else if full
                        callback new Error 'Your Cozy is full! ' +
                            'You can delete some files to be able' +
                            'to add new ones or upgrade your storage plan.'
                    else
                        @updateErrors change, callback
            else
                log.info "Applied #{change.seq}"
                @pouch.setLocalSeq change.seq, (err) =>
                    log.error err if err
                    if change.doc._deleted
                        callback err
                    else
                        @updateRevs change.doc, side, callback

    # Says is the Cozy has no more free disk space
    isCozyFull: (callback) -><commit_msg>Detect when cozy-desktop is unlinked while running<commit_after>async  = require 'async'
device = require('cozy-device-sdk').device
log    = require('printit')
    prefix: 'Synchronize   '
    date: true


# Sync listens to PouchDB about the metadata changes, and calls local and
# remote sides to apply the changes on the filesystem and remote CouchDB
# respectively.
class Sync

    constructor: (@pouch, @local, @remote, @ignore, @events) ->
        @local.other = @remote
        @remote.other = @local

    # Start to synchronize the remote cozy with the local filesystem
    # First, start metadata synchronization in pouch, with the watchers
    # Then, when a stable state is reached, start applying changes from pouch
    #
    # The mode can be:
    # - pull if only changes from the remote cozy are applied to the fs
    # - push if only changes from the fs are applied to the remote cozy
    # - full for the full synchronization of the both sides
    #
    # The callback is called only for an error
    start: (mode, callback) =>
        @stopped = false
        tasks = [
            (next) => @pouch.addAllViews next
        ]
        tasks.push @local.start  unless mode is 'pull'
        tasks.push @remote.start unless mode is 'push'
        async.waterfall tasks, (err) =>
            if err
                callback err
            else
                async.forever @sync, callback

    # Stop the synchronization
    stop: (callback) =>
        @stopped = true
        if @changes
            @changes.cancel()
            @changes = null
        async.parallel [
            (done) => @local.stop done
            (done) => @remote.stop done
        ], callback

    # Start taking changes from pouch and applying them
    sync: (callback) =>
        @pop (err, change) =>
            return if @stopped
            if err
                log.error err
                callback err
            else
                @apply change, (err) ->
                    err = null if @stopped
                    callback err

    # Take the next change from pouch
    # We filter with the byPath view to reject design documents
    #
    # Note: it is difficult to pick only one change at a time because pouch can
    # emit several docs in a row, and `limit: 1` seems to be not effective!
    pop: (callback) =>
        @pouch.getLocalSeq (err, seq) =>
            return callback err if err
            opts =
                limit: 1
                since: seq
                include_docs: true
                filter: '_view'
                view: 'byPath'
            @pouch.db.changes(opts)
                .on 'change', (info) ->
                    callback null, info
                .on 'error', (err) ->
                    callback err
                .on 'complete', (info) =>
                    return if info.results?.length
                    @events.emit 'up-to-date'
                    log.info 'Your cozy is up to date!'
                    opts.live = true
                    opts.returnDocs = false
                    @changes = @pouch.db.changes(opts)
                        .on 'change', (info) =>
                            if @changes
                                @changes.cancel()
                                @changes = null
                                callback null, info
                        .on 'error', (err) =>
                            if @changes
                                @changes = null
                                callback err, null

    # Apply a change to both local and remote
    # At least one side should say it has already this change
    # In some cases, both sides have the change
    apply: (change, callback) =>
        log.info 'apply', change
        doc = change.doc

        if @ignore.isIgnored doc
            @pouch.setLocalSeq change.seq, (err) ->
                callback()
            return

        [side, sideName, rev] = @selectSide doc
        done = @applied(change, sideName, callback)

        switch
            when not side
                @pouch.setLocalSeq change.seq, callback
            when doc.docType is 'file'
                @fileChanged doc, side, rev, done
            when doc.docType is 'folder'
                @folderChanged doc, side, rev, done
            else
                callback new Error "Unknown doctype: #{doc.docType}"

    # Select which side will apply the change
    # It returns the side, its name, and also the last rev applied by this side
    selectSide: (doc) =>
        localRev  = doc.sides.local  or 0
        remoteRev = doc.sides.remote or 0
        if localRev > remoteRev
            return [@remote, 'remote', remoteRev]
        else if remoteRev > localRev
            return [@local, 'local', localRev]
        else
            log.info 'Nothing to do'
            return []

    # Keep track of the sequence number, save side rev, and log errors
    applied: (change, side, callback) =>
        (err) =>
            log.error err if err
            if err?.code is 'ENOSPC'
                callback new Error 'The disk space on your computer is full!'
            else if err?.status is 401
                callback new Error 'The device is no longer registered'
            else if err
                change.doc.errors or= 0
                @isCozyFull (err, full) =>
                    if err
                        @remote.couch.ping (available) =>
                            if available
                                @updateErrors change, callback
                            else
                                @remote.couch.whenAvailable callback
                    else if full
                        callback new Error 'Your Cozy is full! ' +
                            'You can delete some files to be able' +
                            'to add new ones or upgrade your storage plan.'
                    else
                        @updateErrors change, callback
            else
                log.info "Applied #{change.seq}"
                @pouch.setLocalSeq change.seq, (err) =>
                    log.error err if err
                    if change.doc._deleted
                        callback err
                    else
                        @updateRevs change.doc, side, callback

    # Says is the Cozy has no more free disk space
    isCozyFull: (callback) ->
{'fraction_non_alphanumeric': 0.0728398000476077, 'fraction_numerical': 0.0010315004364040308, 'mean_word_length': 1.3075796411570852, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6047454', 'n_tokens_mistral': 3151, 'n_tokens_neox': 2959, 'n_words': 1194}