Compare commits

...

39 commits

Author SHA1 Message Date
Serraniel 20f58e16c3
#27 Added handler for received messages 2019-02-25 18:51:46 +01:00
Serraniel d3aa91f826
#27 Starting job scans in order 2019-02-25 18:42:28 +01:00
Serraniel c5ab4a73a6
#27 Fixed an error in migration to correct number format for LiteDB 2019-02-16 20:58:56 +01:00
Serraniel 8923659610
#27 Changed scanning code so it *should* work with Last;essageId instead of timestamps 2019-02-16 20:23:00 +01:00
Serraniel dfcc430ab1
#27 Changed properties in the jobs and fixed old rescan code. Also set rescan to false (we may ask the user later). 2019-02-16 20:08:17 +01:00
Serraniel 2f68a3d7a9
#27 Fix for 1 day offset and removing old column 2019-02-16 20:01:44 +01:00
Serraniel f4e4b9c02d
#27 Fix for repeating migrations over and over... 2019-02-16 19:45:06 +01:00
Serraniel 7a90260207
#27 Prepared a migration from timestmaps to message ids in the jobs and relocated the Core.cs classfile. 2019-02-15 20:08:11 +01:00
Serraniel 4fdb226a21
#27 Optimized project structure 2019-02-15 17:52:26 +01:00
Serraniel 2ebae762f0
#27 Fixed paths 2019-02-15 17:50:48 +01:00
Serraniel 321c116f34
#27 generated license headers 2019-02-15 17:49:46 +01:00
Serraniel fc359fe226
#27 Added licenseheader configuration file 2019-02-15 17:49:20 +01:00
Serraniel c1c0c372c7
Fix after merge 2019-02-15 17:14:41 +01:00
Serraniel f29e9e2b8a
Merge branch 'develop' into feature/DatabaseJobs
# Conflicts:
#	Discord Media Loader.Application/Classes/Core.cs
#	Discord Media Loader.Application/Classes/JobScheduler.cs
#	Discord Media Loader.Application/Classes/Settings.cs
2019-02-15 17:13:43 +01:00
Serraniel 0772edd354
Debug database in debug configuration 2019-02-15 17:11:00 +01:00
Serraniel 9c141e9f5c
Merge branch 'master' into develop
# Conflicts:
#	Discord Media Loader.Application/Classes/Core.cs
#	Discord Media Loader.Application/Classes/Job.cs
#	Discord Media Loader.Application/Classes/JobScheduler.cs
#	Discord Media Loader.Application/Classes/Settings.cs
#	Discord Media Loader.Application/MainForm.cs
2019-02-15 16:58:01 +01:00
Serraniel 3eb8750df7 Some fixes so it does compile again 2018-06-25 21:14:10 +02:00
Serraniel b52b6b4189 some preparations for some improved logic 2018-05-02 19:57:10 +02:00
Serraniel ec4ee45182 Fixed empty lines 2018-04-23 21:59:45 +02:00
Serraniel 5cd9a0c200 Merge branch 'develop' into feature/DatabaseJobs
# Conflicts:
#	Discord Media Loader.Application/Classes/Core.cs
#	Discord Media Loader.Application/Classes/Job.cs
#	Discord Media Loader.Application/Classes/JobScheduler.cs
#	Discord Media Loader.Application/Classes/Settings.cs
#	Discord Media Loader.Application/DML.Application.csproj
2018-04-23 21:59:29 +02:00
Serraniel 3b8f440bb2 Merge branch 'feature/RPC-config' into develop
* feature/RPC-config:
  Disable RPC by default
  Did some corrections because of sweetlib
2018-04-23 21:41:45 +02:00
Serraniel 7358be85e3 Disable RPC by default 2018-04-23 21:40:25 +02:00
Serraniel 8ca2ec9b27 Did some corrections because of sweetlib 2018-04-23 21:40:17 +02:00
Serraniel c36acc0750 Merge branch 'feature/RPC' into develop
* feature/RPC:
  Revert "Added discord rpc and demo repository"
  Implemented basic rpc setup
  Implemented rpc handler and added rpc library
  Added helper file
  Added basic RPC wrapper into project
  Disabled update check on debug versions
  Added discord rpc and demo repository
2018-04-01 20:23:48 +02:00
Serraniel 842266f338 Revert "Added discord rpc and demo repository"
This reverts commit 9278a804cf.
2018-04-01 20:20:35 +02:00
Serraniel 6a6a2e7c9a Implemented basic rpc setup 2018-04-01 20:17:31 +02:00
Serraniel 051b0713ee Implemented rpc handler and added rpc library 2018-04-01 20:16:58 +02:00
Serraniel 2add4afa24 Added helper file 2018-04-01 20:16:17 +02:00
Serraniel 8aa1296887 Added basic RPC wrapper into project 2018-04-01 18:48:25 +02:00
Serraniel fc27243c1e Disabled update check on debug versions 2018-04-01 18:48:06 +02:00
Serraniel 9278a804cf Added discord rpc and demo repository 2018-04-01 18:28:39 +02:00
Serraniel 27ed4a00ba Merge branch 'master' into develop
* master:
  Removed unneccasry parts of code
  Build
  Added an option to set online state. Refresh timer added as it was required to display correct information in UI
2018-04-01 18:26:01 +02:00
Serraniel d7bbbeade9 Little fixes and debug database 2018-02-05 20:33:40 +01:00
Serraniel a867b35cad Reduced uses 2018-02-01 21:36:38 +01:00
Serraniel c2cfe54bee Added download method to the media data instances... 2018-02-01 21:36:16 +01:00
Serraniel fb364f154d Restructed code to make the jobs save the media data and don´t run in a queue any more 2018-02-01 21:17:12 +01:00
Serraniel b022306cfd Implemented the RescanRequired 2018-02-01 21:16:23 +01:00
Serraniel 4d6cc85732 Added media data class 2018-02-01 20:47:04 +01:00
Serraniel 698729cec7 Merge branch 'support/1.0' into develop
* support/1.0:
  Build
2018-01-22 20:38:45 +01:00
61 changed files with 1151 additions and 530 deletions

View file

@ -1,305 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using DML.Application.Classes;
using SweetLib.Utils;
using SweetLib.Utils.Extensions;
using SweetLib.Utils.Logger;
namespace DML.AppCore.Classes
{
public class JobScheduler
{
private ulong messageScanned = 0;
private ulong totalAttachments = 0;
private ulong attachmentsDownloaded = 0;
private bool Run { get; set; } = false;
public List<Job> JobList { get; set; } = new List<Job>();
public Dictionary<int, Queue<IMessage>> RunningJobs = new Dictionary<int, Queue<IMessage>>();
internal int RunningThreads { get; set; } = 0;
internal ulong MessagesScanned
{
get
{
lock (this)
{
return messageScanned;
}
}
set
{
lock (this)
{
messageScanned = value;
}
}
}
internal ulong TotalAttachments
{
get
{
lock (this)
{
return totalAttachments;
}
}
set
{
lock (this)
{
totalAttachments = value;
}
}
}
internal ulong AttachmentsDownloaded
{
get
{
lock (this)
{
return attachmentsDownloaded;
}
}
set
{
lock (this)
{
attachmentsDownloaded = value;
}
}
}
public void Stop()
{
Run = false;
}
public void Start()
{
Run = true;
Task.Run(async () =>
{
Logger.Info("Started JobScheduler...");
while (Run)
{
try
{
Logger.Debug("Entering job list handler loop...");
//foreach (var job in JobList)
for (var i = JobList.Count - 1; i >= 0; i--)
{
var job = JobList[i];
Logger.Debug($"Checking job {job}");
var hasJob = false;
Logger.Trace("Locking scheduler...");
lock (this)
{
Logger.Trace("Checking if job is already performed...");
hasJob = RunningJobs.ContainsKey(job.Id);
}
Logger.Trace("Unlocked scheduler.");
if (!hasJob)
{
Logger.Debug("Job is not performed yet...Performing job...");
var queue = new Queue<IMessage>();
Logger.Trace("Locking scheduler...");
lock (this)
{
Logger.Trace("Adding job to running jobs.");
RunningJobs.Add(job.Id, queue);
}
Logger.Trace("Unlocked scheduler.");
Logger.Trace("Issuing job message scan...");
var messages = await job.Scan();
if (messages == null)
continue;
Logger.Trace($"Adding {messages.Count} messages to queue...");
foreach (var msg in messages)
{
queue.Enqueue(msg);
}
Logger.Trace($"Added {queue.Count} messages to queue.");
if (messages.Count != queue.Count)
Logger.Warn("Not all messages have been added into the queue.");
var startedDownload = false;
while (!startedDownload)
{
Logger.Debug("Entering loop to check thread availability");
Logger.Trace("Locking scheduler...");
lock (this)
{
Logger.Trace(
$"Checking thread limit. Running: {RunningThreads}, Max: {Core.Settings.ThreadLimit}");
if (RunningThreads >= Core.Settings.ThreadLimit)
continue;
RunningThreads++;
startedDownload = true;
}
Logger.Trace("Unlocked scheduler.");
}
Logger.Trace("Start downloading job async.");
Task.Run(() => WorkQueue(job.Id)); // do not await to work parallel
}
}
}
catch (Exception ex)
{
Logger.Error(ex.Message);
}
}
});
}
private void WorkQueue(int jobId)
{
try
{
Logger.Debug("Beginning job download...");
Logger.Trace("Finding job...");
var job = (from j in JobList where j.Id == jobId select j).FirstOrDefault();
if (job == null)
{
Logger.Warn($"Associating job not found! JobId: {jobId}");
return;
}
Logger.Trace("Found job.");
Queue<IMessage> queue;
Logger.Trace("Locking scheduler...");
lock (this)
{
Logger.Trace("Finiding queue...");
if (!RunningJobs.TryGetValue(jobId, out queue))
{
Logger.Warn($"Queue for job {jobId} not found!");
return;
}
Logger.Trace("Queue found.");
}
Logger.Trace("Unlocked scheduler.");
Logger.Debug($"Messages to process for job {jobId}: {queue.Count}");
while (queue.Count > 0)
{
Logger.Trace("Locking scheduler...");
lock (this)
{
Logger.Trace("Checking if still a job...");
if (!RunningJobs.ContainsKey(jobId))
{
Logger.Warn($"Queue for job {jobId} not found!");
return;
}
Logger.Trace("Continue working...");
}
Logger.Trace("Unlocked scheduler.");
Logger.Trace("Dequeueing message...");
var message = queue.Dequeue();
Logger.Debug($"Attachments for message {message.Id}: {message.Attachments.Count}");
foreach (var a in message.Attachments)
{
try
{
var fileName = Path.Combine(Core.Settings.OperatingFolder, Core.Settings.FileNameScheme);
Logger.Trace("Replacing filename placeholders...");
var extensionRequired = !fileName.EndsWith("%name%");
var serverName = "unknown";
var socketTextChannel = message.Channel as SocketTextChannel;
if (socketTextChannel != null)
{
serverName = socketTextChannel.Guild.Name;
serverName = Path.GetInvalidFileNameChars().Aggregate(serverName, (current, c) => current.Replace(c, ' '));
}
var channelName = message.Channel.Name;
channelName = Path.GetInvalidFileNameChars().Aggregate(channelName, (current, c) => current.Replace(c, ' '));
fileName =
fileName.Replace("%guild%", serverName)
.Replace("%channel%", channelName)
.Replace("%timestamp%", message.CreatedAt.UtcDateTime.ToUnixTimeStamp().ToString())
.Replace("%name%", a.Filename)
.Replace("%id%", a.Id.ToString());
if (extensionRequired)
fileName += Path.GetExtension(a.Filename);
Logger.Trace($"Detemined file name: {fileName}.");
if (File.Exists(fileName) && new FileInfo(fileName).Length == a.Size)
{
Logger.Debug($"{fileName} already existing with its estimated size. Skipping...");
continue;
}
Logger.Trace("Determining directory...");
var fileDirectory = Path.GetDirectoryName(fileName);
if (!Directory.Exists(fileDirectory))
{
Logger.Info($"Directory {fileDirectory} does not exist. Creating directory...");
Directory.CreateDirectory(fileDirectory);
Logger.Debug("Created directory.");
}
var wc = new WebClient();
Logger.Debug($"Starting downloading of attachment {a.Id}...");
wc.DownloadFile(new Uri(a.Url), fileName);
Logger.Debug($"Downloaded attachment {a.Id}.");
Logger.Trace("Updating known timestamp for job...");
job.KnownTimestamp = message.CreatedAt.UtcDateTime.ToUnixTimeStamp();
job.Store();
}
finally
{
AttachmentsDownloaded++;
}
}
}
}
finally
{
Logger.Trace("Locking scheduler...");
lock (this)
{
Logger.Trace($"Removing {jobId} from running jobs...");
RunningJobs.Remove(jobId);
Logger.Trace("Decreasing thread count...");
RunningThreads--;
}
Logger.Trace("Unlocked scheduler.");
}
}
}
}

View file

@ -1,12 +0,0 @@
using System.Windows.Forms;
namespace DML.Application
{
public partial class FrmInternalSplash : Form
{
public FrmInternalSplash()
{
InitializeComponent();
}
}
}

View file

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DML.Application.Helper
{
internal class IdentifiedString<T>
{
internal T Id { get; set; }
internal string Caption { get; set; }
internal IdentifiedString(T id, string caption)
{
Id = id;
Caption = caption;
}
public override string ToString() => Caption;
}
}

View file

@ -1,32 +0,0 @@
using System;
using System.Windows.Forms;
using DML.Application.Classes;
using Nito.AsyncEx;
namespace Discord_Media_Loader
{
static class Program
{
[STAThread]
static void Main(string[] paramStrings)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var splashScreen = new FrmSplash();
if (splashScreen.ShowDialog() == DialogResult.OK)
{
DoLaunch(paramStrings);
}
else
{
Application.Restart();
}
}
private static void DoLaunch(string[] paramStrings)
{
AsyncContext.Run(() => Core.Run(paramStrings));
}
}
}

View file

@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.42000
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Discord_Media_Loader.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View file

@ -1,4 +1,19 @@
using System.Threading.Tasks; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Client".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Client" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited DMLClient.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;

View file

@ -1,31 +1,53 @@
using System; #region LICENSE
using System.Collections.Generic; /**********************************************************************************************
using System.Linq; * Copyright (C) 2017-2019 - All Rights Reserved
using System.Threading.Tasks; *
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited Job.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using DML.Application.Classes; using DML.Application.Classes;
using DML.Client; using DML.Client;
using SweetLib.Utils;
using SweetLib.Utils.Extensions; using SweetLib.Utils.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static SweetLib.Utils.Logger.Logger; using static SweetLib.Utils.Logger.Logger;
namespace DML.AppCore.Classes namespace DML.AppCore.Classes
{ {
internal enum JobState
{
Idle,
Scanning,
Listening
}
public class Job public class Job
{ {
public int Id { get; set; } public int Id { get; set; }
public ulong GuildId { get; set; } public ulong GuildId { get; set; }
public ulong ChannelId { get; set; } public ulong ChannelId { get; set; }
public double KnownTimestamp { get; set; } = 0; public ulong LastMessageId { get; set; } = 0;
private double StopTimestamp { get; set; } = 0;
private bool IsValid { get; set; } = true; private bool IsValid { get; set; } = true;
internal JobState State { get; set; } = JobState.Idle;
internal void Store() internal void Store()
{ {
Debug("Storing job to database..."); Debug("Storing job to database...");
Trace("Getting jobs collection..."); Trace("Getting jobs collection...");
var jobDb = Core.Database.GetCollection<Job>("jobs"); var jobDb = Application.Core.Core.Database.GetCollection<Job>("jobs");
Trace("Adding new value..."); Trace("Adding new value...");
@ -43,7 +65,7 @@ namespace DML.AppCore.Classes
{ {
Debug("Deleting job from database..."); Debug("Deleting job from database...");
Trace("Getting jobs collection..."); Trace("Getting jobs collection...");
var jobDb = Core.Database.GetCollection<Job>("jobs"); var jobDb = Application.Core.Core.Database.GetCollection<Job>("jobs");
Trace("Deleting value..."); Trace("Deleting value...");
jobDb.Delete(Id); jobDb.Delete(Id);
@ -61,115 +83,96 @@ namespace DML.AppCore.Classes
return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault();
} }
internal async Task<List<IMessage>> Scan() /// <summary>
/// Performs scanning task of the job.
/// </summary>
/// <returns>Returns true if the newest messages have been scanned.</returns>
internal async Task<bool> Scan()
{ {
Debug($"Starting scan of guild {GuildId} channel {ChannelId}..."); Debug($"Starting scan of guild {GuildId} channel {ChannelId}...");
var result = new List<IMessage>(); var result = new List<IMessage>();
const ushort limit = 100;
State = JobState.Scanning;
var limit = 100;
var lastId = ulong.MaxValue;
var isFirst = true;
var finished = false; var finished = false;
var scanStartTimeStamp = DateTime.UtcNow;
var guild = FindServerById(GuildId); var guild = FindServerById(GuildId);
var channel = FindChannelById(guild, ChannelId); var channel = FindChannelById(guild, ChannelId);
Debug("Checking channel access"); Debug("Checking channel access");
//channel.GetUser(channel.Guild.CurrentUser.Id);
if (channel.GetUser(channel.Guild.CurrentUser.Id) == null) if (channel.GetUser(channel.Guild.CurrentUser.Id) == null)
{ {
Info("Skipping channel without access"); Info("Skipping channel without access");
return result; return true;
} }
if (Math.Abs(StopTimestamp) < 0.4)
StopTimestamp = KnownTimestamp;
Trace("Initialized scanning parameters."); Trace("Initialized scanning parameters.");
while (!finished) while (!finished)
{ {
Trace("Entering scanning loop..."); Trace("Entering scanning loop...");
var messages = new List<IMessage>(); var messages = new List<IMessage>();
Trace($"Downloading next {limit} messages..."); Trace($"Downloading next {limit} messages...");
if (isFirst) messages.AddRange((await channel.GetMessagesAsync(LastMessageId, Direction.After, limit).ToArray()).SelectMany(collection => collection));
{
//messages = await channel.GetMessagesAsync(limit).ToArray() as SocketMessage[];
var realMessages = await channel.GetMessagesAsync(limit).ToArray();
foreach (var realMessageArray in realMessages)
{
foreach (var realMessage in realMessageArray)
{
messages.Add(realMessage);
}
}
}
else
{
var realMessages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray();
foreach (var realMessageArray in realMessages)
{
foreach (var realMessage in realMessageArray)
{
messages.Add(realMessage);
}
}
//messages = await channel.GetMessagesAsync(lastId, Direction.Before, limit).ToArray() as SocketMessage[];
}
Trace($"Downloaded {messages.Count} messages.");
isFirst = false;
Debug($"Downloaded {messages.Count} messages.");
Trace("Iterating messages...");
foreach (var m in messages) foreach (var m in messages)
{ {
if (!IsValid) if (!IsValid)
return null;
Core.Scheduler.MessagesScanned++;
Debug($"Processing message {m.Id}");
if (m.Id < lastId)
{ {
Trace($"Updating lastId ({lastId}) to {m.Id}"); return false;
lastId = m.Id;
} }
if (m.CreatedAt.UtcDateTime.ToUnixTimeStamp() <= StopTimestamp) Application.Core.Core.Scheduler.MessagesScanned++;
Debug($"Processing message {m.Id}");
if (m.Id > LastMessageId)
{ {
Debug("Found a message with a known timestamp...Stopping scan."); Trace($"Updating lastId ({LastMessageId}) to {m.Id}");
finished = true; LastMessageId = m.Id;
continue;
} }
Trace($"Message {m.Id} has {m.Attachments.Count} attachments."); Trace($"Message {m.Id} has {m.Attachments.Count} attachments.");
if (m.Attachments.Count > 0) if (m.Attachments.Count > 0)
{ {
result.Add(m); result.Add(m);
Core.Scheduler.TotalAttachments += (ulong)m.Attachments.Count; Application.Core.Core.Scheduler.TotalAttachments += (ulong)m.Attachments.Count;
Trace($"Added message {m.Id}"); Trace($"Added message {m.Id}");
} }
Debug($"Finished message {m.Id}"); Debug($"Finished message {m.Id}");
} }
finished = finished || messages.Count < limit; finished = messages.Count < limit;
} }
Trace($"Downloaded all messages for guild {GuildId} channel {ChannelId}."); Trace($"Downloaded all messages for guild {GuildId} channel {ChannelId}.");
Trace("Sorting messages..."); Trace("Sorting messages...");
result.Sort((a, b) => DateTime.Compare(a.CreatedAt.UtcDateTime, b.CreatedAt.UtcDateTime)); result.Sort((a, b) => DateTime.Compare(a.CreatedAt.UtcDateTime, b.CreatedAt.UtcDateTime));
if (result.Count > 0) foreach (var message in result)
{ {
Trace("Updating StopTimestamp for next scan..."); foreach (var attachment in message.Attachments)
StopTimestamp = result[result.Count - 1].CreatedAt.UtcDateTime.ToUnixTimeStamp(); {
var mediaData = new MediaData
{
Id = attachment.Id,
GuildId = (message.Channel as SocketTextChannel)?.Guild?.Id ?? 0,
ChannelId = message.Channel.Id,
DownloadSource = attachment.Url,
Filename = attachment.Filename,
TimeStamp = message.CreatedAt.UtcDateTime.ToUnixTimeStamp(),
FileSize = attachment.Size
};
mediaData.Store();
}
} }
Debug($"Fisnished scan of guild {GuildId} channel {ChannelId}.");
return result; Debug($"Fisnished scan of guild {GuildId} channel {ChannelId}.");
return true;
} }
public void Stop() public void Stop()
@ -181,7 +184,7 @@ namespace DML.AppCore.Classes
{ {
Debug("Restoring jobs..."); Debug("Restoring jobs...");
Trace("Getting jobs collection..."); Trace("Getting jobs collection...");
var jobDb = Core.Database.GetCollection<Job>("jobs"); var jobDb = Application.Core.Core.Database.GetCollection<Job>("jobs");
Trace("Creating new empty job list"); Trace("Creating new empty job list");
return jobDb.FindAll(); return jobDb.FindAll();

View file

@ -0,0 +1,172 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited JobScheduler.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using Discord;
using Discord.WebSocket;
using DML.AppCore.Classes;
using SweetLib.Utils.Extensions;
using SweetLib.Utils.Logger;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace DML.Application.Classes
{
public class JobScheduler
{
private ulong messageScanned = 0;
private ulong totalAttachments = 0;
private ulong attachmentsDownloaded = 0;
private bool Run { get; set; } = false;
public List<Job> JobList { get; set; } = new List<Job>();
public Dictionary<int, Queue<IMessage>> RunningJobs { get; } = new Dictionary<int, Queue<IMessage>>();
internal int RunningThreads { get; set; } = 0;
internal Task SchedulerTask { get; private set; }
internal Task DownloadTask { get; private set; }
internal ulong MessagesScanned
{
get
{
lock (this)
{
return messageScanned;
}
}
set
{
lock (this)
{
messageScanned = value;
}
}
}
internal ulong TotalAttachments
{
get
{
lock (this)
{
return totalAttachments;
}
}
set
{
lock (this)
{
totalAttachments = value;
}
}
}
internal ulong AttachmentsDownloaded
{
get
{
lock (this)
{
return attachmentsDownloaded;
}
}
set
{
lock (this)
{
attachmentsDownloaded = value;
}
}
}
public void StartScheduler()
{
Logger.Info("Starting scheduler jobs");
SchedulerTask = Task.Run(() =>
{
PerformSchedulerTask();
});
DownloadTask = Task.Run(() =>
{
PerformDownloads();
});
}
public void Stop()
{
Run = false;
}
private async void PerformSchedulerTask()
{
Logger.Trace("SchedulerTask started");
foreach (var job in JobList)
{
if (job.State == JobState.Idle)
{
// scan all old messages first
Logger.Debug($"Starting scan for job {job.Id}");
await job.Scan();
job.State = JobState.Listening; // set to listening now
Logger.Debug($"Scan for job {job.Id} finished");
}
}
Logger.Trace("All jobs have been scanned");
}
private async void PerformDownloads()
{
Logger.Trace("SchedulerTask started");
// TODO
}
public Task HandleMessageReceived(SocketMessage msg)
{
Logger.Trace($"Received message: Id: {msg.Id}, Channel: {msg.Channel.Id}, Attachments: {msg.Attachments.Count}");
if (msg.Attachments.Count > 0)
{
foreach (var job in JobList)
{
if (job.State != JobState.Listening || job.ChannelId != msg.Channel.Id)
{
continue;
}
Logger.Debug($"Job found [{job.Id}]");
foreach (var attachment in msg.Attachments)
{
var mediaData = new MediaData
{
Id = attachment.Id,
GuildId = (msg.Channel as SocketTextChannel)?.Guild?.Id ?? 0,
ChannelId = msg.Channel.Id,
DownloadSource = attachment.Url,
Filename = attachment.Filename,
TimeStamp = msg.CreatedAt.UtcDateTime.ToUnixTimeStamp(),
FileSize = attachment.Size
};
mediaData.Store();
}
job.LastMessageId = msg.Id;
job.Store();
}
}
return Task.CompletedTask;
}
}
}

View file

@ -0,0 +1,112 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited MediaData.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using DML.Client;
using SweetLib.Utils.Logger;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using static SweetLib.Utils.Logger.Logger;
namespace DML.Application.Classes
{
public class MediaData
{
public ulong Id { get; set; }
public string DownloadSource { get; set; }
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public double TimeStamp { get; set; }
public string Filename { get; set; }
public int FileSize { get; set; }
internal void Store()
{
Debug("Storing job to database...");
Trace("Getting jobs collection...");
var db = Core.Core.Database.GetCollection<MediaData>("media");
Trace("Adding new value...");
if (db.Find(x => x.Id == Id).Any())
{
db.Update(this);
}
else
{
db.Insert(this);
}
}
internal void Delete()
{
Debug("Deleting job from database...");
Trace("Getting jobs collection...");
var db = Core.Core.Database.GetCollection<MediaData>("media");
Trace("Deleting value...");
db.Delete(Id);
}
internal async Task Download()
{
Trace("Beginning attachment download...");
Debug("Building filename...");
var fileName = Path.Combine(Core.Core.Settings.OperatingFolder, Core.Core.Settings.FileNameScheme);
Debug($"Base filename: {fileName}");
Trace("Determining if extension is required");
var extensionRequired = !fileName.EndsWith("%name%");
Trace($"Extension required: {extensionRequired}");
Trace("Replacing filename placeholders...");
var guildName = DMLClient.Client.GetGuild(GuildId)?.Name ?? GuildId.ToString();
var channelName = DMLClient.Client.GetGuild(GuildId)?.GetChannel(ChannelId)?.Name ?? ChannelId.ToString();
fileName =
fileName.Replace("%guild%", guildName)
.Replace("%channel%", channelName)
.Replace("%timestamp%", TimeStamp.ToString("####"))
.Replace("%name%", Filename)
.Replace("%id%", Id.ToString());
Trace("Adding extension if required");
if (extensionRequired)
{
fileName += Path.GetExtension(Filename);
}
Debug($"Final filename: {fileName}");
if (File.Exists(fileName) && new FileInfo(fileName).Length == FileSize)
{
Logger.Debug($"{fileName} already existing with its estimated size. Skipping...");
return;
}
var wc = new WebClient();
Logger.Debug($"Starting downloading of attachment {Id}...");
wc.DownloadFile(new Uri(DownloadSource), fileName);
Logger.Debug($"Downloaded attachment {Id}.");
Core.Core.Scheduler.AttachmentsDownloaded++;
}
}
}

View file

@ -0,0 +1,74 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited DiscordRpc.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System.Runtime.InteropServices;
namespace DML.Application.Classes.RPC
{
// https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordRpc.cs
// Give that man a cookie ^.^
public class DiscordRpc
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ReadyCallback();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisconnectedCallback(int errorCode, string message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ErrorCallback(int errorCode, string message);
public struct EventHandlers
{
public ReadyCallback readyCallback;
public DisconnectedCallback disconnectedCallback;
public ErrorCallback errorCallback;
}
// Values explanation and example: https://discordapp.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields
[System.Serializable]
public struct RichPresence
{
public string state; /* max 128 bytes */
public string details; /* max 128 bytes */
public long startTimestamp;
public long endTimestamp;
public string largeImageKey; /* max 32 bytes */
public string largeImageText; /* max 128 bytes */
public string smallImageKey; /* max 32 bytes */
public string smallImageText; /* max 128 bytes */
public string partyId; /* max 128 bytes */
public int partySize;
public int partyMax;
public string matchSecret; /* max 128 bytes */
public string joinSecret; /* max 128 bytes */
public string spectateSecret; /* max 128 bytes */
public bool instance;
}
[DllImport(RpcWrapper.Dll, EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
[DllImport(RpcWrapper.Dll, EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdatePresence(ref RichPresence presence);
[DllImport(RpcWrapper.Dll, EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks();
[DllImport(RpcWrapper.Dll, EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown();
}
}

View file

@ -0,0 +1,31 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited DiscordRpcHelper.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DML.Application.Classes.RPC
{
public static class DiscordRpcHelper
{
public static long DateTimeToTimestamp(DateTime dt)
{
return (dt.Ticks - 621355968000000000) / 10000000;
}
}
}

View file

@ -0,0 +1,44 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited RpcWrapper.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DML.Application.Classes.RPC
{
public class RpcWrapper
{
public const string Dll = "discord-rpc-win32";
public RpcWrapper()
{
if (!File.Exists(Dll + ".dll"))
{
MessageBox.Show(
"Missing " + Dll + ".dll\n\n" +
"Grab it from the release on GitHub or from the DiscordRpcDemo/lib/ folder in the repo then put it alongside DiscordRpcDemo.exe.\n\n" +
"https://github.com/nostrenz/cshap-discord-rpc-demo"
);
System.Windows.Forms.Application.Exit();
}
}
}
}

View file

@ -1,4 +1,19 @@
using SweetLib.Utils.Logger; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited Settings.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using SweetLib.Utils.Logger;
namespace DML.Application.Classes namespace DML.Application.Classes
{ {
@ -16,11 +31,13 @@ namespace DML.Application.Classes
public bool SkipExistingFiles { get; set; } = true; public bool SkipExistingFiles { get; set; } = true;
public int ThreadLimit { get; set; } = 50; public int ThreadLimit { get; set; } = 50;
public bool ShowStartUpHints { get; set; } = true; public bool ShowStartUpHints { get; set; } = true;
public bool RescanRequired { get; set; } = false;
public bool UseRPC { get; set; } = false;
public void Store() public void Store()
{ {
Logger.Trace("Getting settings collection..."); Logger.Trace("Getting settings collection...");
var settingsDB = Core.Database.GetCollection<Settings>("settings"); var settingsDB = Core.Core.Database.GetCollection<Settings>("settings");
Logger.Debug("Storing settings to database..."); Logger.Debug("Storing settings to database...");

View file

@ -1,7 +1,24 @@
using Discord; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited Core.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using Discord;
using Discord.Net; using Discord.Net;
using Discord.WebSocket; using Discord.WebSocket;
using DML.AppCore.Classes; using DML.AppCore.Classes;
using DML.Application.Classes;
using DML.Application.Classes.RPC;
using DML.Application.Dialogs; using DML.Application.Dialogs;
using DML.Client; using DML.Client;
using LiteDB; using LiteDB;
@ -19,7 +36,7 @@ using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Logger = SweetLib.Utils.Logger.Logger; using Logger = SweetLib.Utils.Logger.Logger;
namespace DML.Application.Classes namespace DML.Application.Core
{ {
public static class Core public static class Core
{ {
@ -27,11 +44,14 @@ namespace DML.Application.Classes
internal static LiteDatabase Database { get; set; } internal static LiteDatabase Database { get; set; }
internal static Settings Settings { get; set; } internal static Settings Settings { get; set; }
internal static JobScheduler Scheduler { get; } = new JobScheduler(); internal static JobScheduler Scheduler { get; } = new JobScheduler();
internal static RavenClient Raven = new RavenClient("https://0de964231669473e9098b9f6cc1d6278:79d9f2eb24034de199b2a37cc058e0f2@sentry.io/257114"); internal static RavenClient Raven { get; } = new RavenClient("https://0de964231669473e9098b9f6cc1d6278:79d9f2eb24034de199b2a37cc058e0f2@sentry.io/257114");
internal static bool ShuttingDown { get; private set; } = false;
internal static string DataDirectory internal static string DataDirectory
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Serraniel\Discord Media Loader"); => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Serraniel\Discord Media Loader");
internal static DiscordRpc.RichPresence RpcPresence;
internal static DiscordRpc.EventHandlers RpcHandlers = new DiscordRpc.EventHandlers();
public static async Task Run(string[] paramStrings) public static async Task Run(string[] paramStrings)
{ {
try try
@ -91,10 +111,16 @@ namespace DML.Application.Classes
logMemory.ArchiveFile = logFile; logMemory.ArchiveFile = logFile;
} }
var databasePath = Path.Combine(DataDirectory, "config.db");
#if DEBUG
databasePath = Path.Combine(DataDirectory, "config.debug.db");
#endif
Logger.Debug("Loading database..."); Logger.Debug("Loading database...");
Database = new LiteDatabase(Path.Combine(DataDirectory, "config.db")); Database = new LiteDatabase(databasePath);
Database.Log.Logging += (message) => Logger.Trace($"LiteDB: {message}"); Database.Log.Logging += (message) => Logger.Trace($"LiteDB: {message}");
Migrator.CheckMigrations();
Logger.Debug("Loading settings collection out of database..."); Logger.Debug("Loading settings collection out of database...");
var settingsDB = Database.GetCollection<Settings>("settings"); var settingsDB = Database.GetCollection<Settings>("settings");
if (settingsDB.Count() > 1) if (settingsDB.Count() > 1)
@ -245,17 +271,59 @@ namespace DML.Application.Classes
job.Stop(); job.Stop();
job.Delete(); job.Delete();
} }
if (Settings.RescanRequired)
{
Logger.Info("Restting timestamps");
job.LastMessageId = 0;
job.Store();
} }
}
// TODO
// Settings.RescanRequired = false;
// Settings.Store();
if (Settings.UseRPC)
{
Logger.Info("Initializing RPC client");
Logger.Trace("Registering RPC handlers");
RpcHandlers.readyCallback += RpcReadyCallback;
RpcHandlers.disconnectedCallback += RpcDisconnectedCallback;
RpcHandlers.errorCallback += RpcErrorCallback;
RpcPresence.startTimestamp = DiscordRpcHelper.DateTimeToTimestamp(DateTime.UtcNow);
Logger.Debug("Calling RPC initialize");
DiscordRpc.Initialize("430025401851707393", ref RpcHandlers, true, null);
// Do not await ;)
Task.Run(async () =>
{
while (!ShuttingDown)
{
Logger.Trace("Running RPC callbacks");
await Task.Delay(5000);
}
});
}
DMLClient.Client.MessageReceived += Scheduler.HandleMessageReceived;
splash.Close(); splash.Close();
Logger.Info("Starting scheduler..."); Logger.Info("Starting scheduler...");
Scheduler.Start(); Scheduler.StartScheduler();
System.Windows.Forms.Application.Run(new MainForm()); System.Windows.Forms.Application.Run(new MainForm());
// shutting down
ShuttingDown = true;
Logger.Info("Stopping scheduler..."); Logger.Info("Stopping scheduler...");
Scheduler.Stop(); Scheduler.Stop();
Logger.Info("Shutting down RPC client");
DiscordRpc.Shutdown();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -285,5 +353,26 @@ namespace DML.Application.Classes
Logger.Trace($"Trying to find channel in {server} by id: {id}"); Logger.Trace($"Trying to find channel in {server} by id: {id}");
return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault(); return (from c in server.TextChannels where c.Id == id select c).FirstOrDefault();
} }
internal static void RpcUpdatePresence()
{
Logger.Debug("Updating RPC presence");
DiscordRpc.UpdatePresence(ref RpcPresence);
}
private static void RpcReadyCallback()
{
Logger.Debug("RpcReady");
}
private static void RpcDisconnectedCallback(int errorCode, string message)
{
Logger.Warn($"RPC disconnect received: {errorCode} - {message}");
}
private static void RpcErrorCallback(int errorCode, string message)
{
Logger.Error($"RPC error received: {errorCode} - {message}");
}
} }
} }

View file

@ -0,0 +1,70 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited Migrator.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using DML.AppCore.Classes;
using LiteDB;
namespace DML.Application.Core
{
internal static class Migrator
{
internal static ushort Version => 1;
internal static void CheckMigrations()
{
var baseVersion = Core.Database.Engine.UserVersion;
for (var step = (ushort)(baseVersion + 1); step <= Version; step++)
{
Migrate(step);
}
}
private static void Migrate(ushort step)
{
switch (step)
{
case 0:
// base database
break;
case 1:
foreach (var jobDoc in Core.Database.Engine.Find("jobs", Query.All()))
{
// pseudo datetime snowflake conversion https://discordapp.com/developers/docs/reference#convert-snowflake-to-datetime
var pseudoId = 0UL;
var timestamp = (ulong)jobDoc["KnownTimestamp"].AsDouble * 1000; // milliseconds have not been stored
if (timestamp > 0)
{
pseudoId = timestamp - 1420070400000 << 22;
pseudoId = pseudoId - (1000UL * 60 * 60 * 24 << 22); // substract one random day of pseudo id just in case the timestamp has errors
}
jobDoc["LastMessageId"] = Convert.ToInt64(pseudoId); // LiteDB maps (u)long to Int64
jobDoc.Remove("KnownTimestamp");
Core.Database.Engine.Update("jobs", jobDoc);
}
break;
}
Core.Database.Engine.UserVersion = step;
}
}
}

View file

@ -164,9 +164,14 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Classes\Core.cs" /> <Compile Include="Core\Core.cs" />
<Compile Include="Classes\Job.cs" /> <Compile Include="Classes\Job.cs" />
<Compile Include="Classes\JobScheduler.cs" /> <Compile Include="Classes\JobScheduler.cs" />
<Compile Include="Classes\RPC\DiscordRpc.cs" />
<Compile Include="Classes\RPC\DiscordRpcHelper.cs" />
<Compile Include="Classes\RPC\RpcWrapper.cs" />
<Compile Include="Classes\MediaData.cs" />
<Compile Include="Core\Migrator.cs" />
<Compile Include="Dialogs\LoginDialog.cs"> <Compile Include="Dialogs\LoginDialog.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>

View file

@ -1,4 +1,19 @@
namespace DML.Application.Dialogs #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited LoginDialog.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace DML.Application.Dialogs
{ {
partial class LoginDialog partial class LoginDialog
{ {

View file

@ -1,4 +1,19 @@
using System; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited LoginDialog.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Windows.Forms; using System.Windows.Forms;
using DML.Application.Classes; using DML.Application.Classes;
using static SweetLib.Utils.Logger.Logger; using static SweetLib.Utils.Logger.Logger;
@ -15,7 +30,7 @@ namespace DML.Application.Dialogs
private void LoginDialog_Shown(object sender, EventArgs e) private void LoginDialog_Shown(object sender, EventArgs e)
{ {
Trace("Login dialog shown."); Trace("Login dialog shown.");
edToken.Text = Core.Settings.LoginToken; edToken.Text = Core.Core.Settings.LoginToken;
} }
private void LoginDialog_FormClosing(object sender, FormClosingEventArgs e) private void LoginDialog_FormClosing(object sender, FormClosingEventArgs e)
@ -25,9 +40,9 @@ namespace DML.Application.Dialogs
return; return;
Debug("Adjusting login settings..."); Debug("Adjusting login settings...");
Core.Settings.LoginToken = edToken.Text; Core.Core.Settings.LoginToken = edToken.Text;
Core.Settings.Store(); Core.Core.Settings.Store();
} }
private void btnOk_Click(object sender, EventArgs e) private void btnOk_Click(object sender, EventArgs e)

View file

@ -1,4 +1,19 @@
namespace DML.Application #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited FrmInternalSplash.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace DML.Application
{ {
partial class FrmInternalSplash partial class FrmInternalSplash
{ {

View file

@ -0,0 +1,27 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited FrmInternalSplash.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System.Windows.Forms;
namespace DML.Application
{
public partial class FrmInternalSplash : Form
{
public FrmInternalSplash()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,37 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited IdentifiedString.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DML.Application.Helper
{
internal class IdentifiedString<T>
{
internal T Id { get; set; }
internal string Caption { get; set; }
internal IdentifiedString(T id, string caption)
{
Id = id;
Caption = caption;
}
public override string ToString() => Caption;
}
}

View file

@ -1,4 +1,19 @@
namespace DML.Application #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited MainForm.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace DML.Application
{ {
partial class MainForm partial class MainForm
{ {

View file

@ -1,4 +1,19 @@
using System; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited MainForm.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -10,6 +25,7 @@ using Discord.WebSocket;
using DML.AppCore.Classes; using DML.AppCore.Classes;
using DML.Application.Classes; using DML.Application.Classes;
using DML.Application.Helper; using DML.Application.Helper;
using DML.Application.Classes.RPC;
using DML.Client; using DML.Client;
using static DML.Client.DMLClient; using static DML.Client.DMLClient;
using static SweetLib.Utils.Logger.Logger; using static SweetLib.Utils.Logger.Logger;
@ -26,6 +42,8 @@ namespace DML.Application
public partial class MainForm : Form public partial class MainForm : Form
{ {
private bool IsInitialized { get; set; } = false; private bool IsInitialized { get; set; } = false;
private DiscordRpc.RichPresence Presence { get; }
public MainForm() public MainForm()
{ {
InitializeComponent(); InitializeComponent();
@ -46,16 +64,16 @@ namespace DML.Application
lbVersion.Text = $"v{Assembly.GetExecutingAssembly().GetName().Version} Copyright © by Serraniel"; lbVersion.Text = $"v{Assembly.GetExecutingAssembly().GetName().Version} Copyright © by Serraniel";
Trace("Refreshing operating folder component..."); Trace("Refreshing operating folder component...");
edOperatingFolder.Text = Core.Settings.OperatingFolder; edOperatingFolder.Text = Core.Core.Settings.OperatingFolder;
Trace("Refreshing name scheme component..."); Trace("Refreshing name scheme component...");
edNameScheme.Text = Core.Settings.FileNameScheme; edNameScheme.Text = Core.Core.Settings.FileNameScheme;
Trace("Refreshing skip existing files component..."); Trace("Refreshing skip existing files component...");
cbSkipExisting.Checked = Core.Settings.SkipExistingFiles; cbSkipExisting.Checked = Core.Core.Settings.SkipExistingFiles;
Trace("Refreshing thread limit component..."); Trace("Refreshing thread limit component...");
edThreadLimit.Value = Core.Settings.ThreadLimit; edThreadLimit.Value = Core.Core.Settings.ThreadLimit;
if (cbGuild.Items.Count == 0) if (cbGuild.Items.Count == 0)
{ {
@ -70,7 +88,7 @@ namespace DML.Application
Trace("Refreshing job list component..."); Trace("Refreshing job list component...");
var oldIndex = lbxJobs.SelectedIndex; var oldIndex = lbxJobs.SelectedIndex;
lbxJobs.Items.Clear(); lbxJobs.Items.Clear();
foreach (var job in Core.Scheduler.JobList) foreach (var job in Core.Core.Scheduler.JobList)
{ {
lbxJobs.Items.Add(new IdentifiedString<int>(job.Id, $"{FindServerById(job.GuildId)?.Name}:{FindChannelById(FindServerById(job.GuildId), job.ChannelId)?.Name}")); lbxJobs.Items.Add(new IdentifiedString<int>(job.Id, $"{FindServerById(job.GuildId)?.Name}:{FindChannelById(FindServerById(job.GuildId), job.ChannelId)?.Name}"));
} }
@ -89,19 +107,19 @@ namespace DML.Application
} }
Trace("Updating operating folder..."); Trace("Updating operating folder...");
Core.Settings.OperatingFolder = edOperatingFolder.Text; Core.Core.Settings.OperatingFolder = edOperatingFolder.Text;
Trace("Updating name scheme..."); Trace("Updating name scheme...");
Core.Settings.FileNameScheme = edNameScheme.Text; Core.Core.Settings.FileNameScheme = edNameScheme.Text;
Trace("Updating skip existing files..."); Trace("Updating skip existing files...");
Core.Settings.SkipExistingFiles = cbSkipExisting.Checked; Core.Core.Settings.SkipExistingFiles = cbSkipExisting.Checked;
Trace("Updating thread limit..."); Trace("Updating thread limit...");
Core.Settings.ThreadLimit = (int)edThreadLimit.Value; Core.Core.Settings.ThreadLimit = (int)edThreadLimit.Value;
Trace("Storing new settings..."); Trace("Storing new settings...");
Core.Settings.Store(); Core.Core.Settings.Store();
Info("New settings have been saved."); Info("New settings have been saved.");
} }
@ -193,12 +211,12 @@ namespace DML.Application
ChannelId = ((IdentifiedString<ulong>)cbChannel.SelectedItem).Id ChannelId = ((IdentifiedString<ulong>)cbChannel.SelectedItem).Id
}; };
if (!(from j in Core.Scheduler.JobList if (!(from j in Core.Core.Scheduler.JobList
where j.GuildId == job.GuildId && j.ChannelId == job.ChannelId where j.GuildId == job.GuildId && j.ChannelId == job.ChannelId
select j).Any()) select j).Any())
{ {
job.Store(); job.Store();
Core.Scheduler.JobList.Add(job); Core.Core.Scheduler.JobList.Add(job);
} }
RefreshComponents(); RefreshComponents();
@ -216,11 +234,11 @@ namespace DML.Application
var jobId = ((IdentifiedString<int>)lbxJobs.SelectedItem).Id; var jobId = ((IdentifiedString<int>)lbxJobs.SelectedItem).Id;
var job = Core.Scheduler.JobList.FirstOrDefault(j => j.Id == jobId); var job = Core.Core.Scheduler.JobList.FirstOrDefault(j => j.Id == jobId);
if (job != null) if (job != null)
{ {
Core.Scheduler.JobList.Remove(job); Core.Core.Scheduler.JobList.Remove(job);
Core.Scheduler.RunningJobs.Remove(job.Id); Core.Core.Scheduler.RunningJobs.Remove(job.Id);
job.Stop(); job.Stop();
job.Delete(); job.Delete();
} }
@ -231,9 +249,9 @@ namespace DML.Application
private void tmrRefreshProgress_Tick(object sender, System.EventArgs e) private void tmrRefreshProgress_Tick(object sender, System.EventArgs e)
{ {
var scanned = Core.Scheduler.MessagesScanned; var scanned = Core.Core.Scheduler.MessagesScanned;
var totalAttachments = Core.Scheduler.TotalAttachments; var totalAttachments = Core.Core.Scheduler.TotalAttachments;
var done = Core.Scheduler.AttachmentsDownloaded; var done = Core.Core.Scheduler.AttachmentsDownloaded;
var progress = totalAttachments > 0 ? (int)(100 * done / totalAttachments) : 0; var progress = totalAttachments > 0 ? (int)(100 * done / totalAttachments) : 0;
progress = Math.Min(Math.Max(0, progress), 100); progress = Math.Min(Math.Max(0, progress), 100);
@ -241,6 +259,18 @@ namespace DML.Application
pgbProgress.Value = progress; pgbProgress.Value = progress;
lbProgress.Text = $"Scanned: {scanned} Downloaded: {done} Open: {totalAttachments - done}"; lbProgress.Text = $"Scanned: {scanned} Downloaded: {done} Open: {totalAttachments - done}";
if (Core.Core.Settings.UseRPC)
{
Core.Core.RpcPresence.details = "Downloading media files";
Core.Core.RpcPresence.state = $"{done} / {totalAttachments} ({pgbProgress.Value}%)";
Core.Core.RpcPresence.largeImageKey = "main";
Core.Core.RpcPresence.largeImageText = "Visit discordmedialoader.net";
Core.Core.RpcPresence.smallImageKey = "author";
Core.Core.RpcPresence.smallImageText = "Made by Serraniel";
Core.Core.RpcUpdatePresence();
}
} }
private void aboutToolStripMenuItem_Click(object sender, System.EventArgs e) private void aboutToolStripMenuItem_Click(object sender, System.EventArgs e)
@ -285,7 +315,7 @@ namespace DML.Application
private void showTokenToolStripMenuItem_Click(object sender, EventArgs e) private void showTokenToolStripMenuItem_Click(object sender, EventArgs e)
{ {
Clipboard.SetText(Core.Settings.LoginToken); Clipboard.SetText(Core.Core.Settings.LoginToken);
MessageBox.Show(this, "Your login token has been copied to your clipboard.", "Discord Media Loader", MessageBox.Show(this, "Your login token has been copied to your clipboard.", "Discord Media Loader",
MessageBoxButtons.OK); MessageBoxButtons.OK);
} }

View file

@ -1,5 +1,19 @@
using System.Reflection; #region LICENSE
using System.Runtime.CompilerServices; /**********************************************************************************************
* Copyright (C) 2017-2019 - All Rights Reserved
*
* This file is part of "DML.Application".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "DML.Application" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited AssemblyInfo.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden // Allgemeine Informationen über eine Assembly werden über die folgenden

View file

@ -1,12 +1,17 @@
//------------------------------------------------------------------------------ #region LICENSE
// <auto-generated> /**********************************************************************************************
// Dieser Code wurde von einem Tool generiert. * Copyright (C) 2017-2019 - All Rights Reserved
// Laufzeitversion:4.0.30319.42000 *
// * This file is part of "DML.Application".
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn * The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
// der Code erneut generiert wird. *
// </auto-generated> * "DML.Application" is licensed under Apache 2.0.
//------------------------------------------------------------------------------ * Full license is included in the project repository.
*
* Users who edited Resources.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace DML.Application.Properties { namespace DML.Application.Properties {
using System; using System;

View file

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 300 KiB

View file

@ -0,0 +1,15 @@
extensions: .cs .cpp .h
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2017-%CurrentYear% - All Rights Reserved
*
* This file is part of "%Project%".
* The official repository is hosted at https://github.com/Serraniel/DiscordMediaLoader
*
* "%Project%" is licensed under Apache 2.0.
* Full license is included in the project repository.
*
* Users who edited %FileName% under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion

View file

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Sample license text.
-->
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />

View file

@ -190,6 +190,9 @@
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
</Compile> </Compile>
<None Include="..\..\Darkorbit Helper Program\src\Darkorbit Helper Program\Darkorbit Helper Program.licenseheader">
<Link>Darkorbit Helper Program.licenseheader</Link>
</None>
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>

View file

@ -1,4 +1,19 @@
namespace Discord_Media_Loader #region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited FrmDownload.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace Discord_Media_Loader
{ {
partial class FrmDownload partial class FrmDownload
{ {

View file

@ -1,4 +1,19 @@
using System; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited FrmDownload.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Net; using System.Net;

View file

@ -1,4 +1,19 @@
namespace Discord_Media_Loader #region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited FrmSplash.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace Discord_Media_Loader
{ {
partial class FrmSplash partial class FrmSplash
{ {

View file

@ -1,4 +1,19 @@
using System; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited FrmSplash.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Windows.Forms; using System.Windows.Forms;
@ -15,6 +30,7 @@ namespace Discord_Media_Loader
private async void FrmSplash_Shown(object sender, EventArgs e) private async void FrmSplash_Shown(object sender, EventArgs e)
{ {
#if !DEBUG
UseWaitCursor = true; UseWaitCursor = true;
try try
{ {
@ -52,6 +68,7 @@ namespace Discord_Media_Loader
{ {
UseWaitCursor = false; UseWaitCursor = false;
} }
#endif
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
} }

View file

@ -1,4 +1,19 @@
using System; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited TaskBarProgress.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Discord_Media_Loader.Helper namespace Discord_Media_Loader.Helper

View file

@ -1,4 +1,19 @@
using System; #region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited VersionHelper.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;

View file

@ -0,0 +1,47 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited Program.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using DML.Application.Core;
using Nito.AsyncEx;
using System;
using System.Windows.Forms;
namespace Discord_Media_Loader
{
static class Program
{
[STAThread]
static void Main(string[] paramStrings)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var splashScreen = new FrmSplash();
if (splashScreen.ShowDialog() == DialogResult.OK)
{
DoLaunch(paramStrings);
}
else
{
Application.Restart();
}
}
private static void DoLaunch(string[] paramStrings)
{
AsyncContext.Run(() => Core.Run(paramStrings));
}
}
}

View file

@ -1,5 +1,19 @@
using System.Reflection; #region LICENSE
using System.Runtime.CompilerServices; /**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited AssemblyInfo.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden // Allgemeine Informationen über eine Assembly werden über die folgenden

View file

@ -1,12 +1,17 @@
//------------------------------------------------------------------------------ #region LICENSE
// <auto-generated> /**********************************************************************************************
// Dieser Code wurde von einem Tool generiert. * Copyright (C) 2019-2019 - All Rights Reserved
// Laufzeitversion:4.0.30319.42000 *
// * This file is part of "Discord Media Loader".
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn * The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
// der Code erneut generiert wird. *
// </auto-generated> * "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
//------------------------------------------------------------------------------ * Full license is included in the project repository.
*
* Users who edited Resources.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace Discord_Media_Loader.Properties { namespace Discord_Media_Loader.Properties {
using System; using System;

View file

@ -0,0 +1,31 @@
#region LICENSE
/**********************************************************************************************
* Copyright (C) 2019-2019 - All Rights Reserved
*
* This file is part of "Discord Media Loader".
* The official repository is hosted at https://github.com/Serraniel/Darkorbit-Helper-Program
*
* "Discord Media Loader" is licensed under European Union Public Licence V. 1.2.
* Full license is included in the project repository.
*
* Users who edited Settings.Designer.cs under the condition of the used license:
* - Serraniel (https://github.com/Serraniel)
**********************************************************************************************/
#endregion
namespace Discord_Media_Loader.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View file

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 300 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 412 KiB

After

Width:  |  Height:  |  Size: 412 KiB

View file

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Sample license text.
-->
<packages> <packages>
<package id="Discord.Net" version="1.0.2" targetFramework="net461" /> <package id="Discord.Net" version="1.0.2" targetFramework="net461" />
<package id="Discord.Net.Commands" version="1.0.2" targetFramework="net461" /> <package id="Discord.Net.Commands" version="1.0.2" targetFramework="net461" />