/// <summary>
        /// Inner worker synchronizer.
        /// Will never throw for normal sync operations, even in case of failure.
        /// </summary>
        /// <exception cref="TaskCanceledException">Thrown when sync canceled.</exception>
        private async Task <SyncResult> SynchronizeInner(CancellationToken token, SyncPolicy policy)
        {
            token.ThrowIfCancellationRequested();

            IList <DatabaseQueries.TrackAndCount> tracks = await Task.Run(() => {
                using (var db = DatabaseUtility.OpenConnection()) {
                    return((from t in db.GetAllPendingTracks()
                            let trackFilepath = FileNaming.GetDataTrackFilepath(t.TrackId)
                                                where File.Exists(trackFilepath)
                                                select t).ToList());
                }
            });

            if (tracks.Count == 0)
            {
                Log.Debug("No files to synchronize");
                return(new SyncResult(0, 0));
            }

            token.ThrowIfCancellationRequested();

            Log.Debug("{0} tracks queued to synchronize", tracks.Count);
            Log.Event("Sync.start", new Dictionary <string, string>()
            {
                { "policy", policy.ToString() }
            });

            if (policy == SyncPolicy.ForceLast && tracks.Count > 1)
            {
                Log.Debug("Constraining upload to most recent file as per policy");
                tracks = new DatabaseQueries.TrackAndCount[] { tracks.First() };
            }

            int countUploadedPoints = 0;
            int countUploadedChunks = 0;

            foreach (var track in tracks)
            {
                token.ThrowIfCancellationRequested();

                int pendingPoints = track.DataCount - track.UploadedCount;
                Log.Debug("Uploading {0}/{1} points from track {2}", pendingPoints, track.DataCount, track.TrackId);

                try {
                    var reader = new DataReader(track.TrackId);
                    if (!await reader.Skip(track.UploadedCount))
                    {
                        Log.Error(null, "Cannot advance {0} rows in file for track {1}", track.UploadedCount, track.TrackId);
                        continue;
                    }

                    int currentChunk = 0;
                    while (pendingPoints > 0)
                    {
                        int chunkPoints = Math.Min(ChunkSize, pendingPoints);
                        Log.Debug("Processing chunk {0} with {1} points", currentChunk + 1, chunkPoints);

                        var package = new List <DataPiece>(chunkPoints);
                        for (int p = 0; p < chunkPoints; ++p)
                        {
                            if (!await reader.Advance())
                            {
                                throw new Exception(string.Format("Cannot read line for {0}th point", p + 1));
                            }
                            package.Add(new DataPiece {
                                TrackId        = track.TrackId,
                                StartTimestamp = new DateTime(reader.Current.StartTicks),
                                EndTimestamp   = new DateTime(reader.Current.EndTicks),
                                Ppe            = reader.Current.Ppe,
                                PpeX           = reader.Current.PpeX,
                                PpeY           = reader.Current.PpeY,
                                PpeZ           = reader.Current.PpeZ,
                                Latitude       = reader.Current.Latitude,
                                Longitude      = reader.Current.Longitude,
                                Bearing        = reader.Current.Bearing,
                                Accuracy       = reader.Current.Accuracy,
                                Vehicle        = track.VehicleType,
                                Anchorage      = track.AnchorageType,
                                NumberOfPeople = track.NumberOfPeople
                            });
                        }

                        var secret     = Crypto.GenerateSecret();
                        var secretHash = secret.ToSha512Hash();

                        var uploadQuery = new UploadDataQuery {
                            Package    = package,
                            SecretHash = secretHash
                        };
                        var response = await uploadQuery.Execute(token);

                        Log.Debug("Points uploaded successfully, chunk {0} for track ID {1}", currentChunk + 1, track.TrackId);

                        //Store record of uploaded chunk
                        using (var db = DatabaseUtility.OpenConnection()) {
                            var record = new TrackUploadRecord {
                                TrackId    = track.TrackId,
                                UploadedId = response.UploadedTrackId,
                                Secret     = secret,
                                UploadedOn = DateTime.UtcNow,
                                Count      = chunkPoints
                            };

                            db.Insert(record);
                        }

                        pendingPoints -= chunkPoints;
                        currentChunk++;

                        countUploadedPoints += chunkPoints;
                        countUploadedChunks++;
                    }
                }
                catch (IOException exIo) {
                    Log.Error(exIo, "File for track {0} not found", track.TrackId);
                }
                catch (Exception ex) {
                    Log.Error(ex, "Failed while processing track {0}", track.TrackId);
                }
            }

            return(new SyncResult(countUploadedPoints, countUploadedChunks));
        }
        /// <summary>
        /// Runs the synchronization attempt.
        /// Does not raise exceptions.
        /// </summary>
        public async Task <SyncResult> Synchronize(CancellationToken token, SyncPolicy policy = SyncPolicy.Default)
        {
            if (DateTime.UtcNow < NextUploadOpportunity && policy == SyncPolicy.Default)
            {
                Log.Debug("Can't sync: sync attempt too early, next upload scheduled after {0}", NextUploadOpportunity);

                return(new SyncResult(new InvalidOperationException("Sync attempt too early")));
            }

            Settings.LastUploadAttempt = DateTime.UtcNow;

            if (!CheckSyncConditions(policy))
            {
                return(new SyncResult(error: new InvalidOperationException("Sync conditions not met")));
            }

            Log.Debug("Sync attempt started (policy {0})", policy);

            IsSyncing = true;
            StatusChanged.Raise(this);

            try {
                var ret = await SynchronizeInner(token, policy);

                Log.Debug("Sync process terminated normally");
                Log.Event("Sync.terminate", new Dictionary <string, string>()
                {
                    { "policy", policy.ToString() }
                });

                if (ret.HasFailed)
                {
                    UserLog.Add(UserLog.Icon.Error, LogStrings.FileUploadFailure, ret.Error.Message);

                    SyncError.Raise(this, new SyncErrorEventArgs(ret.Error));
                }
                else
                {
                    if (ret.ChunksUploaded == 1)
                    {
                        UserLog.Add(LogStrings.FileUploadSummarySingular);
                    }
                    else
                    {
                        UserLog.Add(LogStrings.FileUploadSummaryPlural, ret.ChunksUploaded);
                    }
                }

                return(ret);
            }
            catch (TaskCanceledException) {
                Log.Debug("Sync process was canceled");
                return(new SyncResult(0, 0));
            }
            catch (Exception ex) {
                Log.Error(ex, "Sync process failed with unforeseen error");

                UserLog.Add(UserLog.Icon.Error, LogStrings.FileUploadFailure, ex.Message);

                return(new SyncResult(error: ex));
            }
            finally {
                IsSyncing = false;
                StatusChanged.Raise(this);
            }
        }