Exemplo n.º 1
0
        /// <summary>
        ///     Enqueues an upload.
        /// </summary>
        /// <param name="username">The username of the remote user.</param>
        /// <param name="filename">The filename to enqueue.</param>
        public void Enqueue(string username, string filename)
        {
            SyncRoot.Wait();

            try
            {
                var upload = new Upload()
                {
                    Username = username, Filename = filename
                };

                Uploads.AddOrUpdate(
                    key: username,
                    addValue: new List <Upload>(new[] { upload }),
                    updateValueFactory: (key, list) =>
                {
                    list.Add(upload);
                    return(list);
                });

                Log.Debug("Enqueued: {File} for {User} at {Time}", Path.GetFileName(upload.Filename), upload.Username, upload.Enqueued);
            }
            finally
            {
                SyncRoot.Release();
                Process();
            }
        }
Exemplo n.º 2
0
        /// <summary>
        ///     Awaits the start of an upload.
        /// </summary>
        /// <param name="username">The username of the remote user.</param>
        /// <param name="filename">The filename for which to await the start.</param>
        /// <returns>The operation context.</returns>
        public Task AwaitStartAsync(string username, string filename)
        {
            SyncRoot.Wait();

            try
            {
                if (!Uploads.TryGetValue(username, out var list))
                {
                    throw new SlskdException($"No enqueued uploads for user {username}");
                }

                var upload = list.FirstOrDefault(e => e.Filename == filename);

                if (upload == default)
                {
                    throw new SlskdException($"File {filename} is not enqueued for user {username}");
                }

                upload.Ready = DateTime.UtcNow;
                Log.Debug("Ready: {File} for {User} at {Time}", Path.GetFileName(upload.Filename), upload.Username, upload.Enqueued);

                return(upload.TaskCompletionSource.Task);
            }
            finally
            {
                SyncRoot.Release();
                Process();
            }
        }
Exemplo n.º 3
0
        /// <summary>
        ///     Signals the completion of an upload.
        /// </summary>
        /// <param name="username">The username of the remote user.</param>
        /// <param name="filename">The completed filename.</param>
        public void Complete(string username, string filename)
        {
            SyncRoot.Wait();

            try
            {
                if (!Uploads.TryGetValue(username, out var list))
                {
                    throw new SlskdException($"No enqueued uploads for user {username}");
                }

                var upload = list.FirstOrDefault(e => e.Filename == filename);

                if (upload == default)
                {
                    throw new SlskdException($"File {filename} is not enqueued for user {username}");
                }

                list.Remove(upload);
                Log.Debug("Complete: {File} for {User} at {Time}", Path.GetFileName(upload.Filename), upload.Username, upload.Enqueued);

                // ensure the slot is returned to the group from which it was acquired the group may have been removed during the
                // transfer. if so, do nothing.
                if (Groups.ContainsKey(upload.Group ?? string.Empty))
                {
                    var group = Groups[upload.Group];

                    group.UsedSlots = Math.Max(0, group.UsedSlots - 1);
                    Log.Debug("Group {Group} slots: {Used}/{Available}", group.Name, group.UsedSlots, group.Slots);
                }

                if (!list.Any() && Uploads.TryRemove(username, out _))
                {
                    Log.Debug("Cleaned up tracking list for {User}; no more queued uploads to track", username);
                }
            }
            finally
            {
                SyncRoot.Release();
                Process();
            }
        }
Exemplo n.º 4
0
        private Upload Process()
        {
            SyncRoot.Wait();

            try
            {
                if (Groups.Values.Sum(g => g.UsedSlots) >= MaxSlots)
                {
                    return(null);
                }

                // flip the uploads dictionary so that it is keyed by group instead of user. wait until just before we process the
                // queue to do this, and fetch each user's group as we do, to allow users to move between groups at run time. we
                // delay "pinning" an upload to a group (via UsedSlots, below) for the same reason.
                var readyUploadsByGroup = Uploads.Aggregate(
                    seed: new ConcurrentDictionary <string, List <Upload> >(),
                    func: (groups, user) =>
                {
                    var ready = user.Value.Where(u => u.Ready.HasValue && !u.Started.HasValue);

                    if (ready.Any())
                    {
                        var group = Users.GetGroup(user.Key);

                        groups.AddOrUpdate(
                            key: group,
                            addValue: new List <Upload>(ready),
                            updateValueFactory: (group, list) =>
                        {
                            list.AddRange(ready);
                            return(list);
                        });
                    }

                    return(groups);
                });

                // process each group in ascending order of priority, and stop after the first ready upload is released.
                foreach (var group in Groups.Values.OrderBy(g => g.Priority).ThenBy(g => g.Name))
                {
                    if (group.UsedSlots >= group.Slots || !readyUploadsByGroup.TryGetValue(group.Name, out var uploads) || !uploads.Any())
                    {
                        continue;
                    }

                    var upload = uploads
                                 .OrderBy(u => group.Strategy == QueueStrategy.FirstInFirstOut ? u.Enqueued : u.Ready)
                                 .First();

                    // mark the upload as started, and "pin" it to the group from which the slot is obtained, so the slot can be
                    // returned to the proper place upon completion
                    upload.Started = DateTime.UtcNow;
                    upload.Group   = group.Name;
                    group.UsedSlots++;

                    // release the upload
                    upload.TaskCompletionSource.SetResult();
                    Log.Debug("Started: {File} for {User} at {Time}", Path.GetFileName(upload.Filename), upload.Username, upload.Enqueued);
                    Log.Debug("Group {Group} slots: {Used}/{Available}", group.Name, group.UsedSlots, group.Slots);

                    return(upload);
                }

                return(null);
            }
            finally
            {
                SyncRoot.Release();
            }
        }
Exemplo n.º 5
0
        private void Configure(Options options)
        {
            int GetExistingUsedSlotsOrDefault(string group)
            => Groups.ContainsKey(group) ? Groups[group].UsedSlots : 0;

            SyncRoot.Wait();

            try
            {
                var optionsHash = Compute.Sha1Hash(options.Groups.ToJson());

                if (optionsHash == LastOptionsHash && options.Global.Upload.Slots == LastGlobalSlots)
                {
                    return;
                }

                MaxSlots = options.Global.Upload.Slots;

                // statically add built-in groups
                var groups = new List <UploadGroup>()
                {
                    // the priority group is hard-coded with priority 0, slot count equivalent to the overall max, and a FIFO
                    // strategy. all other groups have a minimum priority of 1 (enforced by options validation) to ensure that
                    // privileged users always take priority, regardless of user configuration. the strategy is fixed to FIFO
                    // because that gives privileged users the closest experience to the official client, as well as the
                    // appearance of fairness once the first upload begins.
                    new UploadGroup()
                    {
                        Name      = Application.PrivilegedGroup,
                        Priority  = 0,
                        Slots     = MaxSlots,
                        UsedSlots = GetExistingUsedSlotsOrDefault(Application.PrivilegedGroup),
                        Strategy  = QueueStrategy.FirstInFirstOut,
                    },
                    new UploadGroup()
                    {
                        Name      = Application.DefaultGroup,
                        Priority  = options.Groups.Default.Upload.Priority,
                        Slots     = options.Groups.Default.Upload.Slots,
                        UsedSlots = GetExistingUsedSlotsOrDefault(Application.DefaultGroup),
                        Strategy  = (QueueStrategy)Enum.Parse(typeof(QueueStrategy), options.Groups.Default.Upload.Strategy, true),
                    },
                    new UploadGroup()
                    {
                        Name      = Application.LeecherGroup,
                        Priority  = options.Groups.Leechers.Upload.Priority,
                        Slots     = options.Groups.Leechers.Upload.Slots,
                        UsedSlots = GetExistingUsedSlotsOrDefault(Application.LeecherGroup),
                        Strategy  = (QueueStrategy)Enum.Parse(typeof(QueueStrategy), options.Groups.Leechers.Upload.Strategy, true),
                    },
                };

                // dynamically add user-defined groups
                groups.AddRange(options.Groups.UserDefined.Select(kvp => new UploadGroup()
                {
                    Name      = kvp.Key,
                    Priority  = kvp.Value.Upload.Priority,
                    Slots     = kvp.Value.Upload.Slots,
                    UsedSlots = GetExistingUsedSlotsOrDefault(kvp.Key),
                    Strategy  = (QueueStrategy)Enum.Parse(typeof(QueueStrategy), kvp.Value.Upload.Strategy, true),
                }));

                Groups = groups.ToDictionary(g => g.Name);

                LastGlobalSlots = options.Global.Upload.Slots;
                LastOptionsHash = optionsHash;
            }
            finally
            {
                SyncRoot.Release();
                Process();
            }
        }