public List<DeviceNotification> GetByDevices(int[] deviceIds, DeviceNotificationFilter filter = null)
        {
            List<DeviceNotification> notifications = null;

            if (filter != null && filter.GridInterval != null)
            {
                // use native MongoDB query for aggregation
                notifications = QueryWithGridInterval(deviceIds, filter);
            }
            else
            {
                var query = _mongo.DeviceNotifications.AsQueryable();
                if (deviceIds != null)
                    query = query.Where(e => deviceIds.Contains(e.DeviceID));
                notifications = query.Filter(filter).ToList();
            }

            var actualDeviceIds = notifications.Select(e => e.DeviceID).Distinct().ToArray();
            var deviceLookup = _mongo.Devices.Find(Query<Device>.In(e => e.ID, actualDeviceIds)).ToDictionary(e => e.ID);

            foreach (var notification in notifications)
                notification.Device = deviceLookup[notification.DeviceID];

            return notifications;
        }
        public async Task<JArray> Get(string deviceGuid, DateTime? timestamp = null, string names = null, int? waitTimeout = null)
        {
            var device = GetDeviceEnsureAccess(deviceGuid);

            var start = timestamp ?? _timestampRepository.GetCurrentTimestamp();
            var notificationNames = names != null ? names.Split(',') : null;
            if (waitTimeout <= 0)
            {
                var filter = new DeviceNotificationFilter { Start = start, IsDateInclusive = false, Notifications = notificationNames };
                var notifications = DataContext.DeviceNotification.GetByDevice(device.ID, filter);
                return new JArray(notifications.Select(n => MapDeviceNotification(n, device)));
            }

            var config = DeviceHiveConfiguration.RestEndpoint;
            var delayTask = Task.Delay(1000 * Math.Min(config.NotificationPollMaxInterval, waitTimeout ?? config.NotificationPollDefaultInterval));
            using (var waiterHandle = _notificationByDeviceIdWaiter.BeginWait(new object[] { device.ID },
                notificationNames == null ? null : notificationNames.Cast<object>().ToArray()))
            {
                do
                {
                    var filter = new DeviceNotificationFilter { Start = start, IsDateInclusive = false, Notifications = notificationNames };
                    var notifications = DataContext.DeviceNotification.GetByDevice(device.ID, filter);
                    if (notifications != null && notifications.Any())
                        return new JArray(notifications.Select(n => MapDeviceNotification(n, device)));
                }
                while (await Task.WhenAny(waiterHandle.Wait(), delayTask) != delayTask);
            }

            return new JArray();
        }
 public List<DeviceNotification> GetByDevice(int deviceId, DeviceNotificationFilter filter = null)
 {
     using (var context = new DeviceHiveContext())
     {
         var query = context.DeviceNotifications.Where(e => e.Device.ID == deviceId);
         return query.Filter(filter, FilterByGridInterval(filter == null ? null : filter.GridInterval)).ToList();
     }
 }
 public List<DeviceNotification> GetByDevices(int[] deviceIds, DeviceNotificationFilter filter = null)
 {
     using (var context = new DeviceHiveContext())
     {
         var query = context.DeviceNotifications.Include(e => e.Device);
         if (deviceIds != null)
             query = query.Where(e => deviceIds.Contains(e.Device.ID));
         return query.Filter(filter, FilterByGridInterval(filter == null ? null : filter.GridInterval)).ToList();
     }
 }
        public List<DeviceNotification> GetByDevice(int deviceId, DeviceNotificationFilter filter = null)
        {
            if (filter != null && filter.GridInterval != null)
            {
                // use native MongoDB query for aggregation
                return QueryWithGridInterval(new[] { deviceId }, filter);
            }

            return _mongo.DeviceNotifications.AsQueryable().Where(e => e.DeviceID == deviceId).Filter(filter).ToList();
        }
        public void SubsrcibeToDeviceNotifications(DateTime? timestamp, string[] deviceGuids = null, string[] names = null)
        {
            var subscriptionId = Guid.NewGuid();
            var devices = GetDevices(deviceGuids, "GetDeviceNotification");
            var deviceIds = devices == null ? null : devices.Select(d => d.ID).ToArray();

            var initialNotificationList = GetInitialNotificationList(Connection, subscriptionId);
            lock (initialNotificationList)
            {
                _deviceSubscriptionManagerForNotifications.Subscribe(subscriptionId, Connection, deviceIds, names);
                SendResponse(new JProperty("subscriptionId", subscriptionId));

                if (timestamp != null)
                {
                    var filter = new DeviceNotificationFilter { Start = timestamp, IsDateInclusive = false, Notifications = names };
                    var initialNotifications = DataContext.DeviceNotification.GetByDevices(deviceIds, filter)
                        .Where(n => IsDeviceAccessible(n.Device, "GetDeviceNotification")).ToArray();

                    foreach (var notification in initialNotifications)
                    {
                        initialNotificationList.Add(notification.ID);
                        Notify(Connection, subscriptionId, notification, notification.Device, isInitialNotification: true);
                    }
                }
            }
        }
        private List<DeviceNotification> QueryWithGridInterval(int[] deviceIds, DeviceNotificationFilter filter)
        {
            if (filter == null)
                throw new ArgumentNullException("filter");
            if (filter.GridInterval == null)
                throw new ArgumentException("GridInterval property of the filter is null!", "filter.GridInterval");

            var periodStart = DateTime.SpecifyKind(new DateTime(2000, 1, 1), DateTimeKind.Utc);
            var periodMilliseconds = periodStart.Ticks / 10000;

            // prepare a list of operations for aggregation query
            var operations = new List<BsonDocument>();

            // match by devices
            if (deviceIds != null)
            {
                operations.Add(new BsonDocument { { "$match", new BsonDocument { { "DeviceID",
                    new BsonDocument { { "$in", new BsonArray(deviceIds) } } } } } });
            }

            // match by filter criteria
            if (filter.Start != null)
            {
                operations.Add(new BsonDocument { { "$match", new BsonDocument { { "Timestamp",
                    new BsonDocument { { filter.IsDateInclusive ? "$gte" : "$gt", filter.Start.Value } } } } } });
            }
            if (filter.End != null)
            {
                operations.Add(new BsonDocument { { "$match", new BsonDocument { { "Timestamp",
                    new BsonDocument { { filter.IsDateInclusive ? "$lte" : "$lt", filter.End.Value } } } } } });
            }
            if (filter.Notification != null)
            {
                operations.Add(new BsonDocument { { "$match", new BsonDocument { { "Notification", filter.Notification } } } });
            }
            if (filter.Notifications != null)
            {
                operations.Add(new BsonDocument { { "$match", new BsonDocument { { "Notification",
                    new BsonDocument { { "$in", new BsonArray(filter.Notifications) } } } } } });
            }

            // process grid interval aggregation
            operations.Add(new BsonDocument { { "$sort", new BsonDocument { { "Timestamp", 1 } } } });
            operations.Add(new BsonDocument { { "$project", new BsonDocument {
                { "DeviceID", 1 }, { "Timestamp", 1 }, { "Notification", 1 }, { "Parameters", 1 },
                { "tsmod", new BsonDocument { { "$mod", new BsonArray {
                    new BsonDocument { { "$add", new BsonArray {
                        new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", periodStart } } }, periodMilliseconds } } },
                    new BsonInt64(1000 * filter.GridInterval.Value) } } }
                } } } });
            operations.Add(new BsonDocument { { "$project", new BsonDocument {
                { "DeviceID", 1 }, { "Timestamp", 1 }, { "Notification", 1 }, { "Parameters", 1 },
                { "tsinterval", new BsonDocument { { "$subtract", new BsonArray {
                    new BsonDocument { { "$add", new BsonArray {
                        new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", periodStart } } }, periodMilliseconds } } },
                    new BsonString("$tsmod") } } }
                } } } });
            operations.Add(new BsonDocument { { "$group", new BsonDocument {
                { "_id", new BsonDocument { { "tsinterval", "$tsinterval" }, { "DeviceID", "$DeviceID" }, { "Notification", "$Notification" } } },
                { "ID", new BsonDocument { { "$first", "$_id" } } },
                { "Timestamp", new BsonDocument { { "$first", "$Timestamp" } } },
                { "DeviceID", new BsonDocument { { "$first", "$DeviceID" } } },
                { "Notification", new BsonDocument { { "$first", "$Notification" } } },
                { "Parameters", new BsonDocument { { "$first", "$Parameters" } } } } } });
            operations.Add(new BsonDocument { { "$project", new BsonDocument {
                { "_id", "$ID" }, { "DeviceID", 1 }, { "Timestamp", 1 }, { "Notification", 1 }, { "Parameters", 1 } } } });

            // apply sorting and pagination
            if (filter.SortField != DeviceNotificationSortField.None)
            {
                var sortOrder = filter.SortOrder == SortOrder.ASC ? 1 : -1;
                switch (filter.SortField)
                {
                    case DeviceNotificationSortField.Timestamp:
                        operations.Add(new BsonDocument { { "$sort", new BsonDocument { { "Timestamp", sortOrder } } } });
                        break;
                    case DeviceNotificationSortField.Notification:
                        operations.Add(new BsonDocument { { "$sort", new BsonDocument { { "Notification", sortOrder }, { "Timestamp", sortOrder } } } });
                        break;
                }
            }
            if (filter.Skip != null)
            {
                operations.Add(new BsonDocument { { "$skip", filter.Skip.Value } });
            }
            if (filter.Take != null)
            {
                operations.Add(new BsonDocument { { "$limit", filter.Take.Value } });
            }

            // run the aggregation query
            var result = _mongo.DeviceNotifications.Aggregate(operations);
            return result.ResultDocuments.Select(BsonSerializer.Deserialize<DeviceNotification>).ToList();
        }