From 60b459e217051c02f615cee69168a58388470159 Mon Sep 17 00:00:00 2001 From: Andy <88590076+AAndyProgram@users.noreply.github.com> Date: Mon, 23 May 2022 15:51:08 +0300 Subject: [PATCH] 3.0.0.10 Added downloading groups Added downloading Twitter saved posts Added scripts when closing and completing the download Opening Info and Progress forms when downloads start Disabling the opening of forms Info and Progress at the start of downloads if it was once closed Added focusing the main window when opening Info or Progress forms Fixed downloading Instagram tagged data Fixed forbidden characters Instagram stories Updated form field checkers Fixed downloading Imgur and Gfycat if they were posted on Reddit Fixed separate Instagram posts were not downloading via the Video Downloader form. Date time filenames Twitter 4K images --- Changelog.md | 21 ++ ProgramScreenshots/GroupCreating.png | Bin 0 -> 7626 bytes ProgramScreenshots/MainWindowGroups.png | Bin 0 -> 12354 bytes ProgramScreenshots/SavedPosts.png | Bin 6413 -> 7869 bytes ProgramScreenshots/SettingsGlobalBehavior.png | Bin 10439 -> 13663 bytes SCrawler.Plugin.XVIDEOS/SiteSettings.vb | 2 +- SCrawler/API/Base/UserDataBase.vb | 7 +- SCrawler/API/Instagram/SiteSettings.vb | 47 ++-- SCrawler/API/Instagram/UserData.vb | 23 +- SCrawler/API/Reddit/Channel.vb | 8 +- SCrawler/API/Reddit/M3U8.vb | 2 +- SCrawler/API/Reddit/SiteSettings.vb | 2 +- SCrawler/API/Reddit/UserData.vb | 5 +- SCrawler/API/Twitter/SiteSettings.vb | 11 +- SCrawler/API/Twitter/UserData.vb | 80 ++++-- SCrawler/Channels/ChannelViewForm.vb | 2 +- SCrawler/Channels/ChannelsStatsForm.vb | 4 +- SCrawler/Content/Icons/GroupBy_284.ico | Bin 0 -> 2862 bytes .../Download/ActiveDownloadingProgress.vb | 7 +- SCrawler/Download/DownloadedInfoForm.vb | 10 +- SCrawler/Download/Groups/DownloadGroup.vb | 211 +++++++++++++++ .../Groups/DownloadGroupCollection.vb | 90 +++++++ .../Groups/GroupEditorForm.Designer.vb | 253 ++++++++++++++++++ SCrawler/Download/Groups/GroupEditorForm.resx | 207 ++++++++++++++ SCrawler/Download/Groups/GroupEditorForm.vb | 111 ++++++++ SCrawler/Download/TDownloader.vb | 6 +- SCrawler/Download/VideosDownloaderForm.vb | 2 +- .../Editors/GlobalSettingsForm.Designer.vb | 177 ++++++++++-- SCrawler/Editors/GlobalSettingsForm.resx | 6 + SCrawler/Editors/GlobalSettingsForm.vb | 22 +- SCrawler/Editors/SiteEditorForm.vb | 17 +- SCrawler/Editors/UserCreatorForm.vb | 10 +- SCrawler/LabelsKeeper.vb | 2 +- SCrawler/MainFrame.Designer.vb | 52 +++- SCrawler/MainFrame.resx | 3 + SCrawler/MainFrame.vb | 75 +++++- SCrawler/MainFrameObjects.vb | 7 + SCrawler/MainMod.vb | 18 +- SCrawler/My Project/AssemblyInfo.vb | 4 +- SCrawler/My Project/Resources.Designer.vb | 10 + SCrawler/My Project/Resources.resx | 3 + SCrawler/SCrawler.vbproj | 12 + SCrawler/SettingsCLS.vb | 14 +- 43 files changed, 1422 insertions(+), 121 deletions(-) create mode 100644 ProgramScreenshots/GroupCreating.png create mode 100644 ProgramScreenshots/MainWindowGroups.png create mode 100644 SCrawler/Content/Icons/GroupBy_284.ico create mode 100644 SCrawler/Download/Groups/DownloadGroup.vb create mode 100644 SCrawler/Download/Groups/DownloadGroupCollection.vb create mode 100644 SCrawler/Download/Groups/GroupEditorForm.Designer.vb create mode 100644 SCrawler/Download/Groups/GroupEditorForm.resx create mode 100644 SCrawler/Download/Groups/GroupEditorForm.vb diff --git a/Changelog.md b/Changelog.md index 0483056..d93f439 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,24 @@ +# 3.0.0.10 + +- Added + - **Downloading groups** + - **Download saved Twitter posts** (bookmarks) + - Ability to enable/disable progress form opening at the start of downloading + - Ability to enable/disable Info form opening at the start of downloading + - The ability to disable the opening of forms Info and Progress at the start of downloads if it was once closed + - Focusing the main window when opening Info or Progress forms + - Ability to execute a script/command when closing SCrawler + - Ability to execute a script/command after all downloads are completed + - Minor improvements +- Fixed + - Instagram tagged data not downloading (now requires one more parameter **x-csrftoken** to download tagged data) + - In some cases, Instagram Stories cannot be downloaded due to forbidden Windows characters + - Separate Instagram posts were not downloading via the Video Downloader form. + - In some cases, an Imgur video hosted on Reddit won't download + - Gfycat data not downloading from saved Reddit posts + - In some cases, the date and time are not added to the filename + - Unable to download photos from Twitter in full resolution (4K) + # 3.0.0.9 - Added diff --git a/ProgramScreenshots/GroupCreating.png b/ProgramScreenshots/GroupCreating.png new file mode 100644 index 0000000000000000000000000000000000000000..eb476e35321458b9f12ccc7503792aa033432e82 GIT binary patch literal 7626 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
0 And __SaveData Then TextSaver.SaveTextToFile(_TempPostsList.ListToString(, Environment.NewLine), MyFilePosts, True,, EDP.None) + If _TempPostsList.Count > 0 And __SaveData Then TextSaver.SaveTextToFile(_TempPostsList.ListToString(Environment.NewLine), MyFilePosts, True,, EDP.None) _ContentNew.ListAddList(_TempMediaList, LAP.ClearBeforeAdd) DownloadContent(Token) ThrowIfDisposed() @@ -913,6 +913,7 @@ BlockNullPicture: End Using End If End If + Catch iex As IndexOutOfRangeException When Disposed Catch oex As OperationCanceledException When Token.IsCancellationRequested Catch dex As ObjectDisposedException When Disposed Catch ex As Exception @@ -933,7 +934,7 @@ BlockNullPicture: Dim ff As SFile = Nothing Try If Not f.IsEmptyString AndAlso f.Exists Then - If Settings.FileReplaceNameByDate Then + If Settings.FileReplaceNameByDate Or Settings.FileAddTimeToFileName Then ff = f ff.Name = String.Format(FileDateAppenderPattern, f.Name, CStr(AConvert(Of String)(If(m.Post.Date, Now), FileDateAppenderProvider, String.Empty))) ff = SFile.Indexed_IndexFile(ff,, New NumberedFile(ff)) diff --git a/SCrawler/API/Instagram/SiteSettings.vb b/SCrawler/API/Instagram/SiteSettings.vb index 0e03fff..7988e9a 100644 --- a/SCrawler/API/Instagram/SiteSettings.vb +++ b/SCrawler/API/Instagram/SiteSettings.vb @@ -9,6 +9,7 @@ Imports SCrawler.API.Base Imports SCrawler.Plugin Imports SCrawler.Plugin.Attributes +Imports PersonalUtilities.Forms Imports PersonalUtilities.Tools Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.XML.Base @@ -30,18 +31,27 @@ Namespace API.Instagram End Property #End Region #Region "Providers" - Private Class TimersChecker : Implements ICustomProvider + Private Class TimersChecker : Implements IFieldsCheckerProvider + Private Property ErrorMessage As String Implements IFieldsCheckerProvider.ErrorMessage + Private Property Name As String Implements IFieldsCheckerProvider.Name + Private Property TypeError As Boolean Implements IFieldsCheckerProvider.TypeError + Private ReadOnly LVProvider As New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral} Private ReadOnly _LowestValue As Integer Friend Sub New(ByVal LowestValue As Integer) _LowestValue = LowestValue End Sub Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert - If ACheck(Of Integer)(Value) AndAlso CInt(Value) >= _LowestValue Then - Return Value + TypeError = False + ErrorMessage = String.Empty + If Not ACheck(Of Integer)(Value) Then + TypeError = True + ElseIf CInt(Value) < _LowestValue Then + ErrorMessage = $"The value of [{Name}] field must be greater than or equal to {_LowestValue.NumToString(LVProvider)}" Else - Return Nothing + Return Value End If + Return Nothing End Function Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat Throw New NotImplementedException() @@ -53,35 +63,37 @@ Namespace API.Instagram Friend ReadOnly Property Hash As PropertyValue Friend ReadOnly Property HashSavedPosts As PropertyValue - + + Friend ReadOnly Property CSRF_TOKEN As PropertyValue + Friend Property IG_APP_ID As PropertyValue - + Friend Property IG_WWW_CLAIM As PropertyValue - + Friend ReadOnly Property SavedPostsUserName As PropertyValue Friend ReadOnly Property StoriesAndTaggedReady As Boolean Get - Return ACheck(IG_APP_ID.Value) And ACheck(IG_WWW_CLAIM.Value) + Return ACheck(IG_APP_ID.Value) And ACheck(IG_WWW_CLAIM.Value) And ACheck(CSRF_TOKEN.Value) End Get End Property #End Region #Region "Download properties" Friend ReadOnly Property HashUpdateRequired As XMLValue(Of Boolean) - + Friend ReadOnly Property RequestsWaitTimer As PropertyValue Private ReadOnly Property RequestsWaitTimerProvider As IFormatProvider - + Friend ReadOnly Property RequestsWaitTimerTaskCount As PropertyValue Private ReadOnly Property RequestsWaitTimerTaskCountProvider As IFormatProvider - + Friend ReadOnly Property SleepTimerOnPostsLimit As PropertyValue Private ReadOnly Property SleepTimerOnPostsLimitProvider As IFormatProvider - + Friend ReadOnly Property GetStories As PropertyValue - + Friend ReadOnly Property GetTagged As PropertyValue #End Region #Region "429 bypass" @@ -119,7 +131,7 @@ Namespace API.Instagram LastApplyingValue = If(LastApplyingValue, 0) + 10 TooManyRequestsReadyForCatch = False MyMainLOG = $"Instagram downloading error: too many requests. Try again after {If(LastApplyingValue, 10)} minutes..." - End If + End If End If Else .ValueF = Nothing @@ -136,11 +148,13 @@ Namespace API.Instagram Dim app_id$ = String.Empty Dim www_claim$ = String.Empty + Dim token$ = String.Empty With Responser If .File.Exists Then .LoadSettings() With .Headers + If .ContainsKey(Header_CSRF_TOKEN) Then token = .Item(Header_CSRF_TOKEN) If .ContainsKey(Header_IG_APP_ID) Then app_id = .Item(Header_IG_APP_ID) If .ContainsKey(Header_IG_WWW_CLAIM) Then www_claim = .Item(Header_IG_WWW_CLAIM) End With @@ -157,6 +171,7 @@ Namespace API.Instagram HashUpdateRequired = New XMLValue(Of Boolean)("InstaHashUpdateRequired", True, _XML, n) Hash = New PropertyValue(String.Empty, GetType(String)) HashSavedPosts = New PropertyValue(String.Empty, GetType(String)) + CSRF_TOKEN = New PropertyValue(token, GetType(String), Sub(v) ChangeResponserFields(NameOf(CSRF_TOKEN), v)) IG_APP_ID = New PropertyValue(app_id, GetType(String), Sub(v) ChangeResponserFields(NameOf(IG_APP_ID), v)) IG_WWW_CLAIM = New PropertyValue(www_claim, GetType(String), Sub(v) ChangeResponserFields(NameOf(IG_WWW_CLAIM), v)) @@ -192,12 +207,14 @@ Namespace API.Instagram End Function Private Const Header_IG_APP_ID As String = "x-ig-app-id" Private Const Header_IG_WWW_CLAIM As String = "x-ig-www-claim" + Private Const Header_CSRF_TOKEN As String = "x-csrftoken" Private Sub ChangeResponserFields(ByVal PropName As String, ByVal Value As Object) If Not PropName.IsEmptyString Then Dim f$ = String.Empty Select Case PropName Case NameOf(IG_APP_ID) : f = Header_IG_APP_ID Case NameOf(IG_WWW_CLAIM) : f = Header_IG_WWW_CLAIM + Case NameOf(CSRF_TOKEN) : f = Header_CSRF_TOKEN End Select If Not f.IsEmptyString Then If Responser.Headers.Count > 0 AndAlso Responser.Headers.ContainsKey(f) Then Responser.Headers.Remove(f) @@ -303,7 +320,7 @@ Namespace API.Instagram End Try End Function Friend Overrides Function GetSpecialDataF(ByVal URL As String) As IEnumerable(Of UserMedia) - Return UserData.GetVideoInfo(URL, Responser) + Return UserData.GetVideoInfo(URL, Responser, Me) End Function Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) If Options Is Nothing OrElse Not TypeOf Options Is EditorExchangeOptions Then Options = New EditorExchangeOptions(Me) diff --git a/SCrawler/API/Instagram/UserData.vb b/SCrawler/API/Instagram/UserData.vb index a2a40e3..ea3fff5 100644 --- a/SCrawler/API/Instagram/UserData.vb +++ b/SCrawler/API/Instagram/UserData.vb @@ -14,6 +14,7 @@ Imports PersonalUtilities.Tools.WebDocuments.JSON Imports SCrawler.API.Base Imports System.Threading Imports System.Net +Imports System.Reflection Imports UTypes = SCrawler.API.Base.UserMedia.Types Namespace API.Instagram Friend Class UserData : Inherits UserDataBase @@ -262,8 +263,8 @@ Namespace API.Instagram TaggedCount = j.Value("total_count").FromXML(Of Integer)(0) TaggedChecked = True If TaggedCount > 200 Then - Dim a% = MsgBoxE({$"The number of tagged posts is {TaggedCount.NumToString(New ANumbers With { - .FormatOptions = ANumbers.Options.GroupIntegral})}" & vbCr & + Dim a% = MsgBoxE({$"The number of tagged posts by user [{ToString()}] is { _ + TaggedCount.NumToString(New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral})}" & vbCr & "The tagged data download operation can take a long time.", "Too much tagged data"}, vbExclamation,,, {"Continue", New MsgBoxButton("Disable and cancel") With { @@ -277,11 +278,9 @@ Namespace API.Instagram End If End Select Else - If j.Value("status") = "ok" AndAlso j({"data", "user"}).XmlIfNothing.Count = 0 AndAlso _TempMediaList.Count = 0 Then - MySiteSettings.HashUpdateRequired.Value = True - UserExists = False - Throw New ExitException(_DownloadComplete) - End If + If j.Value("status") = "ok" AndAlso j({"data", "user"}).XmlIfNothing.Count = 0 AndAlso + _TempMediaList.Count = 0 AndAlso Section = Sections.Timeline Then _ + UserExists = False : Throw New ExitException(_DownloadComplete) End If End Using Else @@ -501,7 +500,7 @@ Namespace API.Instagram If StoriesList.ListExists Then tmpList = StoriesList.Take(5) If tmpList.ListExists Then - qStr = String.Format(ReqUrl, tmpList.Select(Function(q) $"reel_ids=highlight:{q}").ListToString(, "&")) + qStr = String.Format(ReqUrl, tmpList.Select(Function(q) $"reel_ids=highlight:{q}").ListToString("&")) r = Responser.GetResponse(qStr,, EDP.ThrowException) ThrowAny(Token) If Not r.IsEmptyString Then @@ -509,7 +508,7 @@ Namespace API.Instagram If j.Contains("reels") Then For Each jj In j("reels") i += 1 - sFolder = jj.Value("title") + sFolder = jj.Value("title").StringRemoveWinForbiddenSymbols storyID = jj.Value("id").Replace("highlight:", String.Empty) If sFolder.IsEmptyString Then sFolder = $"Story_{storyID}" If sFolder.IsEmptyString Then sFolder = $"Story_{i}" @@ -567,7 +566,7 @@ Namespace API.Instagram UserExists = False ElseIf Responser.StatusCode = HttpStatusCode.BadRequest Then HasError = True - MyMainLOG = "Instagram credentials have expired" + MyMainLOG = $"Instagram credentials have expired: {ToString()} [{s}]" MySiteSettings.HashUpdateRequired.Value = True ElseIf Responser.StatusCode = HttpStatusCode.Forbidden And s = Sections.Tagged Then Return 3 @@ -582,6 +581,7 @@ Namespace API.Instagram Return 1 Else MySiteSettings.HashUpdateRequired.Value = True + MyMainLOG = $"Instagram hash requested: {ToString()} [{s}]" If Not FromPE Then LogError(ex, Message) : HasError = True Return 0 End If @@ -596,12 +596,13 @@ Namespace API.Instagram m.SpecialFolder = SpecialFolder Return m End Function - Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal r As Response) As IEnumerable(Of UserMedia) + Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal r As Response, ByVal _Settings As SiteSettings) As IEnumerable(Of UserMedia) Try If Not URL.IsEmptyString AndAlso URL.Contains("instagram.com") Then Dim PID$ = RegexReplace(URL, RParams.DMS(".*?instagram.com/p/([_\w\d]+)", 1)) If Not PID.IsEmptyString Then Using t As New UserData + t.SetEnvironment(Settings(_Settings.GetType.GetCustomAttribute(Of Plugin.Attributes.Manifest)().GUID), Nothing, False, False) t.Responser = New Response t.Responser.Copy(r) t._SavedPostsIDs.Add(PID) diff --git a/SCrawler/API/Reddit/Channel.vb b/SCrawler/API/Reddit/Channel.vb index 584907d..3741b27 100644 --- a/SCrawler/API/Reddit/Channel.vb +++ b/SCrawler/API/Reddit/Channel.vb @@ -364,16 +364,16 @@ Namespace API.Reddit Dim l As New List(Of String) If Posts.Count > 0 Or PostsLatest.Count > 0 Then l.ListAddList((From p In PostsAll Where Not p.ID.IsEmptyString Select p.ID), LAP.NotContainsOnly) l.ListAddList(PostsNames, LAP.NotContainsOnly) - If l.Count > 0 Then TextSaver.SaveTextToFile(l.ListToString(, "|"), FilePosts, True,, EDP.SendInLog) + If l.Count > 0 Then TextSaver.SaveTextToFile(l.ListToString("|"), FilePosts, True,, EDP.SendInLog) End If Using x As New XmlFile With {.AllowSameNames = True, .Name = "Channel"} x.Add(Name_Name, Name) x.Add(Name_ID, ID) x.Add(Name_ViewMode, CInt(ViewMode)) x.Add(Name_ViewPeriod, CInt(ViewPeriod)) - x.Add(Name_UsersAdded, CountOfAddedUsers.ListToString(, "|")) - x.Add(Name_PostsDownloaded, CountOfLoadedPostsPerSession.ListToString(, "|")) - x.Add(Name_UsersExistent, ChannelExistentUserNames.ListToString(, "|")) + x.Add(Name_UsersAdded, CountOfAddedUsers.ListToString("|")) + x.Add(Name_PostsDownloaded, CountOfLoadedPostsPerSession.ListToString("|")) + x.Add(Name_UsersExistent, ChannelExistentUserNames.ListToString("|")) If Posts.Count > 0 Or PostsLatest.Count > 0 Then Dim tmpPostList As List(Of UserPost) = Nothing tmpPostList.ListAddList(Posts).ListAddList(PostsLatest) diff --git a/SCrawler/API/Reddit/M3U8.vb b/SCrawler/API/Reddit/M3U8.vb index 2b2ea8b..f9109c6 100644 --- a/SCrawler/API/Reddit/M3U8.vb +++ b/SCrawler/API/Reddit/M3U8.vb @@ -52,7 +52,7 @@ Namespace API.Reddit If Not r.IsEmptyString Then Dim lp As New ListAddParams(LAP.NotContainsOnly) With { .Converter = Function(input) $"{BaseUrl}/{input}", - .e = New ErrorsDescriber(False, False, True, New List(Of String))} + .Error = New ErrorsDescriber(False, False, True, New List(Of String))} Return ListAddList(Of String, List(Of String))(Nothing, DirectCast(RegexReplace(r, PlayListRegEx_2), List(Of String)), lp).ListIfNothing End If End If diff --git a/SCrawler/API/Reddit/SiteSettings.vb b/SCrawler/API/Reddit/SiteSettings.vb index 43d8c28..d6760d2 100644 --- a/SCrawler/API/Reddit/SiteSettings.vb +++ b/SCrawler/API/Reddit/SiteSettings.vb @@ -84,7 +84,7 @@ Namespace API.Reddit If avg > 100 Then Return MsgBoxE({"Over the past hour, Reddit has received an average of " & avg.NumToString(New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral}) & " outage reports:" & vbCr & - dl.ListToString(, vbCr) & vbCr & vbCr & + dl.ListToString(vbCr) & vbCr & vbCr & "Do you want to continue parsing Reddit data?", "There are outage reports on Reddit"}, vbYesNo) = vbYes End If End If diff --git a/SCrawler/API/Reddit/UserData.vb b/SCrawler/API/Reddit/UserData.vb index 04d9c67..4c0cddc 100644 --- a/SCrawler/API/Reddit/UserData.vb +++ b/SCrawler/API/Reddit/UserData.vb @@ -348,7 +348,7 @@ Namespace API.Reddit End If tmpUrl = s.Value("url") - If Not tmpUrl.IsEmptyString AndAlso tmpUrl.Contains("redgifs.com") Then + If Not tmpUrl.IsEmptyString AndAlso tmpUrl.StringContains({"redgifs.com", "gfycat.com"}) Then If SaveToCache Then tmpUrl = s.Value({"media", "oembed"}, "thumbnail_url") If Not tmpUrl.IsEmptyString Then @@ -423,6 +423,8 @@ Namespace API.Reddit _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL.Replace(".gifv", ".mp4"), PostID, PostDate, _UserID, IsChannel), LNC) End If + ElseIf _URL.Contains(".mp4") Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL, PostID, PostDate, _UserID, IsChannel), LNC) ElseIf _URL.Contains(".gif") Then _TempMediaList.ListAddValue(MediaFromData(UTypes.GIF, _URL, PostID, PostDate, _UserID, IsChannel), LNC) Else @@ -700,6 +702,7 @@ Namespace API.Reddit End Using End If End If + Catch iex As IndexOutOfRangeException When Disposed Catch oex As OperationCanceledException When Token.IsCancellationRequested Catch dex As ObjectDisposedException When Disposed Catch ex As Exception diff --git a/SCrawler/API/Twitter/SiteSettings.vb b/SCrawler/API/Twitter/SiteSettings.vb index 24de77d..c89bdbb 100644 --- a/SCrawler/API/Twitter/SiteSettings.vb +++ b/SCrawler/API/Twitter/SiteSettings.vb @@ -12,7 +12,7 @@ Imports PersonalUtilities.Tools Imports PersonalUtilities.Functions.RegularExpressions Imports SCrawler.API.Base Namespace API.Twitter - + Friend Class SiteSettings : Inherits SiteSettingsBase Friend Const Header_Authorization As String = "authorization" Friend Const Header_Token As String = "x-csrf-token" @@ -31,6 +31,8 @@ Namespace API.Twitter Private ReadOnly Property Auth As PropertyValue Private ReadOnly Property Token As PropertyValue + + Friend ReadOnly Property SavedPostsUserName As PropertyValue Friend Overrides ReadOnly Property Responser As WEB.Response Friend Sub New() MyBase.New(TwitterSite) @@ -70,6 +72,7 @@ Namespace API.Twitter Auth = New PropertyValue(a, GetType(String), Sub(v) ChangeResponserFields(NameOf(Auth), v)) Token = New PropertyValue(t, GetType(String), Sub(v) ChangeResponserFields(NameOf(Token), v)) + SavedPostsUserName = New PropertyValue(String.Empty, GetType(String)) UserRegex = RParams.DMS("[htps:/]{7,8}.*?twitter.com/([^/]+)", 1) UrlPatternUser = "https://twitter.com/{0}" @@ -90,7 +93,11 @@ Namespace API.Twitter End If End Sub Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider - Return New UserData + If What = ISiteSettings.Download.SavedPosts Then + Return New UserData With {.IsSavedPosts = True, .User = New UserInfo With {.Name = CStr(AConvert(Of String)(SavedPostsUserName.Value, String.Empty))}} + Else + Return New UserData + End If End Function Friend Overrides Function GetSpecialDataF(ByVal URL As String) As IEnumerable(Of UserMedia) Return UserData.GetVideoInfo(URL, Responser) diff --git a/SCrawler/API/Twitter/UserData.vb b/SCrawler/API/Twitter/UserData.vb index 388577d..7fc9a4a 100644 --- a/SCrawler/API/Twitter/UserData.vb +++ b/SCrawler/API/Twitter/UserData.vb @@ -27,12 +27,19 @@ Namespace API.Twitter End Sub #Region "Download functions" Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) - If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.File.File), LAP.ClearBeforeAdd, LAP.NotContainsOnly) - DownloadData(String.Empty, Token) + If IsSavedPosts Then + If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.Post.ID), LAP.ClearBeforeAdd, LAP.NotContainsOnly) + DownloadData(String.Empty, Token) + Else + If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.File.File), LAP.ClearBeforeAdd, LAP.NotContainsOnly) + DownloadData(String.Empty, Token) + End If End Sub Private Overloads Sub DownloadData(ByVal POST As String, ByVal Token As CancellationToken) Dim URL$ = String.Empty Try + Dim NextCursor$ = String.Empty + Dim __NextCursor As Predicate(Of EContainer) = Function(e) e.Value({"content", "operation", "cursor"}, "cursorType") = "Bottom" Dim PostID$ = String.Empty Dim PostDate$, dName$ Dim m As EContainer, nn As EContainer, s As EContainer @@ -42,24 +49,37 @@ Namespace API.Twitter Dim PicNode As Predicate(Of EContainer) = Function(e) e.Count > 0 AndAlso e.Contains("media_url") Dim UID As Func(Of EContainer, String) = Function(e) e.XmlIfNothing.Item({"user", "id"}).XmlIfNothingValue - URL = $"https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={Name}&count=200&exclude_replies=false&include_rts=1&tweet_mode=extended" - If Not POST.IsEmptyString Then URL &= $"&max_id={POST}" + If IsSavedPosts Then + If Name.IsEmptyString Then Throw New ArgumentNullException With {.HelpLink = 1} + URL = $"https://api.twitter.com/2/timeline/bookmark.json?screen_name={Name}&count=200" & + "&tweet_mode=extended&include_entities=true&include_user_entities=true&include_ext_media_availability=true" + If Not POST.IsEmptyString Then URL &= $"&cursor={SymbolsConverter.ASCII.EncodeSymbolsOnly(POST)}" + Else + URL = $"https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={Name}&count=200&exclude_replies=false&include_rts=1&tweet_mode=extended" + If Not POST.IsEmptyString Then URL &= $"&max_id={POST}" + End If ThrowAny(Token) Dim r$ = Responser.GetResponse(URL,, EDP.ThrowException) If Not r.IsEmptyString Then Using w As EContainer = JsonDocument.Parse(r) If Not w Is Nothing AndAlso w.Count > 0 Then - For Each nn In w + For Each nn In If(IsSavedPosts, w({"globalObjects", "tweets"}).XmlIfNothing, w) ThrowAny(Token) If nn.Count > 0 Then - PostID = nn.Value("id") - If ID.IsEmptyString Then - ID = UID(nn) - If Not ID.IsEmptyString Then UpdateUserInformation() + If IsSavedPosts Then + PostID = nn.Value + If PostID.IsEmptyString Then PostID = nn.Value("id_str") + Else + PostID = nn.Value("id") + If ID.IsEmptyString Then + ID = UID(nn) + If Not ID.IsEmptyString Then UpdateUserInformation() + End If End If - If UserDescriptionNeedToUpdate() AndAlso nn.Value({"user"}, "screen_name") = Name Then UserDescriptionUpdate(nn.Value({"user"}, "description")) + If Not IsSavedPosts AndAlso UserDescriptionNeedToUpdate() AndAlso nn.Value({"user"}, "screen_name") = Name Then _ + UserDescriptionUpdate(nn.Value({"user"}, "description")) 'Date Pattern: 'Sat Jan 01 01:10:15 +0000 2000 @@ -74,8 +94,8 @@ Namespace API.Twitter Continue For End If - If Not ParseUserMediaOnly OrElse (Not nn.Contains("retweeted_status") OrElse - (Not ID.IsEmptyString AndAlso UID(nn("retweeted_status")) = ID)) Then + If IsSavedPosts OrElse Not ParseUserMediaOnly OrElse (Not nn.Contains("retweeted_status") OrElse + (Not ID.IsEmptyString AndAlso UID(nn("retweeted_status")) = ID)) Then If Not CheckVideoNode(nn, PostID, PostDate) Then s = nn.ItemF({"extended_entities", "media"}) If s Is Nothing OrElse s.Count = 0 Then s = nn.ItemF({"retweeted_status", "extended_entities", "media"}) @@ -95,13 +115,25 @@ Namespace API.Twitter End If End If Next + + If IsSavedPosts Then + s = w.ItemF({"timeline", "instructions", 0, "addEntries", "entries"}).XmlIfNothing + If s.Count > 0 Then NextCursor = If(s.ItemF({__NextCursor})?.Value({"content", "operation", "cursor"}, "value"), String.Empty) + End If End If End Using - If POST.IsEmptyString And ExistsDetected Then Exit Sub - If Not PostID.IsEmptyString And NewPostDetected Then DownloadData(PostID, Token) + + If IsSavedPosts Then + If Not NextCursor.IsEmptyString And Not NextCursor = POST Then DownloadData(NextCursor, Token) + Else + If POST.IsEmptyString And ExistsDetected Then Exit Sub + If Not PostID.IsEmptyString And NewPostDetected Then DownloadData(PostID, Token) + End If End If + Catch ane As ArgumentNullException When ane.HelpLink = 1 + MyMainLOG = "Username not set for saved Twitter posts" Catch ex As Exception - ProcessException(ex, Token, $"data downloading error [{URL}]") + ProcessException(ex, Token, $"data downloading error{IIf(IsSavedPosts, " (Saved Posts)", String.Empty)} [{URL}]") End Try End Sub Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal resp As Response) As IEnumerable(Of UserMedia) @@ -128,20 +160,34 @@ Namespace API.Twitter End Function #Region "Picture options" Private Function GetPictureOption(ByVal w As EContainer) As String + Const P4K As String = "4096x4096" Try Dim ww As EContainer = w("sizes") If Not ww Is Nothing AndAlso ww.Count > 0 Then Dim l As New List(Of Sizes) + Dim Orig As Sizes? = New Sizes(w.Value({"original_info"}, "height").FromXML(Of Integer)(-1), P4K) + If Orig.Value.Value = -1 Then Orig = Nothing Dim LargeContained As Boolean = ww.Contains("large") For Each v As EContainer In ww If v.Count > 0 AndAlso v.Contains("h") Then l.Add(New Sizes(v.Value("h"), v.Name)) Next If l.Count > 0 Then l.Sort() - If l(0).Data.IsEmptyString And LargeContained Then Return "large" Else Return l(0).Data + If Orig.HasValue AndAlso l(0).Value < Orig.Value.Value Then + Return P4K + ElseIf l(0).Data.IsEmptyString Then + If LargeContained Then Return "large" Else Return P4K + Else + Return l(0).Data + End If + Else + Return P4K End If + ElseIf Not w.Value({"original_info"}, "height").IsEmptyString Then + Return P4K + Else + Return String.Empty End If - Return String.Empty Catch ex As Exception LogError(ex, "[API.Twitter.UserData.GetPictureOption]") Return String.Empty diff --git a/SCrawler/Channels/ChannelViewForm.vb b/SCrawler/Channels/ChannelViewForm.vb index 66b4694..f031241 100644 --- a/SCrawler/Channels/ChannelViewForm.vb +++ b/SCrawler/Channels/ChannelViewForm.vb @@ -284,7 +284,7 @@ Friend Class ChannelViewForm : Implements IChannelLimits Private Sub AppendPendingUsers() If LIST_POSTS.CheckedIndices.Count > 0 Then Dim c As Channel = GetCurrentChannel(False) - Dim lp As New ListAddParams(LAP.NotContainsOnly) With {.OnAddAction = Sub(ByVal u As PendingUser) u.ChannelUserAdded()} + Dim lp As New ListAddParams(LAP.NotContainsOnly) With {.OnProcessAction = Sub(ByVal u As PendingUser) u.ChannelUserAdded()} PendingUsers.ListAddList((From p As ListViewItem In LIST_POSTS.Items Where p.Checked Select New PendingUser(p.Text, c, GetPostBySelected(CStr(p.Tag)).CachedFile)), lp) diff --git a/SCrawler/Channels/ChannelsStatsForm.vb b/SCrawler/Channels/ChannelsStatsForm.vb index 7447d08..65f183c 100644 --- a/SCrawler/Channels/ChannelsStatsForm.vb +++ b/SCrawler/Channels/ChannelsStatsForm.vb @@ -57,9 +57,9 @@ Friend Class ChannelsStatsForm : Implements IOkCancelDeleteToolbar Try Dim c As List(Of String) = CMB_CHANNELS.Items.CheckedItems.Select(Function(cc) CStr(cc.Value(1))).ListIfNothing If c.ListExists Then - If MsgBoxE({$"The following channels will be deleted:{vbCr}{c.ListToString(, vbCr)}", "Deleting channels"}, vbExclamation,,, {"Confirm", "Cancel"}) = 0 Then + If MsgBoxE({$"The following channels will be deleted:{vbCr}{c.ListToString(vbCr)}", "Deleting channels"}, vbExclamation,,, {"Confirm", "Cancel"}) = 0 Then For Each CID$ In c : Settings.Channels.Remove(Settings.Channels.Find(CID)) : Next - MyMainLOG = $"Deleted channels:{vbNewLine}{c.ListToString(, vbNewLine)}" + MyMainLOG = $"Deleted channels:{vbNewLine}{c.ListToString(vbNewLine)}" MsgBoxE("Channels deleted") DeletedChannels += c.Count c.Clear() diff --git a/SCrawler/Content/Icons/GroupBy_284.ico b/SCrawler/Content/Icons/GroupBy_284.ico new file mode 100644 index 0000000000000000000000000000000000000000..f849cbac9e5bbd4e63b5def9d1a14eb42497e39a GIT binary patch literal 2862 zcmeHHJ66Lm5F7^s>BtdW<^aHjX?=!v|eGDWXjVK*> zDzq3+HI+{s_Kfv%t{$Y1Etyk$8(y*B^gM1aw(z^}Y!y%SRz{dC_P&OEP6Qi-x%N(v zGp|LyR>J2icdMRsLg|+{{nB_`(o4fQdzWqry`Gw!d!J%N)srvhUZdC?=J~ni+WWsX nU;X^Ff93l== 0 And Index <= 8, $"#{Index + 1}: ", String.Empty)}{Name}" + End Function + Private _ControlSent As Boolean = False + Friend Function GetControl() As ToolStripMenuItem + If Not _ControlSent Then + BTT_MENU.Text = ToString() + BTT_MENU.Tag = Key + _ControlSent = True + End If + Return BTT_MENU + End Function + Private Function SetIndex(ByVal Obj As Object, ByVal _Index As Integer) As Object Implements IIndexable.SetIndex + DirectCast(Obj, DownloadGroup).Index = _Index + Return Obj + End Function +#Region "Buttons" + Private Sub BTT_MENU_Click(sender As Object, e As EventArgs) Handles BTT_MENU.Click + DownloadUsers(True) + End Sub + Private Sub BTT_EDIT_Click(sender As Object, e As EventArgs) Handles BTT_EDIT.Click + Using f As New GroupEditorForm(Me) + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then RaiseEvent Updated(Me) + End Using + End Sub + Private Sub BTT_DELETE_Click(sender As Object, e As EventArgs) Handles BTT_DELETE.Click + If MsgBoxE({$"Are you sure you want to delete the [{Name}] group?", "Deleting a group"}, vbExclamation + vbYesNo) = vbYes Then + RaiseEvent Deleted(Me) + MsgBoxE({$"Group [{Name}] deleted", "Deleting a group"}) + End If + End Sub + Private Sub BTT_DOWNLOAD_Click(sender As Object, e As EventArgs) Handles BTT_DOWNLOAD.Click + DownloadUsers(True) + End Sub + Private Sub BTT_DOWNLOAD_FULL_Click(sender As Object, e As EventArgs) Handles BTT_DOWNLOAD_FULL.Click + DownloadUsers(False) + End Sub + Friend Sub DownloadUsers(ByVal UseReadyOption As Boolean) + Try + If Settings.Users.Count > 0 Then + Dim CheckParams As Predicate(Of IUserData) = Function(user) _ + (Temporary = CheckState.Indeterminate Or user.Temporary = CBool(Temporary)) And + (Favorite = CheckState.Indeterminate Or (user.Favorite = CBool(Favorite))) And + (Not UseReadyOption Or ReadyForDownloadIgnore Or user.ReadyForDownload = ReadyForDownload) + Dim f As Func(Of IUserData, IEnumerable(Of IUserData)) = Function(ByVal user As IUserData) As IEnumerable(Of IUserData) + If user.IsCollection Then + With DirectCast(user, UserDataBind) + If Count > 0 Then Return .Collections.SelectMany(f) + End With + Else + If Labels.Count = 0 OrElse user.Labels.ListContains(Labels) Then + If CheckParams.Invoke(user) Then Return {user} + End If + End If + Return New IUserData() {} + End Function + Dim u As IEnumerable(Of IUserData) = Settings.Users.SelectMany(f) + If u.ListExists Then + Downloader.AddRange(u) + Else + MsgBoxE({$"No users found for group [{Name}].", "No users found"}, vbExclamation) + End If + End If + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendInLog, ex, "[DownloadGroup.DownloadUsers]") + End Try + End Sub +#End Region +#Region "IEContainerProvider Support" + Public Function ToEContainer(Optional ByVal e As ErrorsDescriber = Nothing) As EContainer Implements IEContainerProvider.ToEContainer + Return New EContainer("Group", Labels.ListToString("|"), {New EAttribute(Name_Name, Name), + New EAttribute(Name_Temporary, CInt(Temporary)), + New EAttribute(Name_Favorite, CInt(Favorite)), + New EAttribute(Name_ReadyForDownload, ReadyForDownload.BoolToInteger), + New EAttribute(Name_ReadyForDownloadIgnore, ReadyForDownloadIgnore.BoolToInteger)}) + End Function +#End Region +#Region "IDisposable Support" + Private disposedValue As Boolean = False + Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean) + If Not disposedValue Then + If disposing Then + Labels.Clear() + BTT_DELETE.Dispose() + BTT_EDIT.Dispose() + BTT_MENU.Dispose() + SEP_1.Dispose() + BTT_DOWNLOAD.Dispose() + BTT_DOWNLOAD_FULL.Dispose() + End If + disposedValue = True + End If + End Sub + Protected Overrides Sub Finalize() + Dispose(False) + MyBase.Finalize() + End Sub + Friend Overloads Sub Dispose() Implements IDisposable.Dispose + Dispose(True) + GC.SuppressFinalize(Me) + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Download/Groups/DownloadGroupCollection.vb b/SCrawler/Download/Groups/DownloadGroupCollection.vb new file mode 100644 index 0000000..02b8c4c --- /dev/null +++ b/SCrawler/Download/Groups/DownloadGroupCollection.vb @@ -0,0 +1,90 @@ +' Copyright (C) 2022 Andy +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Tools +Namespace DownloadObjects.Groups + Friend Class DownloadGroupCollection : Implements IEnumerable(Of DownloadGroup), IMyEnumerator(Of DownloadGroup) + Friend Event Deleted As DownloadGroup.GroupEventHandler + Friend Event Added As DownloadGroup.GroupEventHandler + Friend Event Updated As DownloadGroup.GroupEventHandler + Private ReadOnly GroupsList As List(Of DownloadGroup) + Private ReadOnly GroupFile As SFile = "Settings\Groups.xml" + Friend Sub New() + GroupsList = New List(Of DownloadGroup) + If GroupFile.Exists Then + Using x As New XmlFile(GroupFile,, False) With {.XmlReadOnly = True, .AllowSameNames = True} + x.LoadData() + If x.Count > 0 Then GroupsList.ListAddList(x, LAP.IgnoreICopier) + End Using + If GroupsList.Count > 0 Then GroupsList.ForEach(Sub(ByVal g As DownloadGroup) + AddHandler g.Deleted, AddressOf OnGroupDeleted + AddHandler g.Updated, AddressOf OnGroupUpdated + End Sub) + End If + GroupsList.ListReindex + End Sub + Friend Function GetLabels() As List(Of String) + Return ListAddList(Nothing, GroupsList.SelectMany(Function(g) g.Labels), LAP.NotContainsOnly) + End Function + Default Friend ReadOnly Property Item(ByVal Index As Integer) As DownloadGroup Implements IMyEnumerator(Of DownloadGroup).MyEnumeratorObject + Get + Return GroupsList(Index) + End Get + End Property + Friend ReadOnly Property Count As Integer Implements IMyEnumerator(Of DownloadGroup).MyEnumeratorCount + Get + Return GroupsList.Count + End Get + End Property + Friend Sub Update() + If Count > 0 Then + Using x As New XmlFile With {.Name = "Groups", .AllowSameNames = True} + x.AddRange(GroupsList) + x.Save(GroupFile) + End Using + Else + GroupFile.Delete() + End If + End Sub + Private Sub OnGroupUpdated(ByVal Sender As DownloadGroup) + Update() + RaiseEvent Updated(Sender) + End Sub + Private Sub OnGroupDeleted(ByVal Sender As DownloadGroup) + RaiseEvent Deleted(Sender) + Dim i% = GroupsList.FindIndex(Function(g) g.Key = Sender.Key) + If i >= 0 Then + GroupsList(i).Dispose() + GroupsList.RemoveAt(i) + GroupsList.ListReindex + Update() + End If + End Sub + Friend Sub Add() + Using f As New GroupEditorForm(Nothing) + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then + GroupsList.Add(f.MyGroup) + GroupsList.ListReindex + RaiseEvent Added(GroupsList.Last) + Update() + End If + End Using + End Sub + Friend Function DownloadGroupIfExists(ByVal Index As Integer) As Boolean + If Index.ValueBetween(0, Count - 1) Then Item(Index).DownloadUsers(True) : Return True Else Return False + End Function + Private Function GetEnumerator() As IEnumerator(Of DownloadGroup) Implements IEnumerable(Of DownloadGroup).GetEnumerator + Return New MyEnumerator(Of DownloadGroup)(Me) + End Function + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Return GetEnumerator() + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Download/Groups/GroupEditorForm.Designer.vb b/SCrawler/Download/Groups/GroupEditorForm.Designer.vb new file mode 100644 index 0000000..9100646 --- /dev/null +++ b/SCrawler/Download/Groups/GroupEditorForm.Designer.vb @@ -0,0 +1,253 @@ +' Copyright (C) 2022 Andy +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace DownloadObjects.Groups + + Partial Friend Class GroupEditorForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim ActionButton7 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(GroupEditorForm)) + Dim ActionButton8 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton9 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim TP_TEMP_FAV As System.Windows.Forms.TableLayoutPanel + Dim TP_READY_FOR_DOWN As System.Windows.Forms.TableLayoutPanel + Me.TXT_NAME = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TXT_LABELS = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.CH_TEMPORARY = New System.Windows.Forms.CheckBox() + Me.CH_FAV = New System.Windows.Forms.CheckBox() + Me.CH_READY_FOR_DOWN = New System.Windows.Forms.CheckBox() + Me.CH_READY_FOR_DOWN_IGNORE = New System.Windows.Forms.CheckBox() + CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_TEMP_FAV = New System.Windows.Forms.TableLayoutPanel() + TP_READY_FOR_DOWN = New System.Windows.Forms.TableLayoutPanel() + CONTAINER_MAIN.ContentPanel.SuspendLayout() + CONTAINER_MAIN.SuspendLayout() + TP_MAIN.SuspendLayout() + CType(Me.TXT_NAME, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_LABELS, System.ComponentModel.ISupportInitialize).BeginInit() + TP_TEMP_FAV.SuspendLayout() + TP_READY_FOR_DOWN.SuspendLayout() + Me.SuspendLayout() + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(476, 111) + CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + CONTAINER_MAIN.LeftToolStripPanelVisible = False + CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + CONTAINER_MAIN.Name = "CONTAINER_MAIN" + CONTAINER_MAIN.RightToolStripPanelVisible = False + CONTAINER_MAIN.Size = New System.Drawing.Size(476, 136) + CONTAINER_MAIN.TabIndex = 0 + CONTAINER_MAIN.TopToolStripPanelVisible = False + ' + 'TP_MAIN + ' + TP_MAIN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Controls.Add(TP_READY_FOR_DOWN, 0, 2) + TP_MAIN.Controls.Add(Me.TXT_NAME, 0, 0) + TP_MAIN.Controls.Add(Me.TXT_LABELS, 0, 3) + TP_MAIN.Controls.Add(TP_TEMP_FAV, 0, 1) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 5 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_MAIN.Size = New System.Drawing.Size(476, 111) + TP_MAIN.TabIndex = 0 + ' + 'TXT_NAME + ' + ActionButton7.BackgroundImage = CType(resources.GetObject("ActionButton7.BackgroundImage"), System.Drawing.Image) + ActionButton7.Index = 0 + ActionButton7.Name = "BTT_CLEAR" + Me.TXT_NAME.Buttons.Add(ActionButton7) + Me.TXT_NAME.CaptionText = "Name" + Me.TXT_NAME.CaptionToolTipEnabled = True + Me.TXT_NAME.CaptionToolTipText = "Group name" + Me.TXT_NAME.CaptionWidth = 50.0R + Me.TXT_NAME.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_NAME.Location = New System.Drawing.Point(4, 4) + Me.TXT_NAME.Name = "TXT_NAME" + Me.TXT_NAME.Size = New System.Drawing.Size(468, 22) + Me.TXT_NAME.TabIndex = 0 + ' + 'TXT_LABELS + ' + ActionButton8.BackgroundImage = CType(resources.GetObject("ActionButton8.BackgroundImage"), System.Drawing.Image) + ActionButton8.Index = 0 + ActionButton8.Name = "BTT_EDIT" + ActionButton9.BackgroundImage = CType(resources.GetObject("ActionButton9.BackgroundImage"), System.Drawing.Image) + ActionButton9.Index = 1 + ActionButton9.Name = "BTT_CLEAR" + Me.TXT_LABELS.Buttons.Add(ActionButton8) + Me.TXT_LABELS.Buttons.Add(ActionButton9) + Me.TXT_LABELS.CaptionText = "Labels" + Me.TXT_LABELS.CaptionToolTipEnabled = True + Me.TXT_LABELS.CaptionToolTipText = "Group labels" + Me.TXT_LABELS.CaptionWidth = 50.0R + Me.TXT_LABELS.ClearTextByButtonClear = False + Me.TXT_LABELS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_LABELS.Location = New System.Drawing.Point(4, 85) + Me.TXT_LABELS.Name = "TXT_LABELS" + Me.TXT_LABELS.Size = New System.Drawing.Size(468, 22) + Me.TXT_LABELS.TabIndex = 1 + ' + 'TP_TEMP_FAV + ' + TP_TEMP_FAV.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_TEMP_FAV.ColumnCount = 2 + TP_TEMP_FAV.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_TEMP_FAV.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_TEMP_FAV.Controls.Add(Me.CH_TEMPORARY, 0, 0) + TP_TEMP_FAV.Controls.Add(Me.CH_FAV, 1, 0) + TP_TEMP_FAV.Dock = System.Windows.Forms.DockStyle.Fill + TP_TEMP_FAV.Location = New System.Drawing.Point(1, 30) + TP_TEMP_FAV.Margin = New System.Windows.Forms.Padding(0) + TP_TEMP_FAV.Name = "TP_TEMP_FAV" + TP_TEMP_FAV.RowCount = 1 + TP_TEMP_FAV.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_TEMP_FAV.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_TEMP_FAV.Size = New System.Drawing.Size(474, 25) + TP_TEMP_FAV.TabIndex = 2 + ' + 'TP_READY_FOR_DOWN + ' + TP_READY_FOR_DOWN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_READY_FOR_DOWN.ColumnCount = 2 + TP_READY_FOR_DOWN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_READY_FOR_DOWN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_READY_FOR_DOWN.Controls.Add(Me.CH_READY_FOR_DOWN, 0, 0) + TP_READY_FOR_DOWN.Controls.Add(Me.CH_READY_FOR_DOWN_IGNORE, 1, 0) + TP_READY_FOR_DOWN.Dock = System.Windows.Forms.DockStyle.Fill + TP_READY_FOR_DOWN.Location = New System.Drawing.Point(1, 56) + TP_READY_FOR_DOWN.Margin = New System.Windows.Forms.Padding(0) + TP_READY_FOR_DOWN.Name = "TP_READY_FOR_DOWN" + TP_READY_FOR_DOWN.RowCount = 1 + TP_READY_FOR_DOWN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_READY_FOR_DOWN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_READY_FOR_DOWN.Size = New System.Drawing.Size(474, 25) + TP_READY_FOR_DOWN.TabIndex = 3 + ' + 'CH_TEMPORARY + ' + Me.CH_TEMPORARY.AutoSize = True + Me.CH_TEMPORARY.Checked = True + Me.CH_TEMPORARY.CheckState = System.Windows.Forms.CheckState.Indeterminate + Me.CH_TEMPORARY.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_TEMPORARY.Location = New System.Drawing.Point(4, 4) + Me.CH_TEMPORARY.Name = "CH_TEMPORARY" + Me.CH_TEMPORARY.Size = New System.Drawing.Size(229, 17) + Me.CH_TEMPORARY.TabIndex = 0 + Me.CH_TEMPORARY.Text = "Temporary" + Me.CH_TEMPORARY.ThreeState = True + Me.CH_TEMPORARY.UseVisualStyleBackColor = True + ' + 'CH_FAV + ' + Me.CH_FAV.AutoSize = True + Me.CH_FAV.Checked = True + Me.CH_FAV.CheckState = System.Windows.Forms.CheckState.Indeterminate + Me.CH_FAV.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_FAV.Location = New System.Drawing.Point(240, 4) + Me.CH_FAV.Name = "CH_FAV" + Me.CH_FAV.Size = New System.Drawing.Size(230, 17) + Me.CH_FAV.TabIndex = 1 + Me.CH_FAV.Text = "Favorite" + Me.CH_FAV.ThreeState = True + Me.CH_FAV.UseVisualStyleBackColor = True + ' + 'CH_READY_FOR_DOWN + ' + Me.CH_READY_FOR_DOWN.AutoSize = True + Me.CH_READY_FOR_DOWN.Checked = True + Me.CH_READY_FOR_DOWN.CheckState = System.Windows.Forms.CheckState.Checked + Me.CH_READY_FOR_DOWN.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_READY_FOR_DOWN.Location = New System.Drawing.Point(4, 4) + Me.CH_READY_FOR_DOWN.Name = "CH_READY_FOR_DOWN" + Me.CH_READY_FOR_DOWN.Size = New System.Drawing.Size(229, 17) + Me.CH_READY_FOR_DOWN.TabIndex = 0 + Me.CH_READY_FOR_DOWN.Text = "Ready for download" + Me.CH_READY_FOR_DOWN.UseVisualStyleBackColor = True + ' + 'CH_READY_FOR_DOWN_IGNORE + ' + Me.CH_READY_FOR_DOWN_IGNORE.AutoSize = True + Me.CH_READY_FOR_DOWN_IGNORE.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_READY_FOR_DOWN_IGNORE.Location = New System.Drawing.Point(240, 4) + Me.CH_READY_FOR_DOWN_IGNORE.Name = "CH_READY_FOR_DOWN_IGNORE" + Me.CH_READY_FOR_DOWN_IGNORE.Size = New System.Drawing.Size(230, 17) + Me.CH_READY_FOR_DOWN_IGNORE.TabIndex = 1 + Me.CH_READY_FOR_DOWN_IGNORE.Text = "Ignore ready for download" + Me.CH_READY_FOR_DOWN_IGNORE.UseVisualStyleBackColor = True + ' + 'GroupEditorForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(476, 136) + Me.Controls.Add(CONTAINER_MAIN) + Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle + Me.KeyPreview = True + Me.MaximizeBox = False + Me.MaximumSize = New System.Drawing.Size(492, 175) + Me.MinimizeBox = False + Me.MinimumSize = New System.Drawing.Size(492, 175) + Me.Name = "GroupEditorForm" + Me.ShowIcon = False + Me.ShowInTaskbar = False + Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide + Me.Text = "Group" + CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + CONTAINER_MAIN.ResumeLayout(False) + CONTAINER_MAIN.PerformLayout() + TP_MAIN.ResumeLayout(False) + CType(Me.TXT_NAME, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_LABELS, System.ComponentModel.ISupportInitialize).EndInit() + TP_TEMP_FAV.ResumeLayout(False) + TP_TEMP_FAV.PerformLayout() + TP_READY_FOR_DOWN.ResumeLayout(False) + TP_READY_FOR_DOWN.PerformLayout() + Me.ResumeLayout(False) + + End Sub + Private WithEvents TXT_NAME As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_LABELS As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents CH_READY_FOR_DOWN As CheckBox + Private WithEvents CH_READY_FOR_DOWN_IGNORE As CheckBox + Private WithEvents CH_TEMPORARY As CheckBox + Private WithEvents CH_FAV As CheckBox + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Download/Groups/GroupEditorForm.resx b/SCrawler/Download/Groups/GroupEditorForm.resx new file mode 100644 index 0000000..8feac98 --- /dev/null +++ b/SCrawler/Download/Groups/GroupEditorForm.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACH + DwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKOWlDQ1BQaG90b3Nob3AgSUNDIHByb2Zp + bGUAAEjHnZZ3VFTXFofPvXd6oc0w0hl6ky4wgPQuIB0EURhmBhjKAMMMTWyIqEBEEREBRZCggAGjoUis + iGIhKKhgD0gQUGIwiqioZEbWSnx5ee/l5ffHvd/aZ+9z99l7n7UuACRPHy4vBZYCIJkn4Ad6ONNXhUfQ + sf0ABniAAaYAMFnpqb5B7sFAJC83F3q6yAn8i94MAUj8vmXo6U+ng/9P0qxUvgAAyF/E5mxOOkvE+SJO + yhSkiu0zIqbGJIoZRomZL0pQxHJijlvkpZ99FtlRzOxkHlvE4pxT2clsMfeIeHuGkCNixEfEBRlcTqaI + b4tYM0mYzBXxW3FsMoeZDgCKJLYLOKx4EZuImMQPDnQR8XIAcKS4LzjmCxZwsgTiQ7mkpGbzuXHxArou + S49uam3NoHtyMpM4AoGhP5OVyOSz6S4pyalMXjYAi2f+LBlxbemiIluaWltaGpoZmX5RqP+6+Dcl7u0i + vQr43DOI1veH7a/8UuoAYMyKarPrD1vMfgA6tgIgd/8Pm+YhACRFfWu/8cV5aOJ5iRcIUm2MjTMzM424 + HJaRuKC/6386/A198T0j8Xa/l4fuyollCpMEdHHdWClJKUI+PT2VyeLQDf88xP848K/zWBrIieXwOTxR + RKhoyri8OFG7eWyugJvCo3N5/6mJ/zDsT1qca5Eo9Z8ANcoISN2gAuTnPoCiEAESeVDc9d/75oMPBeKb + F6Y6sTj3nwX9+65wifiRzo37HOcSGExnCfkZi2viawnQgAAkARXIAxWgAXSBITADVsAWOAI3sAL4gWAQ + DtYCFogHyYAPMkEu2AwKQBHYBfaCSlAD6kEjaAEnQAc4DS6Ay+A6uAnugAdgBIyD52AGvAHzEARhITJE + geQhVUgLMoDMIAZkD7lBPlAgFA5FQ3EQDxJCudAWqAgqhSqhWqgR+hY6BV2ArkID0D1oFJqCfoXewwhM + gqmwMqwNG8MM2An2hoPhNXAcnAbnwPnwTrgCroOPwe3wBfg6fAcegZ/DswhAiAgNUUMMEQbigvghEUgs + wkc2IIVIOVKHtCBdSC9yCxlBppF3KAyKgqKjDFG2KE9UCIqFSkNtQBWjKlFHUe2oHtQt1ChqBvUJTUYr + oQ3QNmgv9Cp0HDoTXYAuRzeg29CX0HfQ4+g3GAyGhtHBWGE8MeGYBMw6TDHmAKYVcx4zgBnDzGKxWHms + AdYO64dlYgXYAux+7DHsOewgdhz7FkfEqeLMcO64CBwPl4crxzXhzuIGcRO4ebwUXgtvg/fDs/HZ+BJ8 + Pb4LfwM/jp8nSBN0CHaEYEICYTOhgtBCuER4SHhFJBLVidbEACKXuIlYQTxOvEIcJb4jyZD0SS6kSJKQ + tJN0hHSedI/0ikwma5MdyRFkAXknuZF8kfyY/FaCImEk4SXBltgoUSXRLjEo8UISL6kl6SS5VjJHslzy + pOQNyWkpvJS2lIsUU2qDVJXUKalhqVlpirSptJ90snSxdJP0VelJGayMtoybDFsmX+awzEWZMQpC0aC4 + UFiULZR6yiXKOBVD1aF6UROoRdRvqP3UGVkZ2WWyobJZslWyZ2RHaAhNm+ZFS6KV0E7QhmjvlygvcVrC + WbJjScuSwSVzcopyjnIcuUK5Vrk7cu/l6fJu8onyu+U75B8poBT0FQIUMhUOKlxSmFakKtoqshQLFU8o + 3leClfSVApXWKR1W6lOaVVZR9lBOVd6vfFF5WoWm4qiSoFKmclZlSpWiaq/KVS1TPaf6jC5Ld6In0Svo + PfQZNSU1TzWhWq1av9q8uo56iHqeeqv6Iw2CBkMjVqNMo1tjRlNV01czV7NZ874WXouhFa+1T6tXa05b + RztMe5t2h/akjpyOl06OTrPOQ12yroNumm6d7m09jB5DL1HvgN5NfVjfQj9ev0r/hgFsYGnANThgMLAU + vdR6KW9p3dJhQ5Khk2GGYbPhqBHNyMcoz6jD6IWxpnGE8W7jXuNPJhYmSSb1Jg9MZUxXmOaZdpn+aqZv + xjKrMrttTjZ3N99o3mn+cpnBMs6yg8vuWlAsfC22WXRbfLS0suRbtlhOWWlaRVtVWw0zqAx/RjHjijXa + 2tl6o/Vp63c2ljYCmxM2v9ga2ibaNtlOLtdZzllev3zMTt2OaVdrN2JPt4+2P2Q/4qDmwHSoc3jiqOHI + dmxwnHDSc0pwOub0wtnEme/c5jznYuOy3uW8K+Lq4Vro2u8m4xbiVun22F3dPc692X3Gw8Jjncd5T7Sn + t+duz2EvZS+WV6PXzAqrFetX9HiTvIO8K72f+Oj78H26fGHfFb57fB+u1FrJW9nhB/y8/Pb4PfLX8U/z + /z4AE+AfUBXwNNA0MDewN4gSFBXUFPQm2Dm4JPhBiG6IMKQ7VDI0MrQxdC7MNaw0bGSV8ar1q66HK4Rz + wzsjsBGhEQ0Rs6vdVu9dPR5pEVkQObRGZ03WmqtrFdYmrT0TJRnFjDoZjY4Oi26K/sD0Y9YxZ2O8Yqpj + ZlgurH2s52xHdhl7imPHKeVMxNrFlsZOxtnF7YmbineIL4+f5rpwK7kvEzwTahLmEv0SjyQuJIUltSbj + kqOTT/FkeIm8nhSVlKyUgVSD1ILUkTSbtL1pM3xvfkM6lL4mvVNAFf1M9Ql1hVuFoxn2GVUZbzNDM09m + SWfxsvqy9bN3ZE/kuOd8vQ61jrWuO1ctd3Pu6Hqn9bUboA0xG7o3amzM3zi+yWPT0c2EzYmbf8gzySvN + e70lbEtXvnL+pvyxrR5bmwskCvgFw9tst9VsR23nbu/fYb5j/45PhezCa0UmReVFH4pZxde+Mv2q4quF + nbE7+0ssSw7uwuzi7Rra7bD7aKl0aU7p2B7fPe1l9LLCstd7o/ZeLV9WXrOPsE+4b6TCp6Jzv+b+Xfs/ + VMZX3qlyrmqtVqreUT13gH1g8KDjwZYa5ZqimveHuIfu1nrUttdp15UfxhzOOPy0PrS+92vG140NCg1F + DR+P8I6MHA082tNo1djYpNRU0gw3C5unjkUeu/mN6zedLYYtta201qLj4Ljw+LNvo78dOuF9ovsk42TL + d1rfVbdR2grbofbs9pmO+I6RzvDOgVMrTnV32Xa1fW/0/ZHTaqerzsieKTlLOJt/duFczrnZ86nnpy/E + XRjrjup+cHHVxds9AT39l7wvXbnsfvlir1PvuSt2V05ftbl66hrjWsd1y+vtfRZ9bT9Y/NDWb9nffsPq + RudN65tdA8sHzg46DF645Xrr8m2v29fvrLwzMBQydHc4cnjkLvvu5L2key/vZ9yff7DpIfph4SOpR+WP + lR7X/aj3Y+uI5ciZUdfRvidBTx6Mscae/5T+04fx/Kfkp+UTqhONk2aTp6fcp24+W/1s/Hnq8/npgp+l + f65+ofviu18cf+mbWTUz/pL/cuHX4lfyr468Xva6e9Z/9vGb5Dfzc4Vv5d8efcd41/s+7P3EfOYH7IeK + j3ofuz55f3q4kLyw8Bv3hPP74uYdwgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAahJREFUOE9j+P//P8l4 + vaOjPYyNIYkPO1lZsa1wdNy42sHh3Hxb22KQGFaF2LC4qjjroUP7n97s6vx/Ny/3/ypn54+LbGwisSpG + x+aaouwZren/u5f2/3/18tX/qzNn/l/i4XGSgYFBFasGZKwjzcJ6YVnU152blvw3LHH53zCl/ufatWu+ + T+1vDALJY9UEwxrijExHZgd+/Xy1Hcg98BNkCMglMM0gjKEJhuX5GVh2TvD+/O5c0///P9b///qo819P + lgmKZhBG0QTDMjwMzJs7XT+9OVHz///XFf+/PWj7j00zCKNwQFiah4FtXbPjp8d78////7bo/4/79Tg1 + gzAKR1mUg3lOocXbe9uz/v9/M/H/1zuVeDWDMJwhJcDBvK4p4tb1DQn//r/u+f/zRh5BzSAMZyyrdVh9 + c33B9//32159vZr2hxjNIAwm1GUE3e+ur/n9/+Ls/592Nf9fUun3khjNIMzAysTAv6g6+OT/E33/j09N + +zWpMuImsZpBmMHIQK9x19T8/03x1ufE+TkqsCnChxmUlFWuyEpJtAHTtT42BfjxfwYAtlm0ShMkSB4A + AAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + False + + \ No newline at end of file diff --git a/SCrawler/Download/Groups/GroupEditorForm.vb b/SCrawler/Download/Groups/GroupEditorForm.vb new file mode 100644 index 0000000..b5b4e3b --- /dev/null +++ b/SCrawler/Download/Groups/GroupEditorForm.vb @@ -0,0 +1,111 @@ +' Copyright (C) 2022 Andy +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Controls.Base +Imports PersonalUtilities.Forms.Toolbars +Namespace DownloadObjects.Groups + Friend Class GroupEditorForm : Implements IOkCancelToolbar + Private ReadOnly MyDefs As DefaultFormProps + Friend Property MyGroup As DownloadGroup + Private ReadOnly MyLabels As List(Of String) + Friend Sub New(ByRef g As DownloadGroup) + InitializeComponent() + MyGroup = g + MyLabels = New List(Of String) + If Not MyGroup Is Nothing Then MyLabels.ListAddList(MyGroup.Labels) + MyDefs = New DefaultFormProps + End Sub + Private Class NameChecker : Implements IFieldsCheckerProvider + Private Property ErrorMessage As String Implements IFieldsCheckerProvider.ErrorMessage + Private Property Name As String Implements IFieldsCheckerProvider.Name + Private Property TypeError As Boolean Implements IFieldsCheckerProvider.TypeError + Private ReadOnly ExistingGroupName As String + Friend Sub New(ByVal _ExistingGroupName As String) + ExistingGroupName = _ExistingGroupName + End Sub + Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert + If Not ACheck(Value) Then + ErrorMessage = "Group name cannot be empty" + ElseIf Not ExistingGroupName.IsEmptyString AndAlso CStr(Value) = ExistingGroupName Then + Return Value + ElseIf Settings.Groups.Count > 0 AndAlso Settings.Groups.LongCount(Function(g) g.Name = CStr(Value)) > 0 Then + ErrorMessage = "A group with the same name already exists" + Else + Return Value + End If + Return Nothing + End Function + Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat + Throw New NotImplementedException("GetFormat is not available in this context") + End Function + End Class + Private Sub GroupEditorForm_Load(sender As Object, e As EventArgs) Handles Me.Load + With MyDefs + .MyViewInitialize(Me, Settings.Design, True) + .AddOkCancelToolbar() + .DelegateClosingChecker() + If Not MyGroup Is Nothing Then + With MyGroup + TXT_NAME.Text = .Name + CH_TEMPORARY.CheckState = .Temporary + CH_FAV.CheckState = .Favorite + CH_READY_FOR_DOWN.Checked = .ReadyForDownload + CH_READY_FOR_DOWN_IGNORE.Checked = .ReadyForDownloadIgnore + TXT_LABELS.Text = MyLabels.ListToString + Text &= $" { .Name}" + End With + Else + Text = "New Group" + End If + .MyFieldsChecker = New FieldsChecker + DirectCast(.MyFieldsChecker, FieldsChecker).AddControl(Of String)(TXT_NAME, TXT_NAME.CaptionText,, New NameChecker(If(MyGroup?.Name, String.Empty))) + .MyFieldsChecker.EndLoaderOperations() + .AppendDetectors() + .EndLoaderOperations() + End With + End Sub + Private Sub GroupEditorForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + MyLabels.Clear() + End Sub + Private Sub ToolbarBttOK() Implements IOkCancelToolbar.ToolbarBttOK + If MyDefs.MyFieldsChecker.AllParamsOK Then + If MyGroup Is Nothing Then MyGroup = New DownloadGroup + With MyGroup + .Name = TXT_NAME.Text + .Temporary = CH_TEMPORARY.CheckState + .Favorite = CH_FAV.CheckState + .ReadyForDownload = CH_READY_FOR_DOWN.Checked + .ReadyForDownloadIgnore = CH_READY_FOR_DOWN_IGNORE.Checked + .Labels.ListAddList(MyLabels, LAP.ClearBeforeAdd, LAP.NotContainsOnly) + End With + MyDefs.CloseForm() + End If + End Sub + Private Sub ToolbarBttCancel() Implements IOkCancelToolbar.ToolbarBttCancel + MyDefs.CloseForm(DialogResult.Cancel) + End Sub + Private Sub TXT_LABELS_ActionOnButtonClick(ByVal Sender As ActionButton) Handles TXT_LABELS.ActionOnButtonClick + Select Case Sender.DefaultButton + Case ActionButton.DefaultButtons.Edit + Using f As New LabelsForm(MyLabels) + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then + MyLabels.ListAddList(f.LabelsList, LAP.NotContainsOnly, LAP.ClearBeforeAdd) + TXT_LABELS.Clear() + TXT_LABELS.Text = MyLabels.ListToString + End If + End Using + Case ActionButton.DefaultButtons.Clear + MyLabels.Clear() + TXT_LABELS.Clear() + End Select + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Download/TDownloader.vb b/SCrawler/Download/TDownloader.vb index 2e4881a..09b94f2 100644 --- a/SCrawler/Download/TDownloader.vb +++ b/SCrawler/Download/TDownloader.vb @@ -206,14 +206,15 @@ Namespace DownloadObjects End Sub Private CheckerThread As Thread Private Sub [Start]() - If Not MyProgressForm.Opened AndAlso Pool.LongCount(Function(p) p.Count > 0) > 1 Then MyProgressForm.Show() + If MyProgressForm.ReadyToOpen AndAlso Pool.LongCount(Function(p) p.Count > 0) > 1 Then MyProgressForm.Show() : MainFrameObj.Focus() If Not If(CheckerThread?.IsAlive, False) Then MainProgress.Enabled = True + If InfoForm.ReadyToOpen Then InfoForm.Show() : MainFrameObj.Focus() CheckerThread = New Thread(New ThreadStart(AddressOf JobsChecker)) CheckerThread.SetApartmentState(ApartmentState.MTA) CheckerThread.Start() End If - End Sub + End Sub Private Sub JobsChecker() Try MainProgress.TotalCount = 0 @@ -235,6 +236,7 @@ Namespace DownloadObjects End With MyProgressForm.DisableProgressChange = True If Pool.Count > 0 Then Pool.ForEach(Sub(p) If Not p.Progress Is Nothing Then p.Progress.TotalCount = 0) + ExecuteCommand(Settings.DownloadsCompleteCommand) End Try End Sub Private Sub StartDownloading(ByRef _Job As Job) diff --git a/SCrawler/Download/VideosDownloaderForm.vb b/SCrawler/Download/VideosDownloaderForm.vb index 21fa905..0f5eb42 100644 --- a/SCrawler/Download/VideosDownloaderForm.vb +++ b/SCrawler/Download/VideosDownloaderForm.vb @@ -63,7 +63,7 @@ Namespace DownloadObjects End Sub Private Sub UpdateUrlsFile() If UrlList.Count > 0 Then - TextSaver.SaveTextToFile(UrlList.ListToString(, Environment.NewLine), DownloadingUrlsFile, True,, EDP.SendInLog) + TextSaver.SaveTextToFile(UrlList.ListToString(Environment.NewLine), DownloadingUrlsFile, True,, EDP.SendInLog) Else DownloadingUrlsFile.Delete(, Settings.DeleteMode, EDP.SendInLog) End If diff --git a/SCrawler/Editors/GlobalSettingsForm.Designer.vb b/SCrawler/Editors/GlobalSettingsForm.Designer.vb index 07465ca..24f9080 100644 --- a/SCrawler/Editors/GlobalSettingsForm.Designer.vb +++ b/SCrawler/Editors/GlobalSettingsForm.Designer.vb @@ -41,6 +41,8 @@ Dim TP_DOWNLOADING As System.Windows.Forms.TableLayoutPanel Dim ActionButton8 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() Dim ActionButton9 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim TP_OPEN_INFO As System.Windows.Forms.TableLayoutPanel + Dim TP_OPEN_PROGRESS As System.Windows.Forms.TableLayoutPanel Me.TXT_GLOBAL_PATH = New PersonalUtilities.Forms.Controls.TextBoxExtended() Me.TXT_IMAGE_LARGE = New PersonalUtilities.Forms.Controls.TextBoxExtended() Me.TXT_IMAGE_SMALL = New PersonalUtilities.Forms.Controls.TextBoxExtended() @@ -75,9 +77,15 @@ Me.CH_CLOSE_TO_TRAY = New System.Windows.Forms.CheckBox() Me.CH_SHOW_NOTIFY = New System.Windows.Forms.CheckBox() Me.CH_RECYCLE_DEL = New System.Windows.Forms.CheckBox() + Me.CH_DOWN_OPEN_INFO = New System.Windows.Forms.CheckBox() + Me.CH_DOWN_OPEN_PROGRESS = New System.Windows.Forms.CheckBox() + Me.TXT_CLOSE_SCRIPT = New PersonalUtilities.Forms.Controls.TextBoxExtended() Me.TXT_SCRIPT = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TXT_DOWN_COMPLETE_SCRIPT = New PersonalUtilities.Forms.Controls.TextBoxExtended() Me.TAB_MAIN = New System.Windows.Forms.TabControl() Me.CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + Me.CH_DOWN_OPEN_INFO_SUSPEND = New System.Windows.Forms.CheckBox() + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND = New System.Windows.Forms.CheckBox() TP_BASIS = New System.Windows.Forms.TableLayoutPanel() TP_IMAGES = New System.Windows.Forms.TableLayoutPanel() TP_FILE_NAME = New System.Windows.Forms.TableLayoutPanel() @@ -94,6 +102,8 @@ TP_BEHAVIOR = New System.Windows.Forms.TableLayoutPanel() TAB_DOWN = New System.Windows.Forms.TabPage() TP_DOWNLOADING = New System.Windows.Forms.TableLayoutPanel() + TP_OPEN_INFO = New System.Windows.Forms.TableLayoutPanel() + TP_OPEN_PROGRESS = New System.Windows.Forms.TableLayoutPanel() TP_BASIS.SuspendLayout() CType(Me.TXT_GLOBAL_PATH, System.ComponentModel.ISupportInitialize).BeginInit() TP_IMAGES.SuspendLayout() @@ -117,12 +127,16 @@ TAB_BEHAVIOR.SuspendLayout() TP_BEHAVIOR.SuspendLayout() CType(Me.TXT_FOLDER_CMD, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_CLOSE_SCRIPT, System.ComponentModel.ISupportInitialize).BeginInit() TAB_DOWN.SuspendLayout() TP_DOWNLOADING.SuspendLayout() CType(Me.TXT_SCRIPT, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_DOWN_COMPLETE_SCRIPT, System.ComponentModel.ISupportInitialize).BeginInit() Me.TAB_MAIN.SuspendLayout() Me.CONTAINER_MAIN.ContentPanel.SuspendLayout() Me.CONTAINER_MAIN.SuspendLayout() + TP_OPEN_INFO.SuspendLayout() + TP_OPEN_PROGRESS.SuspendLayout() Me.SuspendLayout() ' 'TP_BASIS @@ -633,7 +647,7 @@ TAB_DEFAULTS.Location = New System.Drawing.Point(4, 22) TAB_DEFAULTS.Name = "TAB_DEFAULTS" TAB_DEFAULTS.Padding = New System.Windows.Forms.Padding(3) - TAB_DEFAULTS.Size = New System.Drawing.Size(576, 335) + TAB_DEFAULTS.Size = New System.Drawing.Size(576, 284) TAB_DEFAULTS.TabIndex = 1 TAB_DEFAULTS.Text = "Defaults" ' @@ -656,7 +670,7 @@ TP_DEFS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_DEFS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) TP_DEFS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) - TP_DEFS.Size = New System.Drawing.Size(570, 329) + TP_DEFS.Size = New System.Drawing.Size(570, 278) TP_DEFS.TabIndex = 0 ' 'TAB_DEFS_CHANNELS @@ -665,7 +679,7 @@ TAB_DEFS_CHANNELS.Location = New System.Drawing.Point(4, 22) TAB_DEFS_CHANNELS.Name = "TAB_DEFS_CHANNELS" TAB_DEFS_CHANNELS.Padding = New System.Windows.Forms.Padding(3) - TAB_DEFS_CHANNELS.Size = New System.Drawing.Size(576, 335) + TAB_DEFS_CHANNELS.Size = New System.Drawing.Size(576, 284) TAB_DEFS_CHANNELS.TabIndex = 4 TAB_DEFS_CHANNELS.Text = "Channels" ' @@ -689,7 +703,7 @@ TP_CHANNELS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_CHANNELS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_CHANNELS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_CHANNELS.Size = New System.Drawing.Size(570, 329) + TP_CHANNELS.Size = New System.Drawing.Size(570, 278) TP_CHANNELS.TabIndex = 0 ' 'TXT_CHANNEL_USER_POST_LIMIT @@ -717,7 +731,7 @@ TAB_BEHAVIOR.Controls.Add(TP_BEHAVIOR) TAB_BEHAVIOR.Location = New System.Drawing.Point(4, 22) TAB_BEHAVIOR.Name = "TAB_BEHAVIOR" - TAB_BEHAVIOR.Size = New System.Drawing.Size(576, 335) + TAB_BEHAVIOR.Size = New System.Drawing.Size(576, 284) TAB_BEHAVIOR.TabIndex = 5 TAB_BEHAVIOR.Text = "Behavior" ' @@ -726,25 +740,31 @@ TP_BEHAVIOR.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] TP_BEHAVIOR.ColumnCount = 1 TP_BEHAVIOR.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_BEHAVIOR.Controls.Add(Me.TXT_FOLDER_CMD, 0, 5) + TP_BEHAVIOR.Controls.Add(Me.TXT_FOLDER_CMD, 0, 7) TP_BEHAVIOR.Controls.Add(Me.CH_EXIT_CONFIRM, 0, 0) TP_BEHAVIOR.Controls.Add(Me.CH_CLOSE_TO_TRAY, 0, 1) TP_BEHAVIOR.Controls.Add(Me.CH_SHOW_NOTIFY, 0, 2) TP_BEHAVIOR.Controls.Add(Me.CH_FAST_LOAD, 0, 3) + TP_BEHAVIOR.Controls.Add(Me.TXT_CLOSE_SCRIPT, 0, 8) + TP_BEHAVIOR.Controls.Add(TP_OPEN_INFO, 0, 5) TP_BEHAVIOR.Controls.Add(Me.CH_RECYCLE_DEL, 0, 4) + TP_BEHAVIOR.Controls.Add(TP_OPEN_PROGRESS, 0, 6) TP_BEHAVIOR.Dock = System.Windows.Forms.DockStyle.Fill TP_BEHAVIOR.Location = New System.Drawing.Point(0, 0) TP_BEHAVIOR.Name = "TP_BEHAVIOR" - TP_BEHAVIOR.RowCount = 7 + TP_BEHAVIOR.RowCount = 11 + TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_BEHAVIOR.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) - TP_BEHAVIOR.Size = New System.Drawing.Size(576, 335) + TP_BEHAVIOR.Size = New System.Drawing.Size(576, 284) TP_BEHAVIOR.TabIndex = 0 ' 'TXT_FOLDER_CMD @@ -762,12 +782,12 @@ Me.TXT_FOLDER_CMD.CaptionToolTipText = "The command to open a folder." Me.TXT_FOLDER_CMD.Dock = System.Windows.Forms.DockStyle.Fill Me.TXT_FOLDER_CMD.LeaveDefaultButtons = True - Me.TXT_FOLDER_CMD.Location = New System.Drawing.Point(4, 134) + Me.TXT_FOLDER_CMD.Location = New System.Drawing.Point(4, 186) Me.TXT_FOLDER_CMD.Name = "TXT_FOLDER_CMD" Me.TXT_FOLDER_CMD.PlaceholderEnabled = True Me.TXT_FOLDER_CMD.PlaceholderText = "MyCommand /arg {0}" Me.TXT_FOLDER_CMD.Size = New System.Drawing.Size(568, 22) - Me.TXT_FOLDER_CMD.TabIndex = 5 + Me.TXT_FOLDER_CMD.TabIndex = 7 ' 'CH_EXIT_CONFIRM ' @@ -813,12 +833,48 @@ Me.CH_RECYCLE_DEL.Text = "Delete data to recycle bin" Me.CH_RECYCLE_DEL.UseVisualStyleBackColor = True ' + 'CH_DOWN_OPEN_INFO + ' + Me.CH_DOWN_OPEN_INFO.AutoSize = True + Me.CH_DOWN_OPEN_INFO.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_DOWN_OPEN_INFO.Location = New System.Drawing.Point(4, 4) + Me.CH_DOWN_OPEN_INFO.Name = "CH_DOWN_OPEN_INFO" + Me.CH_DOWN_OPEN_INFO.Size = New System.Drawing.Size(279, 17) + Me.CH_DOWN_OPEN_INFO.TabIndex = 0 + Me.CH_DOWN_OPEN_INFO.Text = "Open the 'Info' form when the download starts" + Me.CH_DOWN_OPEN_INFO.UseVisualStyleBackColor = True + ' + 'CH_DOWN_OPEN_PROGRESS + ' + Me.CH_DOWN_OPEN_PROGRESS.AutoSize = True + Me.CH_DOWN_OPEN_PROGRESS.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_DOWN_OPEN_PROGRESS.Location = New System.Drawing.Point(4, 4) + Me.CH_DOWN_OPEN_PROGRESS.Name = "CH_DOWN_OPEN_PROGRESS" + Me.CH_DOWN_OPEN_PROGRESS.Size = New System.Drawing.Size(279, 17) + Me.CH_DOWN_OPEN_PROGRESS.TabIndex = 0 + Me.CH_DOWN_OPEN_PROGRESS.Text = "Open the 'Progress' form when the download starts" + Me.CH_DOWN_OPEN_PROGRESS.UseVisualStyleBackColor = True + ' + 'TXT_CLOSE_SCRIPT + ' + Me.TXT_CLOSE_SCRIPT.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.CheckBox + Me.TXT_CLOSE_SCRIPT.CaptionText = "Close cmd" + Me.TXT_CLOSE_SCRIPT.CaptionToolTipEnabled = True + Me.TXT_CLOSE_SCRIPT.CaptionToolTipText = "This command will be executed when SCrawler is closed" + Me.TXT_CLOSE_SCRIPT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_CLOSE_SCRIPT.Location = New System.Drawing.Point(4, 215) + Me.TXT_CLOSE_SCRIPT.Name = "TXT_CLOSE_SCRIPT" + Me.TXT_CLOSE_SCRIPT.PlaceholderEnabled = True + Me.TXT_CLOSE_SCRIPT.PlaceholderText = "Enter command here..." + Me.TXT_CLOSE_SCRIPT.Size = New System.Drawing.Size(568, 22) + Me.TXT_CLOSE_SCRIPT.TabIndex = 8 + ' 'TAB_DOWN ' TAB_DOWN.Controls.Add(TP_DOWNLOADING) TAB_DOWN.Location = New System.Drawing.Point(4, 22) TAB_DOWN.Name = "TAB_DOWN" - TAB_DOWN.Size = New System.Drawing.Size(576, 335) + TAB_DOWN.Size = New System.Drawing.Size(576, 284) TAB_DOWN.TabIndex = 6 TAB_DOWN.Text = "Downloading" ' @@ -831,16 +887,18 @@ TP_DOWNLOADING.Controls.Add(TP_FILE_PATTERNS, 0, 2) TP_DOWNLOADING.Controls.Add(Me.TXT_SCRIPT, 0, 3) TP_DOWNLOADING.Controls.Add(Me.CH_UDESCR_UP, 0, 0) + TP_DOWNLOADING.Controls.Add(Me.TXT_DOWN_COMPLETE_SCRIPT, 0, 4) TP_DOWNLOADING.Dock = System.Windows.Forms.DockStyle.Fill TP_DOWNLOADING.Location = New System.Drawing.Point(0, 0) TP_DOWNLOADING.Name = "TP_DOWNLOADING" - TP_DOWNLOADING.RowCount = 5 + TP_DOWNLOADING.RowCount = 6 TP_DOWNLOADING.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_DOWNLOADING.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30.0!)) TP_DOWNLOADING.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30.0!)) TP_DOWNLOADING.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_DOWNLOADING.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) TP_DOWNLOADING.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_DOWNLOADING.Size = New System.Drawing.Size(576, 335) + TP_DOWNLOADING.Size = New System.Drawing.Size(576, 284) TP_DOWNLOADING.TabIndex = 0 ' 'TXT_SCRIPT @@ -867,6 +925,21 @@ Me.TXT_SCRIPT.Size = New System.Drawing.Size(568, 22) Me.TXT_SCRIPT.TabIndex = 3 ' + 'TXT_DOWN_COMPLETE_SCRIPT + ' + Me.TXT_DOWN_COMPLETE_SCRIPT.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.CheckBox + Me.TXT_DOWN_COMPLETE_SCRIPT.CaptionText = "After download cmd" + Me.TXT_DOWN_COMPLETE_SCRIPT.CaptionToolTipEnabled = True + Me.TXT_DOWN_COMPLETE_SCRIPT.CaptionToolTipText = "This command will be executed after all downloads are completed" + Me.TXT_DOWN_COMPLETE_SCRIPT.CaptionWidth = 120.0R + Me.TXT_DOWN_COMPLETE_SCRIPT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_DOWN_COMPLETE_SCRIPT.Location = New System.Drawing.Point(4, 121) + Me.TXT_DOWN_COMPLETE_SCRIPT.Name = "TXT_DOWN_COMPLETE_SCRIPT" + Me.TXT_DOWN_COMPLETE_SCRIPT.PlaceholderEnabled = True + Me.TXT_DOWN_COMPLETE_SCRIPT.PlaceholderText = "Enter command here..." + Me.TXT_DOWN_COMPLETE_SCRIPT.Size = New System.Drawing.Size(568, 22) + Me.TXT_DOWN_COMPLETE_SCRIPT.TabIndex = 4 + ' 'TAB_MAIN ' Me.TAB_MAIN.Controls.Add(TAB_BASIS) @@ -878,7 +951,7 @@ Me.TAB_MAIN.Location = New System.Drawing.Point(0, 0) Me.TAB_MAIN.Name = "TAB_MAIN" Me.TAB_MAIN.SelectedIndex = 0 - Me.TAB_MAIN.Size = New System.Drawing.Size(584, 285) + Me.TAB_MAIN.Size = New System.Drawing.Size(584, 310) Me.TAB_MAIN.TabIndex = 1 ' 'CONTAINER_MAIN @@ -887,7 +960,7 @@ 'CONTAINER_MAIN.ContentPanel ' Me.CONTAINER_MAIN.ContentPanel.Controls.Add(Me.TAB_MAIN) - Me.CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(584, 285) + Me.CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(584, 310) Me.CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill Me.CONTAINER_MAIN.LeftToolStripPanelVisible = False Me.CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) @@ -897,6 +970,66 @@ Me.CONTAINER_MAIN.TabIndex = 0 Me.CONTAINER_MAIN.TopToolStripPanelVisible = False ' + 'TP_OPEN_INFO + ' + TP_OPEN_INFO.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_OPEN_INFO.ColumnCount = 2 + TP_OPEN_INFO.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_OPEN_INFO.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_OPEN_INFO.Controls.Add(Me.CH_DOWN_OPEN_INFO, 0, 0) + TP_OPEN_INFO.Controls.Add(Me.CH_DOWN_OPEN_INFO_SUSPEND, 1, 0) + TP_OPEN_INFO.Dock = System.Windows.Forms.DockStyle.Fill + TP_OPEN_INFO.Location = New System.Drawing.Point(1, 131) + TP_OPEN_INFO.Margin = New System.Windows.Forms.Padding(0) + TP_OPEN_INFO.Name = "TP_OPEN_INFO" + TP_OPEN_INFO.RowCount = 1 + TP_OPEN_INFO.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OPEN_INFO.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_OPEN_INFO.Size = New System.Drawing.Size(574, 25) + TP_OPEN_INFO.TabIndex = 5 + ' + 'TP_OPEN_PROGRESS + ' + TP_OPEN_PROGRESS.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_OPEN_PROGRESS.ColumnCount = 2 + TP_OPEN_PROGRESS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_OPEN_PROGRESS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_OPEN_PROGRESS.Controls.Add(Me.CH_DOWN_OPEN_PROGRESS, 0, 0) + TP_OPEN_PROGRESS.Controls.Add(Me.CH_DOWN_OPEN_PROGRESS_SUSPEND, 1, 0) + TP_OPEN_PROGRESS.Dock = System.Windows.Forms.DockStyle.Fill + TP_OPEN_PROGRESS.Location = New System.Drawing.Point(1, 157) + TP_OPEN_PROGRESS.Margin = New System.Windows.Forms.Padding(0) + TP_OPEN_PROGRESS.Name = "TP_OPEN_PROGRESS" + TP_OPEN_PROGRESS.RowCount = 1 + TP_OPEN_PROGRESS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OPEN_PROGRESS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_OPEN_PROGRESS.Size = New System.Drawing.Size(574, 25) + TP_OPEN_PROGRESS.TabIndex = 6 + ' + 'CH_DOWN_OPEN_INFO_SUSPEND + ' + Me.CH_DOWN_OPEN_INFO_SUSPEND.AutoSize = True + Me.CH_DOWN_OPEN_INFO_SUSPEND.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_DOWN_OPEN_INFO_SUSPEND.Location = New System.Drawing.Point(290, 4) + Me.CH_DOWN_OPEN_INFO_SUSPEND.Name = "CH_DOWN_OPEN_INFO_SUSPEND" + Me.CH_DOWN_OPEN_INFO_SUSPEND.Size = New System.Drawing.Size(280, 17) + Me.CH_DOWN_OPEN_INFO_SUSPEND.TabIndex = 1 + Me.CH_DOWN_OPEN_INFO_SUSPEND.Text = "Don't open again" + TT_MAIN.SetToolTip(Me.CH_DOWN_OPEN_INFO_SUSPEND, "Do not open the form automatically if it was once closed") + Me.CH_DOWN_OPEN_INFO_SUSPEND.UseVisualStyleBackColor = True + ' + 'CH_DOWN_OPEN_PROGRESS_SUSPEND + ' + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.AutoSize = True + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.Location = New System.Drawing.Point(290, 4) + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.Name = "CH_DOWN_OPEN_PROGRESS_SUSPEND" + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.Size = New System.Drawing.Size(280, 17) + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.TabIndex = 1 + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.Text = "Don't open again" + TT_MAIN.SetToolTip(Me.CH_DOWN_OPEN_PROGRESS_SUSPEND, "Do not open the form automatically if it was once closed") + Me.CH_DOWN_OPEN_PROGRESS_SUSPEND.UseVisualStyleBackColor = True + ' 'GlobalSettingsForm ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) @@ -943,14 +1076,20 @@ TP_BEHAVIOR.ResumeLayout(False) TP_BEHAVIOR.PerformLayout() CType(Me.TXT_FOLDER_CMD, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_CLOSE_SCRIPT, System.ComponentModel.ISupportInitialize).EndInit() TAB_DOWN.ResumeLayout(False) TP_DOWNLOADING.ResumeLayout(False) TP_DOWNLOADING.PerformLayout() CType(Me.TXT_SCRIPT, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_DOWN_COMPLETE_SCRIPT, System.ComponentModel.ISupportInitialize).EndInit() Me.TAB_MAIN.ResumeLayout(False) Me.CONTAINER_MAIN.ContentPanel.ResumeLayout(False) Me.CONTAINER_MAIN.ResumeLayout(False) Me.CONTAINER_MAIN.PerformLayout() + TP_OPEN_INFO.ResumeLayout(False) + TP_OPEN_INFO.PerformLayout() + TP_OPEN_PROGRESS.ResumeLayout(False) + TP_OPEN_PROGRESS.PerformLayout() Me.ResumeLayout(False) End Sub @@ -992,5 +1131,11 @@ Private WithEvents TXT_SCRIPT As PersonalUtilities.Forms.Controls.TextBoxExtended Private WithEvents CH_SHOW_GROUPS As CheckBox Private WithEvents CH_USERS_GROUPING As CheckBox + Private WithEvents CH_DOWN_OPEN_INFO As CheckBox + Private WithEvents CH_DOWN_OPEN_PROGRESS As CheckBox + Private WithEvents TXT_CLOSE_SCRIPT As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_DOWN_COMPLETE_SCRIPT As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents CH_DOWN_OPEN_INFO_SUSPEND As CheckBox + Private WithEvents CH_DOWN_OPEN_PROGRESS_SUSPEND As CheckBox End Class End Namespace \ No newline at end of file diff --git a/SCrawler/Editors/GlobalSettingsForm.resx b/SCrawler/Editors/GlobalSettingsForm.resx index 0be48c3..f236247 100644 --- a/SCrawler/Editors/GlobalSettingsForm.resx +++ b/SCrawler/Editors/GlobalSettingsForm.resx @@ -243,6 +243,12 @@ If checked, videos will be stored in separate folder; otherwise, videos will be AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + False + + + False + False diff --git a/SCrawler/Editors/GlobalSettingsForm.vb b/SCrawler/Editors/GlobalSettingsForm.vb index c5818c3..b2b2727 100644 --- a/SCrawler/Editors/GlobalSettingsForm.vb +++ b/SCrawler/Editors/GlobalSettingsForm.vb @@ -11,10 +11,10 @@ Imports PersonalUtilities.Forms.Controls.Base Imports PersonalUtilities.Forms.Toolbars Namespace Editors Friend Class GlobalSettingsForm : Implements IOkCancelToolbar - Private ReadOnly MyDefs As DefaultFormProps(Of FieldsChecker) + Private ReadOnly MyDefs As DefaultFormProps Friend Sub New() InitializeComponent() - MyDefs = New DefaultFormProps(Of FieldsChecker) + MyDefs = New DefaultFormProps End Sub Private Sub GlobalSettingsForm_Load(sender As Object, e As EventArgs) Handles Me.Load Try @@ -40,8 +40,14 @@ Namespace Editors CH_SHOW_NOTIFY.Checked = .ShowNotifications CH_FAST_LOAD.Checked = .FastProfilesLoading CH_RECYCLE_DEL.Checked = .DeleteToRecycleBin + CH_DOWN_OPEN_INFO.Checked = .DownloadOpenInfo + CH_DOWN_OPEN_INFO_SUSPEND.Checked = Not .DownloadOpenInfo.Attribute + CH_DOWN_OPEN_PROGRESS.Checked = .DownloadOpenProgress + CH_DOWN_OPEN_PROGRESS_SUSPEND.Checked = Not .DownloadOpenProgress.Attribute TXT_FOLDER_CMD.Text = .OpenFolderInOtherProgram TXT_FOLDER_CMD.Checked = .OpenFolderInOtherProgram.Attribute + TXT_CLOSE_SCRIPT.Text = .ClosingCommand + TXT_CLOSE_SCRIPT.Checked = .ClosingCommand.Attribute 'Defaults CH_SEPARATE_VIDEO_FOLDER.Checked = .SeparateVideoFolder.Value CH_DEF_TEMP.Checked = .DefaultTemporary @@ -51,6 +57,8 @@ Namespace Editors CH_UDESCR_UP.Checked = .UpdateUserDescriptionEveryTime TXT_SCRIPT.Checked = .ScriptData.Attribute TXT_SCRIPT.Text = .ScriptData.Value + TXT_DOWN_COMPLETE_SCRIPT.Text = .DownloadsCompleteCommand + TXT_DOWN_COMPLETE_SCRIPT.Checked = .DownloadsCompleteCommand.Attribute 'Downloading: file names CH_FILE_NAME_CHANGE.Checked = .FileReplaceNameByDate Or .FileAddDateToFileName Or .FileAddTimeToFileName OPT_FILE_NAME_REPLACE.Checked = .FileReplaceNameByDate @@ -70,7 +78,7 @@ Namespace Editors CH_CHANNELS_USERS_TEMP.Checked = .ChannelsDefaultTemporary End With .MyFieldsChecker = New FieldsChecker - With .MyFieldsChecker + With DirectCast(.MyFieldsChecker, FieldsChecker) .AddControl(Of String)(TXT_GLOBAL_PATH, TXT_GLOBAL_PATH.CaptionText) .AddControl(Of String)(TXT_COLLECTIONS_PATH, TXT_COLLECTIONS_PATH.CaptionText) .EndLoaderOperations() @@ -132,8 +140,14 @@ Namespace Editors .ShowNotifications.Value = CH_SHOW_NOTIFY.Checked .FastProfilesLoading.Value = CH_FAST_LOAD.Checked .DeleteToRecycleBin.Value = CH_RECYCLE_DEL.Checked + .DownloadOpenInfo.Value = CH_DOWN_OPEN_INFO.Checked + .DownloadOpenInfo.Attribute.Value = Not CH_DOWN_OPEN_INFO_SUSPEND.Checked + .DownloadOpenProgress.Value = CH_DOWN_OPEN_PROGRESS.Checked + .DownloadOpenProgress.Attribute.Value = Not CH_DOWN_OPEN_PROGRESS_SUSPEND.Checked .OpenFolderInOtherProgram.Value = TXT_FOLDER_CMD.Text .OpenFolderInOtherProgram.Attribute.Value = TXT_FOLDER_CMD.Checked + .ClosingCommand.Value = TXT_CLOSE_SCRIPT.Text + .ClosingCommand.Attribute.Value = TXT_CLOSE_SCRIPT.Checked 'Defaults .SeparateVideoFolder.Value = CH_SEPARATE_VIDEO_FOLDER.Checked .DefaultTemporary.Value = CH_DEF_TEMP.Checked @@ -143,6 +157,8 @@ Namespace Editors .UpdateUserDescriptionEveryTime.Value = CH_UDESCR_UP.Checked .ScriptData.Value = TXT_SCRIPT.Text .ScriptData.Attribute.Value = TXT_SCRIPT.Checked + .DownloadsCompleteCommand.Value = TXT_DOWN_COMPLETE_SCRIPT.Text + .DownloadsCompleteCommand.Attribute.Value = TXT_DOWN_COMPLETE_SCRIPT.Checked 'Downloading: file names If CH_FILE_NAME_CHANGE.Checked Then .FileReplaceNameByDate.Value = OPT_FILE_NAME_REPLACE.Checked diff --git a/SCrawler/Editors/SiteEditorForm.vb b/SCrawler/Editors/SiteEditorForm.vb index 721b1d2..1e9a16a 100644 --- a/SCrawler/Editors/SiteEditorForm.vb +++ b/SCrawler/Editors/SiteEditorForm.vb @@ -18,13 +18,17 @@ Namespace Editors Friend Class SiteEditorForm : Implements IOkCancelToolbar Private ReadOnly LBL_AUTH As Label Private ReadOnly LBL_OTHER As Label - Private ReadOnly MyDefs As DefaultFormProps(Of FieldsChecker) + Private ReadOnly MyDefs As DefaultFormProps Private WithEvents SpecialButton As Button #Region "Providers" - Private Class SavedPostsChecker : Implements ICustomProvider + Private Class SavedPostsChecker : Implements IFieldsCheckerProvider + Private Property ErrorMessage As String Implements IFieldsCheckerProvider.ErrorMessage + Private Property Name As String Implements IFieldsCheckerProvider.Name + Private Property TypeError As Boolean Implements IFieldsCheckerProvider.TypeError Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert - If Not ACheck(Value) OrElse CStr(Value).Contains("/") Then + If ACheck(Value) AndAlso CStr(Value).Contains("/") Then + ErrorMessage = $"Path [{Name}] contains forbidden character ""/""" Return Nothing Else Return Value @@ -38,7 +42,7 @@ Namespace Editors Private ReadOnly Property Host As SettingsHost Friend Sub New(ByVal h As SettingsHost) InitializeComponent() - MyDefs = New DefaultFormProps(Of FieldsChecker) + MyDefs = New DefaultFormProps Host = h LBL_AUTH = New Label With {.Text = "Authorization", .TextAlign = ContentAlignment.MiddleCenter, .Dock = DockStyle.Fill} LBL_OTHER = New Label With {.Text = "Other Parameters", .TextAlign = ContentAlignment.MiddleCenter, .Dock = DockStyle.Fill} @@ -65,7 +69,7 @@ Namespace Editors SiteDefaultsFunctions.SetChecker(TP_SITE_PROPS, Host) - With MyDefs.MyFieldsChecker + With DirectCast(MyDefs.MyFieldsChecker, FieldsChecker) .AddControl(Of String)(TXT_PATH, TXT_PATH.CaptionText, True, New SavedPostsChecker) .AddControl(Of String)(TXT_PATH_SAVED_POSTS, TXT_PATH_SAVED_POSTS.CaptionText, True, New SavedPostsChecker) End With @@ -113,7 +117,8 @@ Namespace Editors AddTpControl(.Control, .ControlHeight) If .LeftOffset > offset Then offset = .LeftOffset If Not .Options.AllowNull Or Not .ProviderFieldsChecker Is Nothing Then - MyDefs.MyFieldsChecker.AddControl(.Control, .Options.ControlText, .Type, .Options.AllowNull, .ProviderFieldsChecker) + DirectCast(MyDefs.MyFieldsChecker, FieldsChecker). + AddControl(.Control, .Options.ControlText, .Type, .Options.AllowNull, .ProviderFieldsChecker) End If End If End With diff --git a/SCrawler/Editors/UserCreatorForm.vb b/SCrawler/Editors/UserCreatorForm.vb index f544913..68ac8c6 100644 --- a/SCrawler/Editors/UserCreatorForm.vb +++ b/SCrawler/Editors/UserCreatorForm.vb @@ -19,7 +19,7 @@ Imports ADB = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons Namespace Editors Friend Class UserCreatorForm : Implements IOkCancelToolbar - Private ReadOnly MyDef As DefaultFormProps(Of FieldsChecker) + Private ReadOnly MyDef As DefaultFormProps Friend Property User As UserInfo Friend Property UserInstance As IUserData Friend Property StartIndex As Integer = -1 @@ -97,7 +97,7 @@ Namespace Editors Friend Sub New() InitializeComponent() UserLabels = New List(Of String) - MyDef = New DefaultFormProps(Of FieldsChecker) + MyDef = New DefaultFormProps End Sub ''' Edit exist user Friend Sub New(ByVal _Instance As IUserData) @@ -163,7 +163,7 @@ Namespace Editors End If End If .MyFieldsChecker = New FieldsChecker - .MyFieldsChecker.AddControl(Of String)(TXT_USER, TXT_USER.CaptionText) + DirectCast(.MyFieldsChecker, FieldsChecker).AddControl(Of String)(TXT_USER, TXT_USER.CaptionText) .MyFieldsChecker.EndLoaderOperations() .AppendDetectors() .EndLoaderOperations() @@ -447,8 +447,8 @@ CloseForm: Dim m As New MMessage($"Added {Added} users (skipped (already exists and/or duplicated) {Skipped})") If BannedUsers.ListExists Or NonIdentified.Count > 0 Then Dim t$ = String.Empty - If BannedUsers.ListExists Then t.StringAppendLine($"Banned users:{vbNewLine}{BannedUsers.ListToString(, vbNewLine)}") - If NonIdentified.Count > 0 Then t.StringAppendLine($"Non-Identified users:{vbNewLine}{NonIdentified.ListToString(, vbNewLine)}", vbNewLine.StringDup(2)) + If BannedUsers.ListExists Then t.StringAppendLine($"Banned users:{vbNewLine}{BannedUsers.ListToString(vbNewLine)}") + If NonIdentified.Count > 0 Then t.StringAppendLine($"Non-Identified users:{vbNewLine}{NonIdentified.ListToString(vbNewLine)}", vbNewLine.StringDup(2)) m.Style = MsgBoxStyle.Exclamation m.Text.StringAppendLine("Some of users does not recognized and/or banned") m.Text.StringAppendLine(t, vbNewLine.StringDup(2)) diff --git a/SCrawler/LabelsKeeper.vb b/SCrawler/LabelsKeeper.vb index bd786fa..a2072cc 100644 --- a/SCrawler/LabelsKeeper.vb +++ b/SCrawler/LabelsKeeper.vb @@ -72,7 +72,7 @@ Friend Class LabelsKeeper : Implements ICollection(Of String), IMyEnumerator(Of Friend Sub Update() If LabelsList.Count > 0 Then LabelsList.Sort() - TextSaver.SaveTextToFile(LabelsList.ListToString(, vbNewLine), LabelsFile, True, False, EDP.SendInLog) + TextSaver.SaveTextToFile(LabelsList.ListToString(vbNewLine), LabelsFile, True, False, EDP.SendInLog) Else LabelsFile.Delete(, Settings.DeleteMode, EDP.SendInLog) End If diff --git a/SCrawler/MainFrame.Designer.vb b/SCrawler/MainFrame.Designer.vb index f792dcc..5d38f22 100644 --- a/SCrawler/MainFrame.Designer.vb +++ b/SCrawler/MainFrame.Designer.vb @@ -29,6 +29,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Dim MENU_VIEW_SEP_2 As System.Windows.Forms.ToolStripSeparator Dim TRAY_SEP_1 As System.Windows.Forms.ToolStripSeparator Dim MENU_DOWN_ALL_SEP_1 As System.Windows.Forms.ToolStripSeparator + Dim MENU_DOWN_ALL_SEP_2 As System.Windows.Forms.ToolStripSeparator Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(MainFrame)) Me.MENU_SETTINGS = New System.Windows.Forms.ToolStripDropDownButton() Me.BTT_SETTINGS = New System.Windows.Forms.ToolStripMenuItem() @@ -46,6 +47,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_DOWN_SITE = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_DOWN_ALL_FULL = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_DOWN_SITE_FULL = New System.Windows.Forms.ToolStripMenuItem() + Me.BTT_ADD_NEW_GROUP = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_DOWN_VIDEO = New System.Windows.Forms.ToolStripButton() Me.BTT_DOWN_STOP = New System.Windows.Forms.ToolStripButton() Me.MENU_VIEW = New System.Windows.Forms.ToolStripDropDownButton() @@ -65,6 +67,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_SHOW_NO_LABELS = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_SHOW_EXCLUDED_LABELS = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_SHOW_EXCLUDED_LABELS_IGNORE = New System.Windows.Forms.ToolStripMenuItem() + Me.BTT_SHOW_SHOW_GROUPS = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_SHOW_LIMIT_DATES = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_LOG = New System.Windows.Forms.ToolStripButton() Me.BTT_VERSION_INFO = New System.Windows.Forms.ToolStripButton() @@ -98,7 +101,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.TRAY_CONTEXT = New System.Windows.Forms.ContextMenuStrip(Me.components) Me.BTT_TRAY_SHOW_HIDE = New System.Windows.Forms.ToolStripMenuItem() Me.BTT_TRAY_CLOSE = New System.Windows.Forms.ToolStripMenuItem() - Me.BTT_SHOW_SHOW_GROUPS = New System.Windows.Forms.ToolStripMenuItem() + Me.BTT_TRAY_CLOSE_NO_SCRIPT = New System.Windows.Forms.ToolStripMenuItem() SEP_1 = New System.Windows.Forms.ToolStripSeparator() SEP_2 = New System.Windows.Forms.ToolStripSeparator() CONTEXT_SEP_1 = New System.Windows.Forms.ToolStripSeparator() @@ -114,6 +117,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form MENU_VIEW_SEP_2 = New System.Windows.Forms.ToolStripSeparator() TRAY_SEP_1 = New System.Windows.Forms.ToolStripSeparator() MENU_DOWN_ALL_SEP_1 = New System.Windows.Forms.ToolStripSeparator() + MENU_DOWN_ALL_SEP_2 = New System.Windows.Forms.ToolStripSeparator() Me.Toolbar_TOP.SuspendLayout() Me.Toolbar_BOTTOM.SuspendLayout() Me.USER_CONTEXT.SuspendLayout() @@ -188,13 +192,18 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form 'TRAY_SEP_1 ' TRAY_SEP_1.Name = "TRAY_SEP_1" - TRAY_SEP_1.Size = New System.Drawing.Size(130, 6) + TRAY_SEP_1.Size = New System.Drawing.Size(177, 6) ' 'MENU_DOWN_ALL_SEP_1 ' MENU_DOWN_ALL_SEP_1.Name = "MENU_DOWN_ALL_SEP_1" MENU_DOWN_ALL_SEP_1.Size = New System.Drawing.Size(228, 6) ' + 'MENU_DOWN_ALL_SEP_2 + ' + MENU_DOWN_ALL_SEP_2.Name = "MENU_DOWN_ALL_SEP_2" + MENU_DOWN_ALL_SEP_2.Size = New System.Drawing.Size(228, 6) + ' 'MENU_SETTINGS ' Me.MENU_SETTINGS.AutoToolTip = False @@ -295,7 +304,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form 'MENU_DOWN_ALL ' Me.MENU_DOWN_ALL.AutoToolTip = False - Me.MENU_DOWN_ALL.DropDownItems.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_DOWN_ALL, Me.BTT_DOWN_SITE, MENU_DOWN_ALL_SEP_1, Me.BTT_DOWN_ALL_FULL, Me.BTT_DOWN_SITE_FULL}) + Me.MENU_DOWN_ALL.DropDownItems.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_DOWN_ALL, Me.BTT_DOWN_SITE, MENU_DOWN_ALL_SEP_1, Me.BTT_DOWN_ALL_FULL, Me.BTT_DOWN_SITE_FULL, MENU_DOWN_ALL_SEP_2, Me.BTT_ADD_NEW_GROUP}) Me.MENU_DOWN_ALL.Image = Global.SCrawler.My.Resources.Resources.StartPic_01_Green_16 Me.MENU_DOWN_ALL.ImageTransparentColor = System.Drawing.Color.Magenta Me.MENU_DOWN_ALL.Name = "MENU_DOWN_ALL" @@ -339,6 +348,13 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_DOWN_SITE_FULL.Text = "Download all site users [FULL]" Me.BTT_DOWN_SITE_FULL.ToolTipText = "Download all users from specific sites. The 'Ready for download' option will be i" & "gnored." + ' + 'BTT_ADD_NEW_GROUP + ' + Me.BTT_ADD_NEW_GROUP.Image = Global.SCrawler.My.Resources.Resources.PlusPIC + Me.BTT_ADD_NEW_GROUP.Name = "BTT_ADD_NEW_GROUP" + Me.BTT_ADD_NEW_GROUP.Size = New System.Drawing.Size(231, 22) + Me.BTT_ADD_NEW_GROUP.Text = "Add a new download group" ' 'BTT_DOWN_VIDEO ' @@ -471,6 +487,12 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_SHOW_EXCLUDED_LABELS_IGNORE.Size = New System.Drawing.Size(231, 22) Me.BTT_SHOW_EXCLUDED_LABELS_IGNORE.Text = "Ignore excluded labels" ' + 'BTT_SHOW_SHOW_GROUPS + ' + Me.BTT_SHOW_SHOW_GROUPS.Name = "BTT_SHOW_SHOW_GROUPS" + Me.BTT_SHOW_SHOW_GROUPS.Size = New System.Drawing.Size(231, 22) + Me.BTT_SHOW_SHOW_GROUPS.Text = "Show groups instead of labels" + ' 'BTT_SHOW_LIMIT_DATES ' Me.BTT_SHOW_LIMIT_DATES.AutoToolTip = True @@ -697,14 +719,14 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form ' 'TRAY_CONTEXT ' - Me.TRAY_CONTEXT.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_TRAY_SHOW_HIDE, TRAY_SEP_1, Me.BTT_TRAY_CLOSE}) + Me.TRAY_CONTEXT.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_TRAY_SHOW_HIDE, TRAY_SEP_1, Me.BTT_TRAY_CLOSE, Me.BTT_TRAY_CLOSE_NO_SCRIPT}) Me.TRAY_CONTEXT.Name = "TRAY_CONTEXT" - Me.TRAY_CONTEXT.Size = New System.Drawing.Size(134, 54) + Me.TRAY_CONTEXT.Size = New System.Drawing.Size(181, 98) ' 'BTT_TRAY_SHOW_HIDE ' Me.BTT_TRAY_SHOW_HIDE.Name = "BTT_TRAY_SHOW_HIDE" - Me.BTT_TRAY_SHOW_HIDE.Size = New System.Drawing.Size(133, 22) + Me.BTT_TRAY_SHOW_HIDE.Size = New System.Drawing.Size(180, 22) Me.BTT_TRAY_SHOW_HIDE.Text = "Show/Hide" ' 'BTT_TRAY_CLOSE @@ -713,14 +735,18 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_TRAY_CLOSE.ForeColor = System.Drawing.Color.Maroon Me.BTT_TRAY_CLOSE.Image = CType(resources.GetObject("BTT_TRAY_CLOSE.Image"), System.Drawing.Image) Me.BTT_TRAY_CLOSE.Name = "BTT_TRAY_CLOSE" - Me.BTT_TRAY_CLOSE.Size = New System.Drawing.Size(133, 22) + Me.BTT_TRAY_CLOSE.Size = New System.Drawing.Size(180, 22) Me.BTT_TRAY_CLOSE.Text = "Close" ' - 'BTT_SHOW_SHOW_GROUPS + 'BTT_TRAY_CLOSE_NO_SCRIPT ' - Me.BTT_SHOW_SHOW_GROUPS.Name = "BTT_SHOW_SHOW_GROUPS" - Me.BTT_SHOW_SHOW_GROUPS.Size = New System.Drawing.Size(231, 22) - Me.BTT_SHOW_SHOW_GROUPS.Text = "Show groups instead of labels" + Me.BTT_TRAY_CLOSE_NO_SCRIPT.BackColor = System.Drawing.Color.FromArgb(CType(CType(255, Byte), Integer), CType(CType(192, Byte), Integer), CType(CType(192, Byte), Integer)) + Me.BTT_TRAY_CLOSE_NO_SCRIPT.ForeColor = System.Drawing.Color.Maroon + Me.BTT_TRAY_CLOSE_NO_SCRIPT.Image = Global.SCrawler.My.Resources.Resources.Delete + Me.BTT_TRAY_CLOSE_NO_SCRIPT.Name = "BTT_TRAY_CLOSE_NO_SCRIPT" + Me.BTT_TRAY_CLOSE_NO_SCRIPT.Size = New System.Drawing.Size(160, 22) + Me.BTT_TRAY_CLOSE_NO_SCRIPT.Text = "Close (no script)" + Me.BTT_TRAY_CLOSE_NO_SCRIPT.Visible = False ' 'MainFrame ' @@ -794,7 +820,6 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Private WithEvents BTT_CONTEXT_CHANGE_FOLDER As ToolStripMenuItem Private WithEvents BTT_DOWN_SAVED As ToolStripButton Private WithEvents TrayIcon As NotifyIcon - Private WithEvents TRAY_CONTEXT As ContextMenuStrip Private WithEvents BTT_TRAY_SHOW_HIDE As ToolStripMenuItem Private WithEvents BTT_TRAY_CLOSE As ToolStripMenuItem Private WithEvents BTT_DONATE As ToolStripButton @@ -814,4 +839,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Private WithEvents BTT_SHOW_EXCLUDED_LABELS As ToolStripMenuItem Private WithEvents BTT_SHOW_EXCLUDED_LABELS_IGNORE As ToolStripMenuItem Private WithEvents BTT_SHOW_SHOW_GROUPS As ToolStripMenuItem + Private WithEvents BTT_ADD_NEW_GROUP As ToolStripMenuItem + Friend WithEvents BTT_TRAY_CLOSE_NO_SCRIPT As ToolStripMenuItem + Friend WithEvents TRAY_CONTEXT As ContextMenuStrip End Class \ No newline at end of file diff --git a/SCrawler/MainFrame.resx b/SCrawler/MainFrame.resx index bc999df..01d77b8 100644 --- a/SCrawler/MainFrame.resx +++ b/SCrawler/MainFrame.resx @@ -162,6 +162,9 @@ False + + False + 132, 17 diff --git a/SCrawler/MainFrame.vb b/SCrawler/MainFrame.vb index 3ed34cb..f98bad4 100644 --- a/SCrawler/MainFrame.vb +++ b/SCrawler/MainFrame.vb @@ -1,4 +1,4 @@ -' Copyright (C) 2022 Andy +' Copyright (C) 2022 Andy ' This program is free software: you can redistribute it and/or modify ' it under the terms of the GNU General Public License as published by ' the Free Software Foundation, either version 3 of the License, or @@ -45,6 +45,7 @@ Public Class MainFrame If _VideoDownloadingMode Then GoTo FormClosingInvoker Settings.DeleteCachePath() MainFrameObj = New MainFrameObjects(Me) + MainFrameObj.ChangeCloseVisible() MainProgress = New Toolbars.MyProgress(Toolbar_BOTTOM, PR_MAIN, LBL_STATUS, "Downloading profiles' data") With { .DropCurrentProgressOnTotalChange = False, .Enabled = False} Downloader = New TDownloader @@ -84,6 +85,14 @@ Public Class MainFrame BTT_SITE_ALL.Checked = Settings.SelectedSites.Count = 0 BTT_SITE_SPECIFIC.Checked = Settings.SelectedSites.Count > 0 BTT_SHOW_LIMIT_DATES.Checked = Settings.LastUpdatedDate.HasValue + With Settings.Groups + AddHandler .Added, AddressOf GROUPS_Added + AddHandler .Deleted, AddressOf GROUPS_Deleted + AddHandler .Updated, AddressOf GROUPS_Updated + If .Count > 0 Then + For Each ugroup As Groups.DownloadGroup In Settings.Groups : GROUPS_Added(ugroup) : Next + End If + End With _UFinit = False GoTo EndFunction FormClosingInvoker: @@ -94,6 +103,7 @@ EndFunction: Private _IgnoreTrayOptions As Boolean = False Private _IgnoreCloseConfirm As Boolean = False Private Async Sub MainFrame_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + If _VideoDownloadingMode Then Exit Sub If Settings.CloseToTray And Not _IgnoreTrayOptions Then e.Cancel = True Hide() @@ -119,6 +129,7 @@ EndFunction: End Sub) End If Downloader.Dispose() + MyProgressForm.Dispose() InfoForm.Dispose() If Not MyChannels Is Nothing Then MyChannels.Dispose() If Not VideoDownloader Is Nothing Then VideoDownloader.Dispose() @@ -141,11 +152,15 @@ DropCloseParams: Exit Sub CloseContinue: If Not BATCH Is Nothing Then BATCH.Dispose() : BATCH = Nothing - If Not MyMainLOG.IsEmptyString Then SaveLogToFile() If _CloseInvoked Then Close() CloseResume: End If End Sub + Private _DisableClosingScript As Boolean = False + Private Sub MainFrame_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + If Not _DisableClosingScript And Not _VideoDownloadingMode Then ExecuteCommand(Settings.ClosingCommand) + If Not MyMainLOG.IsEmptyString Then SaveLogToFile() + End Sub #Region "Tray" Private Sub TrayIcon_MouseClick(sender As Object, e As MouseEventArgs) Handles TrayIcon.MouseClick If e.Button = MouseButtons.Left Then @@ -156,6 +171,13 @@ CloseResume: If Visible Then Hide() Else Show() End Sub Private Sub BTT_TRAY_CLOSE_Click(sender As Object, e As EventArgs) Handles BTT_TRAY_CLOSE.Click + ClosePressed(False) + End Sub + Private Sub BTT_TRAY_CLOSE_NO_SCRIPT_Click(sender As Object, e As EventArgs) Handles BTT_TRAY_CLOSE_NO_SCRIPT.Click + ClosePressed(True) + End Sub + Private Sub ClosePressed(ByVal DisableScript As Boolean) + _DisableClosingScript = DisableScript If CheckForClose(False) Then _IgnoreTrayOptions = True : _IgnoreCloseConfirm = True : Close() End Sub Private Function CheckForClose(ByVal _Ignore As Boolean) As Boolean @@ -177,10 +199,27 @@ CloseResume: Case Keys.F3 : EditSelectedUser() Case Keys.F5 : BTT_DOWN_SELECTED.PerformClick() Case Keys.F6 : If Settings.ShowingMode.Value = ShowingModes.All Then BTT_DOWN_ALL.PerformClick() - Case Else : b = False + Case Else : b = NumGroup(e) End Select If b Then e.Handled = True End Sub + Private Function NumGroup(ByVal e As KeyEventArgs) As Boolean + Dim GroupExists As Func(Of Integer, Boolean) = Function(i) Settings.Groups.DownloadGroupIfExists(i - 1) + If e.Control And Settings.Groups.Count > 0 Then + Select Case e.KeyCode + Case Keys.D1, Keys.NumPad1 : Return GroupExists(1) + Case Keys.D2, Keys.NumPad2 : Return GroupExists(2) + Case Keys.D3, Keys.NumPad3 : Return GroupExists(3) + Case Keys.D4, Keys.NumPad4 : Return GroupExists(4) + Case Keys.D5, Keys.NumPad5 : Return GroupExists(5) + Case Keys.D6, Keys.NumPad6 : Return GroupExists(6) + Case Keys.D7, Keys.NumPad7 : Return GroupExists(7) + Case Keys.D8, Keys.NumPad8 : Return GroupExists(8) + Case Keys.D9, Keys.NumPad9 : Return GroupExists(9) + End Select + End If + Return False + End Function Private Sub BTT_VERSION_INFO_Click(sender As Object, e As EventArgs) Handles BTT_VERSION_INFO.Click CheckVersion(True) End Sub @@ -357,6 +396,24 @@ CloseResume: End If End Using End Sub +#End Region +#Region "Download groups" + Private Sub BTT_ADD_NEW_GROUP_Click(sender As Object, e As EventArgs) Handles BTT_ADD_NEW_GROUP.Click + Settings.Groups.Add() + End Sub + Private Sub GROUPS_Added(ByVal Sender As Groups.DownloadGroup) + Dim i% = MENU_DOWN_ALL.DropDownItems.IndexOf(BTT_ADD_NEW_GROUP) + Dim a As Action = Sub() MENU_DOWN_ALL.DropDownItems.Insert(i, Sender.GetControl) + If Toolbar_TOP.InvokeRequired Then Toolbar_TOP.Invoke(a) Else a.Invoke + End Sub + Private Sub GROUPS_Updated(ByVal Sender As Groups.DownloadGroup) + Dim i% = MENU_DOWN_ALL.DropDownItems.IndexOf(Sender.GetControl) + Dim a As Action = Sub() MENU_DOWN_ALL.DropDownItems(i).Text = Sender.ToString + If Toolbar_TOP.InvokeRequired Then Toolbar_TOP.Invoke(a) Else a.Invoke + End Sub + Private Sub GROUPS_Deleted(ByVal Sender As Groups.DownloadGroup) + MENU_DOWN_ALL.DropDownItems.Remove(Sender.GetControl) + End Sub #End Region Private Sub BTT_DOWN_VIDEO_Click(sender As Object, e As EventArgs) Handles BTT_DOWN_VIDEO.Click DownloadVideoByURL() @@ -655,7 +712,7 @@ CloseResume: End Try End Sub Private Function AskForMassReplace(ByVal users As List(Of IUserData), ByVal param As String) As Boolean - Dim u$ = users.ListIfNothing.Take(20).Select(Function(uu) uu.Name).ListToString(, vbCr) + Dim u$ = users.ListIfNothing.Take(20).Select(Function(uu) uu.Name).ListToString(vbCr) If Not u.IsEmptyString And users.ListExists(21) Then u &= vbCr & "..." Return users.ListExists AndAlso (users.Count = 1 OrElse MsgBoxE({$"Do you really want to change [{param}] for {users.Count} users?{vbCr}{vbCr}{u}", "Users' parameters change"}, @@ -921,7 +978,7 @@ CloseResume: If users.ListExists Then If USER_CONTEXT.Visible Then USER_CONTEXT.Hide() Dim ugn As Func(Of IUserData, String) = Function(u) $"{IIf(u.IsCollection, "Collection", "User")}: {u.Name}" - Dim m As New MMessage(users.Select(ugn).ListToString(, vbNewLine), "Users deleting", + Dim m As New MMessage(users.Select(ugn).ListToString(vbNewLine), "Users deleting", {New Messaging.MsgBoxButton("Delete and ban") With {.ToolTip = "Users and their data will be deleted and added to the blacklist"}, New Messaging.MsgBoxButton("Delete user only and ban") With { .ToolTip = "Users will be deleted and added to the blacklist (user data will not be deleted)"}, @@ -980,8 +1037,8 @@ CloseResume: m.Text = "No one user deleted!" m.Style = MsgBoxStyle.Critical Else - m.Text = $"The following users were deleted:{vbNewLine}{removedUsers.ListToString(, vbNewLine)}{vbNewLine.StringDup(2)}" - m.Text &= $"The following users were NOT deleted:{vbNewLine}{leftUsers.ListToString(, vbNewLine)}" + m.Text = $"The following users were deleted:{vbNewLine}{removedUsers.ListToString(vbNewLine)}{vbNewLine.StringDup(2)}" + m.Text &= $"The following users were NOT deleted:{vbNewLine}{leftUsers.ListToString(vbNewLine)}" m.Style = MsgBoxStyle.Exclamation End If If b Then Settings.UpdateBlackList() @@ -1063,7 +1120,7 @@ ResumeDownloadingOperation: users(0).DownloadToDate = d Downloader.Add(users(0)) Else - Dim uStr$ = users.Select(Function(u) u.ToString()).ListToString(, vbNewLine) + Dim uStr$ = users.Select(Function(u) u.ToString()).ListToString(vbNewLine) If MsgBoxE({$"You are select {users.Count} users' profiles{vbNewLine}Do you want to download all of them?{vbNewLine.StringDup(2)}" & $"Selected users:{vbNewLine}{uStr}", "A few users selected"}, MsgBoxStyle.Question + MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then @@ -1127,4 +1184,4 @@ ResumeDownloadingOperation: Private Sub BTT_PR_INFO_Click(sender As Object, e As EventArgs) Handles BTT_PR_INFO.Click If MyProgressForm.Visible Then MyProgressForm.BringToFront() Else MyProgressForm.Show() End Sub -End Class +End Class \ No newline at end of file diff --git a/SCrawler/MainFrameObjects.vb b/SCrawler/MainFrameObjects.vb index f4c7583..6d216df 100644 --- a/SCrawler/MainFrameObjects.vb +++ b/SCrawler/MainFrameObjects.vb @@ -34,4 +34,11 @@ Friend Class MainFrameObjects Catch ex As Exception End Try End Sub + Friend Sub Focus() + If MF.Visible Then MF.BringToFront() : MF.Activate() + End Sub + Friend Sub ChangeCloseVisible() + Dim a As Action = Sub() MF.BTT_TRAY_CLOSE_NO_SCRIPT.Visible = Settings.ClosingCommand.Attribute And Not Settings.ClosingCommand.IsEmptyString + If MF.TRAY_CONTEXT.InvokeRequired Then MF.TRAY_CONTEXT.Invoke(a) Else a.Invoke + End Sub End Class \ No newline at end of file diff --git a/SCrawler/MainMod.vb b/SCrawler/MainMod.vb index 4328942..0c5f8dc 100644 --- a/SCrawler/MainMod.vb +++ b/SCrawler/MainMod.vb @@ -54,6 +54,20 @@ Friend Module MainMod End If End Try End Sub + Friend Sub ExecuteCommand(ByVal Obj As XMLValueAttribute(Of String, Boolean)) + Try + If Obj.Attribute And Not Obj.IsEmptyString Then + Using b As New BatchExecutor With {.RedirectStandardError = True} + With b + .Execute({Obj.Value}, EDP.SendInLog + EDP.ThrowException) + If .HasError Or Not .ErrorOutput.IsEmptyString Then Throw New Exception(.ErrorOutput, .ErrorException) + End With + End Using + End If + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendInLog, ex, $"[{Obj.Name}] command: [{Obj.Value}]") + End Try + End Sub Friend Enum ViewModes As Integer IconLarge = View.LargeIcon IconSmall = View.SmallIcon @@ -449,14 +463,14 @@ Friend Module MainMod m.Text = $"This user is banned:{vbNewLine}User: {Found(0).Name}" If Not Found(0).Reason.IsEmptyString Then m.Text.StringAppendLine($"Reason: {Found(0).Reason}") Else - m.Text = $"These users was banned:{vbNewLine.StringDup(2)}{Found.Select(Function(u) u.Info).ListToString(, vbNewLine)}" + m.Text = $"These users was banned:{vbNewLine.StringDup(2)}{Found.Select(Function(u) u.Info).ListToString(vbNewLine)}" End If Dim r% = MsgBoxE(m) If r = 2 Then Return Found.Select(Function(u) u.Name).ToArray Else If r = 0 Then - Settings.BlackList.ListDisposeRemove(Found, False) + Settings.BlackList.ListDisposeRemove(Found) Settings.UpdateBlackList() End If End If diff --git a/SCrawler/My Project/AssemblyInfo.vb b/SCrawler/My Project/AssemblyInfo.vb index 696a527..0c86b2e 100644 --- a/SCrawler/My Project/AssemblyInfo.vb +++ b/SCrawler/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler/My Project/Resources.Designer.vb b/SCrawler/My Project/Resources.Designer.vb index 54bcdab..c130c2b 100644 --- a/SCrawler/My Project/Resources.Designer.vb +++ b/SCrawler/My Project/Resources.Designer.vb @@ -110,6 +110,16 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + ''' + Friend ReadOnly Property GroupBy_284() As System.Drawing.Icon + Get + Dim obj As Object = ResourceManager.GetObject("GroupBy_284", resourceCulture) + Return CType(obj,System.Drawing.Icon) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Drawing.Bitmap. ''' diff --git a/SCrawler/My Project/Resources.resx b/SCrawler/My Project/Resources.resx index 2c52e69..ba1a7ba 100644 --- a/SCrawler/My Project/Resources.resx +++ b/SCrawler/My Project/Resources.resx @@ -193,4 +193,7 @@ ..\Content\Pictures\ScriptPic32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Content\Icons\GroupBy_284.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/SCrawler/SCrawler.vbproj b/SCrawler/SCrawler.vbproj index 5ef3ef6..e99b93f 100644 --- a/SCrawler/SCrawler.vbproj +++ b/SCrawler/SCrawler.vbproj @@ -160,7 +160,15 @@ Form + + + + GroupEditorForm.vb + + + Form + @@ -314,6 +322,9 @@ DownloadSavedPostsForm.vb + + GroupEditorForm.vb + CollectionEditorForm.vb @@ -380,6 +391,7 @@ + PreserveNewest diff --git a/SCrawler/SettingsCLS.vb b/SCrawler/SettingsCLS.vb index 666dc48..752990d 100644 --- a/SCrawler/SettingsCLS.vb +++ b/SCrawler/SettingsCLS.vb @@ -33,6 +33,7 @@ Friend Class SettingsCLS : Implements IDisposable Friend ReadOnly Property UsersList As List(Of UserInfo) Friend Property Channels As Reddit.ChannelsCollection Friend ReadOnly Property Labels As LabelsKeeper + Friend ReadOnly Property Groups As Groups.DownloadGroupCollection Friend ReadOnly Property BlackList As List(Of UserBan) Private ReadOnly BlackListFile As SFile = $"{SettingsFolderName}\BlackList.txt" Private ReadOnly UsersSettingsFile As SFile = $"{SettingsFolderName}\Users.xml" @@ -69,6 +70,11 @@ Friend Class SettingsCLS : Implements IDisposable FastProfilesLoading = New XMLValue(Of Boolean)("FastProfilesLoading", False, MyXML) MaxLargeImageHeigh = New XMLValue(Of Integer)("MaxLargeImageHeigh", 150, MyXML) MaxSmallImageHeigh = New XMLValue(Of Integer)("MaxSmallImageHeigh", 15, MyXML) + DownloadOpenInfo = New XMLValueAttribute(Of Boolean, Boolean)("DownloadOpenInfo", "OpenAgain", False, False, MyXML) + DownloadOpenProgress = New XMLValueAttribute(Of Boolean, Boolean)("DownloadOpenProgress", "OpenAgain", False, False, MyXML) + DownloadsCompleteCommand = New XMLValueAttribute(Of String, Boolean)("DownloadsCompleteCommand", "Use",,, MyXML) + ClosingCommand = New XMLValueAttribute(Of String, Boolean)("ClosingCommand", "Use",,, MyXML) + AddHandler ClosingCommand.OnValueChanged, Sub(s, __n, v) MainFrameObj?.ChangeCloseVisible() InfoViewMode = New XMLValue(Of Integer)("InfoViewMode", DownloadedInfoForm.ViewModes.Session, MyXML) ViewMode = New XMLValue(Of Integer)("ViewMode", ViewModes.IconLarge, MyXML) ShowingMode = New XMLValue(Of Integer)("ShowingMode", ShowingModes.All, MyXML) @@ -124,6 +130,8 @@ Friend Class SettingsCLS : Implements IDisposable DeleteToRecycleBin = New XMLValue(Of Boolean)("DeleteToRecycleBin", True, MyXML) Labels = New LabelsKeeper(MyXML) + Groups = New Groups.DownloadGroupCollection + Labels.AddRange(Groups.GetLabels, False) MyXML.EndUpdate() If MyXML.ChangesDetected Then MyXML.Sort() : MyXML.UpdateData() @@ -316,7 +324,7 @@ Friend Class SettingsCLS : Implements IDisposable #End Region Friend Sub UpdateBlackList() If BlackList.Count > 0 Then - TextSaver.SaveTextToFile(BlackList.ListToString(, vbNewLine), BlackListFile, True, False, EDP.None) + TextSaver.SaveTextToFile(BlackList.ListToString(vbNewLine), BlackListFile, True, False, EDP.None) Else BlackListFile.Delete(, Settings.DeleteMode) End If @@ -398,6 +406,10 @@ Friend Class SettingsCLS : Implements IDisposable Friend ReadOnly Property FastProfilesLoading As XMLValue(Of Boolean) Friend ReadOnly Property MaxLargeImageHeigh As XMLValue(Of Integer) Friend ReadOnly Property MaxSmallImageHeigh As XMLValue(Of Integer) + Friend ReadOnly Property DownloadOpenInfo As XMLValueAttribute(Of Boolean, Boolean) + Friend ReadOnly Property DownloadOpenProgress As XMLValueAttribute(Of Boolean, Boolean) + Friend ReadOnly Property DownloadsCompleteCommand As XMLValueAttribute(Of String, Boolean) + Friend ReadOnly Property ClosingCommand As XMLValueAttribute(Of String, Boolean) Friend ReadOnly Property InfoViewMode As XMLValue(Of Integer) Friend ReadOnly Property ViewMode As XMLValue(Of Integer) Friend ReadOnly Property ViewModeIsPicture As Boolean