From e868c2e6948c002d149145412f6ecb55b07ed020 Mon Sep 17 00:00:00 2001 From: Andy <88590076+AAndyProgram@users.noreply.github.com> Date: Fri, 12 May 2023 20:00:32 +0300 Subject: [PATCH] 2023.5.12.0 IPluginContentProvider: add 'ProgressPreChanged' and 'ProgressPreMaximumChanged' events YT.MediaItem: change folder opening on double click YT.VideoListForm: change the icon for the 'Download' button Add advanced progress Add user metrics calculation UserDataBase: fix GIF hash bug Instagram: heic to jpg Mastodon.SiteSettings: add the main domain to the list of domains with saving the settings Mastodon.UserData: handle 'Forbidden' error; fix bug in parsing non-user posts Pinterest: remove cookies requirement for saved posts PornHub: fix resolutions issue; add 'DownloadUHD' option Reddit: fix missing images bug; fix broken images bug; update container parsing function MainFrame: fix collection pointing bug --- Changelog.md | 18 + .../SeparateVideoDownloader.png | Bin 7031 -> 0 bytes ProgramScreenshots/SettingsSitePornHub.png | Bin 16287 -> 16932 bytes ProgramScreenshots/UserMetrics.png | Bin 0 -> 14436 bytes README.md | 29 +- .../Interfaces/IPluginContentProvider.vb | 2 + .../My Project/AssemblyInfo.vb | 4 +- .../Content/Pictures/StartPic_Green_16.png | Bin 0 -> 373 bytes SCrawler.YouTube/Downloader/MediaItem.vb | 2 +- .../Downloader/VideoListForm.Designer.vb | 3 +- .../Downloader/VideoListForm.resx | 371 ++++++------- SCrawler.YouTube/My Project/AssemblyInfo.vb | 4 +- .../My Project/Resources.Designer.vb | 10 + SCrawler.YouTube/My Project/Resources.resx | 3 + SCrawler.YouTube/SCrawler.YouTube.vbproj | 3 + .../My Project/AssemblyInfo.vb | 4 +- SCrawler/API/Base/M3U8Base.vb | 79 +-- SCrawler/API/Base/UserDataBase.vb | 45 +- SCrawler/API/Instagram/UserData.vb | 30 +- SCrawler/API/LPSG/UserData.vb | 2 + SCrawler/API/Mastodon/SiteSettings.vb | 9 + SCrawler/API/Mastodon/UserData.vb | 6 +- SCrawler/API/Pinterest/SiteSettings.vb | 3 +- SCrawler/API/Pinterest/UserData.vb | 8 + SCrawler/API/PornHub/Declarations.vb | 7 +- SCrawler/API/PornHub/M3U8.vb | 18 +- SCrawler/API/PornHub/SiteSettings.vb | 3 + SCrawler/API/PornHub/UserData.vb | 113 +++- SCrawler/API/PornHub/UserExchangeOptions.vb | 4 + SCrawler/API/Reddit/M3U8.vb | 28 +- SCrawler/API/Reddit/UserData.vb | 151 ++++-- SCrawler/API/Redgifs/UserData.vb | 6 +- SCrawler/API/ThisVid/UserData.vb | 8 + SCrawler/API/Twitter/UserData.vb | 68 ++- SCrawler/API/XVIDEOS/M3U8.vb | 4 +- SCrawler/API/XVIDEOS/UserData.vb | 8 +- SCrawler/API/Xhamster/M3U8.vb | 4 +- SCrawler/API/Xhamster/UserData.vb | 11 +- SCrawler/API/YouTube/UserData.vb | 11 +- SCrawler/API/YouTube/YTPreProgress.vb | 38 ++ .../Download/ActiveDownloadingProgress.vb | 5 + SCrawler/Download/DownloadProgress.vb | 19 +- SCrawler/Editors/UserCreatorForm.Designer.vb | 11 +- SCrawler/Editors/UsersInfoForm.Designer.vb | 287 ++++++++++ SCrawler/Editors/UsersInfoForm.resx | 150 ++++++ SCrawler/Editors/UsersInfoForm.vb | 504 ++++++++++++++++++ SCrawler/MainFrame.Designer.vb | 7 +- SCrawler/MainFrame.resx | 5 + SCrawler/MainFrame.vb | 58 +- SCrawler/MainMod.vb | 2 +- SCrawler/My Project/AssemblyInfo.vb | 4 +- SCrawler/MyProgressExt.vb | 176 ++++++ .../PluginsEnvironment/Hosts/UserDataHost.vb | 8 + SCrawler/SCrawler.vbproj | 11 + SCrawler/SettingsCLS.vb | 18 +- 55 files changed, 1980 insertions(+), 402 deletions(-) delete mode 100644 ProgramScreenshots/SeparateVideoDownloader.png create mode 100644 ProgramScreenshots/UserMetrics.png create mode 100644 SCrawler.YouTube/Content/Pictures/StartPic_Green_16.png create mode 100644 SCrawler/API/YouTube/YTPreProgress.vb create mode 100644 SCrawler/Editors/UsersInfoForm.Designer.vb create mode 100644 SCrawler/Editors/UsersInfoForm.resx create mode 100644 SCrawler/Editors/UsersInfoForm.vb create mode 100644 SCrawler/MyProgressExt.vb diff --git a/Changelog.md b/Changelog.md index 1de7195..30b550d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,21 @@ +# 2023.5.12.0 + +*2023-05-12* + +- Added + - Advanced progress (make progress bars more informative) + - User metrics calculation + - Reddit: improve parsing function + - PornHub: add `Download UHD` option +- Fixed + - MD5 GIF hash bug + - Mastodon: handle 'Forbidden' error + - Mastodon: bug in parsing non-user posts + - Pinterest: remove cookies requirement for saved posts + - PornHub: resolutions issue + - Reddit: missing & broken images bug + - Main window: collection pointing bug + # 2023.4.28.0 *2023-04-28* diff --git a/ProgramScreenshots/SeparateVideoDownloader.png b/ProgramScreenshots/SeparateVideoDownloader.png deleted file mode 100644 index 4d3e5f825c7c4d00ea9479a67b4c282c046fe4e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7031 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 diff --git a/ProgramScreenshots/SettingsSitePornHub.png b/ProgramScreenshots/SettingsSitePornHub.png index 25794ff2c97f3247147a962a7bd5d9013302a454..2a02a1d095e1ed67401fe088170c954427e6b2e4 100644 GIT binary patch literal 16932 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 E2ghfNIsgCw literal 16287 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 diff --git a/ProgramScreenshots/UserMetrics.png b/ProgramScreenshots/UserMetrics.png new file mode 100644 index 0000000000000000000000000000000000000000..d968b22ef7d183a4d11388b90651680b6bec0ec3 GIT binary patch literal 14436 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 MQ&*!1Y!mk10Q6{dod5s; literal 0 HcmV?d00001 diff --git a/README.md b/README.md index aedabcb..0856039 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ A program to download photo and video from [any site](#supported-sites) (e.g. Yo ![Main window](ProgramScreenshots/MainWindow.png) ![Channels window](ProgramScreenshots/Channels.png) -[**YouTube standalone application:**](https://github.com/AAndyProgram/SCrawler/wiki/YouTube%20downloader) +[**YouTube standalone application:**](https://github.com/AAndyProgram/SCrawler/wiki/YouTube-downloader) ![YouTube application](ProgramScreenshots/AppYouTube.png) @@ -38,7 +38,7 @@ A program to download photo and video from [any site](#supported-sites) (e.g. Yo - PornHub images, videos, save (liked) posts; - XHamster images, videos, saved posts; - XVIDEOS videos, saved posts; - - ThiVid images, videos, saved posts; + - ThisVid images, videos, saved posts; - [Other](#supported-sites) supported sites - Parse [channel and view data](https://github.com/AAndyProgram/SCrawler/wiki/Channels) - Download [saved Reddit, Twitter and Instagram posts](https://github.com/AAndyProgram/SCrawler/wiki/Home#saved-posts) @@ -158,11 +158,34 @@ The program has an intuitive interface. Just add a user profile and **click the ```Download``` button**. -Read more about adding users and subreddits [here](https://github.com/AAndyProgram/SCrawler/wiki#Add%20user) +```mermaid +stateDiagram +Start: Add site credentials +What: What would I like to do +DownUser: Download user +DownVideo: Download video +AUser: Add user (1) +OVIF: Open standalone downloader (2) +AVideo: Add video url +F5: Press 'F5' or click the download button +[*]-->Start +Start-->What +What-->DownUser +What-->DownVideo +DownUser-->AUser +DownVideo-->OVIF +OVIF-->AVideo +AVideo-->F5 +AUser-->F5 +F5-->[*] +``` +1. Press `Insert` or click the `Download` button ([read more here](https://github.com/AAndyProgram/SCrawler/wiki#users-list), [hot keys](https://github.com/AAndyProgram/SCrawler/wiki#hot-keys)) +2. Click the `Download` button, then `Standalone downloader` ([read more here](https://github.com/AAndyProgram/SCrawler/wiki#download-separate-video)) ![Add user](ProgramScreenshots/CreateUserClear.png) # Contact me Matrix (Element): https://matrix.to/#/@andyprogram:matrix.org + Discord: AndyProgram#3804 \ No newline at end of file diff --git a/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb b/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb index 0385313..2bf0c9a 100644 --- a/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb +++ b/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb @@ -10,6 +10,8 @@ Namespace Plugin Public Interface IPluginContentProvider : Inherits IDisposable Event ProgressChanged(ByVal Value As Integer) Event ProgressMaximumChanged(ByVal Value As Integer, ByVal Add As Boolean) + Event ProgressPreChanged As ProgressChangedEventHandler + Event ProgressPreMaximumChanged As ProgressMaximumChangedEventHandler Property Thrower As IThrower Property LogProvider As ILogProvider Property Settings As ISiteSettings diff --git a/SCrawler.PluginProvider/My Project/AssemblyInfo.vb b/SCrawler.PluginProvider/My Project/AssemblyInfo.vb index 3a3c5ee..fbd419a 100644 --- a/SCrawler.PluginProvider/My Project/AssemblyInfo.vb +++ b/SCrawler.PluginProvider/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler.YouTube/Content/Pictures/StartPic_Green_16.png b/SCrawler.YouTube/Content/Pictures/StartPic_Green_16.png new file mode 100644 index 0000000000000000000000000000000000000000..5e025e6034b4d7f14a181968d39bdc52c938431f GIT binary patch literal 373 zcmV-*0gC>KP)RCr#cld(&~KorKG!6}pAEI1XqrRXFCGPxJYlBG@^^9N+= z)IT6&7cD}UAdX!R3QkJggn}SAITSKw_I{9)kltya4<3Pg@Au_9^5sZ6RoC^ch)i@x zosrVL^nL%n4R|f^Tz6T1mv@=UqVOShKFn}I+*46?B?ONTeWNO?l0@3Dc zBaTAA>IMeZvt#|;cyeGJ_W`!Mt<2Pnr`3T9&q61>cwI>HnC!tYum~&(hvlt+2si@; zYm!I`18b1nX?)X39GN7Jv#YFP&=am;e!l{lCflgy*SWdh-kq7T!*0XK!N2nl)7O{M T#0_6I00000NkvXXu0mjfQ&6S4 literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Downloader/MediaItem.vb b/SCrawler.YouTube/Downloader/MediaItem.vb index e79b510..a7477af 100644 --- a/SCrawler.YouTube/Downloader/MediaItem.vb +++ b/SCrawler.YouTube/Downloader/MediaItem.vb @@ -367,7 +367,7 @@ Namespace DownloadObjects.STDownloader If FileOption = SFO.File And MyContainer.File.Exists(SFO.File, False) Then MyContainer.File.Open(SFO.File,, EDP.ShowMainMsg) ElseIf MyContainer.File.Exists(SFO.Path, False) Then - MyContainer.File.Open(SFO.Path,, EDP.ShowMainMsg) + GlobalOpenPath(MyContainer.File, EDP.ShowMainMsg) Else m.Show() End If diff --git a/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb b/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb index bd7c91f..ec1a631 100644 --- a/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb +++ b/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb @@ -182,7 +182,7 @@ Namespace DownloadObjects.STDownloader ' 'BTT_DOWN ' - Me.BTT_DOWN.Image = CType(resources.GetObject("BTT_DOWN.Image"), System.Drawing.Image) + Me.BTT_DOWN.Image = Global.SCrawler.My.Resources.Resources.StartPic_Green_16 Me.BTT_DOWN.ImageTransparentColor = System.Drawing.Color.Magenta Me.BTT_DOWN.Name = "BTT_DOWN" Me.BTT_DOWN.Size = New System.Drawing.Size(81, 22) @@ -192,6 +192,7 @@ Namespace DownloadObjects.STDownloader 'BTT_STOP ' Me.BTT_STOP.AutoToolTip = False + Me.BTT_STOP.Enabled = False Me.BTT_STOP.Image = CType(resources.GetObject("BTT_STOP.Image"), System.Drawing.Image) Me.BTT_STOP.ImageTransparentColor = System.Drawing.Color.Magenta Me.BTT_STOP.Name = "BTT_STOP" diff --git a/SCrawler.YouTube/Downloader/VideoListForm.resx b/SCrawler.YouTube/Downloader/VideoListForm.resx index 18d3f3c..3e09d16 100644 --- a/SCrawler.YouTube/Downloader/VideoListForm.resx +++ b/SCrawler.YouTube/Downloader/VideoListForm.resx @@ -136,109 +136,25 @@ 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 @@ -257,122 +173,185 @@ 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 - VAUAAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVESURBVEhLjZVtTFNXGMcLQmdHO6AdarLCHOIAgRZ6a2nd + FBwvUrRgSwUdI2pGxRUUX1CiGI3RRKOYLNmH7cP2YctMZtwSXZaNCaNmTiBDBm7iKkWHtZS+Fy57SRbo + 2f+UVmcsG0/yy+095zn/59/nnHsvh8ZQSUmcKTX1w+vLllmupaR8sFEsfgnDMcHJ/4kRhUL5W0rKjdGl + SwevpKXpMRQHooKTNO4Yjfz+kpLPR1pbydiFC2SopSVwTanszhcIVmI6di4rcpgZ5nX7tm0P2XPnyHR7 + O7FUV3vfy8hoTImPT8T0XJHuFSvev3fgAHlw8iQZO3GC2JBsPXyYdCiVNyQCwatIiVhkVKV6A+JW9vx5 + wmIt29ZGpo8fJ/c0Gt+XYvG7SBGCGM51kchGxR+0tJCHR48SG5Imzpwhd1tbA9cUih9yBIJ0JD5VBG0p + tdfU2KhzKj4NcfbQITINY/66OmJKShpGmhIIOF3JyRct9fVkDAWswHbkCLFjgfP0aWJGu7qUyt5sPj8D + ycEiIzJZ6UR1tW3q7FnCwsw0TLFYN71/P5ncvZt0KBTu7SLRZaSWAyFHt3z5yx1y+Y3BnTsDj5A0Dibg + xok98aBlroMHyXUUyePzM39imJJxvd4+depUsCUsHLNoL9vcTKYgbsrL86bzeJ9A2AAkgG44J5YRiTJQ + pNe6fTux79lDHE1NxLlvH3GjmA9ClqamQJ9Kdft+ZaWNPXYs6JjFHItctrHxsXgmj3cRevUgKyQeDYLB + zRMKM7+VyXqGa2sDjoYG4gIeo5F4IeBHMf/evWQKbqeoMO5Z3AfF0d4eudwn4fOpOHW+CiwGT45qKLgK + oXBVl1Taa9+yhbh27CAe4DMYiB8OJ1FwEhs4WVlJ/OvXE79KRbygJz/fnysQXML6XYA654FnxMPBXZ2Y + mNUlkfT+WlAw4926lXjUauJhGOIWi4lbICDu2FjiXrSIWPj8QGdqqk+ZkEA3tAFkg+fBvOLhCLarXyIZ + moCgA2JO4AJUOEhMDBmUSv/SZ2R8jfx3QA5YkHgw7HV1RrtKZXXweM+KAzp2Ny1txqTR3M9MTq7Ekniw + MHG/wXDQXVTkdsD9fOLj4CGwZmWRQa12+M01a3Kx9D9fK8HwGAxtrg0bPI64uGfEXeg9bRcVHwOW6Ghy + B/Tn5gaGqqru6pVK2qb5i/h27TrqLCvzRnLuEonIo7Kyv83p6QEqPhISHwA94CbDzA7RfzJfERzFNkdR + kc/B5weFn2rLkiXEXF7++6l16/oGNm2asGZmkl8geosKg++BiV5Xr579Ua+/XSSR0Bfkk1e9t76+lYo7 + I4knJRFzQcEfjQzThdSmsuzst4YrKu6b0fuweDfoBB3ApFDM9lVVDXys0dCN54IojnfzZpszISGyc4g3 + yGSdSDQCupHCfYWFrw3qdKO3c3Iei38DvgJXwVBu7swtne4ecpcDLseh1Zq9KSkRnRsZ5jskNYbEw18q + 7v7iYuVgVdWoKScn8G/xL6KiyC2pdMakVo8jrwQkcvpqa7dNqNUeD0TDzu+Ulv65J7J4OLjNhYX52FhL + v1RKroTETfn5s5+p1Y/KV678CDlzBRCLO6urj9kqKnyjSmWAOm+Wy7sxPp94OLiGtWtltB3dWGdSqWYv + w/kLPF475rRgrkWIKCY1NX5Ap/v0QUWF61JxMU5fdDPGGcCn8zRpnuBeral5+2etdvymRuMuS0+n3wId + EAN6XB+vpT8SgBxsDF0X+vg/B+jRpCdnE3gFRHzgFgHqmH6s6ZXeLySoCfr+fzEELRgyxuH8Aw1h2aqt + epieAAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3 - FeVFihZsqaBjRM2ouILiC0oUozGaaHxJluzD9mH7sGUmM26JLsuGwqiZE5jKQAcGAR3WUvpeuOwlWaRn - /1NanbFsPMkvt/ec5/yff59z7r08Gn1FRTHmpKRPri1aNHxVJvt4nVT6GoajApP/E0MqFfubTHZ9ZOHC - 3kvJyQYMxYCIwCSNfpNJeKuo6Kuh5mYyeu4cudnU5L/Ksh15ItFSTEfPZIWPQYZ527Z58yPu9GkydfYs - Ga6s9HyYmlovi42Nx/RMkY4lSz66v3cveXjsGBk9epRYkWw5cIC0sux1uUj0JlLCFhlRq9dA3MKdOUM4 - rOVaWsjUkSPkvlbr/UYq/QApYhDFuyaRWKn4w6Ym8ujQIWJF0vjJk6Svudl/VaX6KUskSkHic0XQlmJb - VZWVOqfiUxDn9u8nUzDmq6kh5oSEAaSxQMRrT0w8P1xbS0ZRwAKsBw8SGxY4Tpwgd9GudpbtyhQKU5Ec - KDKkUBSPV1ZaJ0+dIhzMTMEUh3VTe/aQiR07SKtK5doikVxEaikQ8/SLF7/eqlRe/3nbNv9jJI2Bcbhx - YE/caJlz3z5yDUVyhcK0XximaMxgsE0ePx5oCQfHHNrLNTaSSYibc3M9KQLB5xA2AjmgG86LZiSSVBTp - smzZQmw7dxJ7QwNx7N5NXCjmhdBAQ4O/W62+86C83ModPhxwzGGOQy5XX/9UPE0gOA+9WpARFI8EgeDn - isVpVxSKzt7qar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t1Op9MqFQipOnaeD+eDZUQ0GXyUW - p7dnZ3fZNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jMy/PliEQXsH47oM4F4AXxUPCXxcdn - tMvlXf1q9RPPpk3ErdEQN8MQl1RKXCIRcUVHE9e8eWRAKPS3JSV52bg4uqF1IBO8DGYVD0WgXbfk8r5x - CNoh5gBOQIUDREWR3uzsvwypqd8h/32QBeYkHghbTY3JplZb7ALBi+KAjvUnJz8xa7UP0hITy7EkFsxN - 3Gc07nMVFLjscD+b+Bh4BCwZGaRXpxt4Z/nyHCz9z9dKINxGY4tz7Vq3PSbmBXEnek/bRcVHwXBkJOkH - N3Jy/H0VFfcMLEvbNHsR7/bthxwlJZ5wzp0SCXlcUvL33ZQUPxUfCor3gE5gZpjpPvpPZiuCo9hiLyjw - 2oXCgPBzbVmwgAyWlv5+fOXK7p7168ctaWnkV4jeBjfAj7QAZdmy6ZsGw50CuZy+IJ+96j21tc1U3BFO - PCGBDK5a9Uc9w7QjtaEkM/PdgbKyB4PofUi8A7SBVlpEpZrurqjo+UyrpRvPBxE8z4YNVkdcXHjnEK9T - KNqQaAJ0I8W78/Pf6tXrR+5kZT0V/x58Cy6DvvT0J7f1+vvIXQz4PLtON+iRycI6NzHMD0iqD4qHvlT8 - PYWFbG9FxciVrCz/v8W/joggt1HArNGMIa8IxPO6q6s3j2s0bjdEQ877i4v/3BlePBT8xvz8PGzs8K3s - bHIpKG7Oy5v+UqN5XLp06afImSmAmN9WWXnYWlbmvceyfuq8UanswPhs4qHgG1esUNB24OvnN6vV0xfh - /BWB4CzmdGCmRYgIJikptkev/+JhWZnzQmEhTl9kI8YZIKTzNGmW4F+uqnrvrk43dkOrdZWkpNBvgR5I - AT2uT9fSH3FACdYFr3N9/F8C9GjSk7MevAHCPnDzAHVMP9b0Su/nEtQEff+/GoQWDBrj8f4B7pXZMs39 - OqoAAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVBSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC39sVN + xfEiRQu2VNAxomZUXEHxBSWK0RhNNIrJkn3YPmwftsxkxi3RZVEmjJoxgUyROnFBQIe1lL4XLntJFunZ + /5RWZywbT/LL7T3nOf/n3+ecey+HhqW4OM6ckvLp9SVLRq5JJJ+sF4tfw3BMcPJ/YlihUP0mkXSPLl48 + cCk11YChOBAVnKQxaDLxbxYXfz3c0kLGzp0jfc3NgWsqVZdSIFiO6djZrMgxxDBv27dsecSeOUOm29rI + SFWV96P09AZJfHwipmeLdC1b9vH9ffvIw+PHydixY8SGZOvBg6RdpeqWCgRvIiVikVG1+h2IW9mzZwmL + tWxrK5k+epTc12p934rFHyJFCGI410UiGxV/2NxMHh0+TGxImjh1itxuaQlcUyh+yhEI0pD4XBG0pcRe + XW2jzqn4NMTZAwfINIz5a2uJOSnpHtJUQMDpTE4+P1JXR8ZQwApshw4ROxY4T54kFrSrU6Xqzebz05Ec + LDIsk5VMVFXZpk6fJizMTMMUi3XTe/eSyZ07SbtC4d4qEl1EahkQcvRLl77eLpd3927fHniMpHEwATdO + 7IkHLXPt30+uo0g+n59xm2GKxw0G+9SJE8GWsHDMor1sUxOZgrg5P9+bxuN9AWEjkAK64ZxYRiRKR5Fe + 69atxL5rF3E0NhLnnj3EjWI+CN1tbAz0qdV3HlRU2NgjR4KOWcyxyGUbGp6KZ/B456FXB7JC4tEgGNx8 + oTDje5msp7+mJuCorycu4DGZiBcCfhTz795NpuB2igrjnsV9UBzt7ZHLfVI+n4pT55lgIXh2VEPBVQiF + mZ25ub32TZuIa9s24gE+o5H44XASBSexgZMVFcS/di3xq9XEC3qUSn+eQHAB63cA6pwHXhAPB3dFYmJW + p1TaO6hUPvFu3kw8Gg3xMAxxi8XELRAQd2wscS9YQO7y+YGOlBSfKiGBbmg9yAYvgznFwxFs102p1DIB + QQfEnMAFqHCQmBgykJv7lyE9/QryPwA5YF7iwbDX1prsarXVweO9KA7o2GBq6hOzVvsgIzm5AkviwfzE + /UbjfndhodsB93OJj4NHwJqVRQZ0unvvrlyZh6X/+VoJhsdobHWtW+dxxMW9IO5C72m7qPgYGImOJoOg + Oy8vYKms/NWgUtE2zV3Et2PHYWdpqTeSc5dIRB6Xlv5tSUsLUPHhkHg/6AFmhpmx0H8yVxEcxVZHYaHP + wecHhZ9ry6JFZKis7PcTq1f39W/YMGHNyCB3IXoL3AA/0gKUFStmfjYY7hRKpfQF+exV762ra6Hizkji + SUlkaM2aPxoYphOpjaXZ2e/dKy9/MITeh8W7QAdop0UUipm+ysr+z7VauvFcEMXxbtxocyYkRHYO8XqZ + rAOJJkA3UrinoOCtAb1+9E5OzlPxq+A7cBlYMjOf3NLr7yN3KeByHDrdkFciiejcxDA/IKkhJB7+UnH3 + FhWpBiorR6/m5AT+Lf5NVBS5hQJmjWYcecUgkdNXU7NlQqPxeCAadj5YUvLnrsji4eA2FRQosbEjN3Nz + yaWQuFmpnPlKo3lctnz5Z8iZLYBY2FFVdcRWXu4bVKkC1HmTXN6F8bnEw8E1rlolo+24gnVmtXrmIpy/ + wuO1YU4HZluEiGJSUuL79fovH5aXuy4UFeH0RTdhnAF8Ok+T5gju5erq93/R6cZvaLXu0rQ0+i3QAzGg + x/XpWvojAcjB+tB1vo//S4AeTXpyNoA3QMQHbgGgjunHml7p/XyCmqDv/1dD0IIhYxzOP1xr2RYPr44i + AAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3 - FeVFihZsqaBjRM2ouILiC0oUozGaaHxJluzD9mH7sGUmM26JLsuGwqiZE5jKQAcGAR3WUvpeuOwlWaRn - /1NanbFsPMkvt/ec5/yff59z7r08Gn1FRTHmpKRPri1aNHxVJvt4nVT6GoajApP/E0MqFfubTHZ9ZOHC - 3kvJyQYMxYCIwCSNfpNJeKuo6Kuh5mYyeu4cudnU5L/Ksh15ItFSTEfPZIWPQYZ527Z58yPu9GkydfYs - Ga6s9HyYmlovi42Nx/RMkY4lSz66v3cveXjsGBk9epRYkWw5cIC0sux1uUj0JlLCFhlRq9dA3MKdOUM4 - rOVaWsjUkSPkvlbr/UYq/QApYhDFuyaRWKn4w6Ym8ujQIWJF0vjJk6Svudl/VaX6KUskSkHic0XQlmJb - VZWVOqfiUxDn9u8nUzDmq6kh5oSEAaSxQMRrT0w8P1xbS0ZRwAKsBw8SGxY4Tpwgd9GudpbtyhQKU5Ec - KDKkUBSPV1ZaJ0+dIhzMTMEUh3VTe/aQiR07SKtK5doikVxEaikQ8/SLF7/eqlRe/3nbNv9jJI2Bcbhx - YE/caJlz3z5yDUVyhcK0XximaMxgsE0ePx5oCQfHHNrLNTaSSYibc3M9KQLB5xA2AjmgG86LZiSSVBTp - smzZQmw7dxJ7QwNx7N5NXCjmhdBAQ4O/W62+86C83ModPhxwzGGOQy5XX/9UPE0gOA+9WpARFI8EgeDn - isVpVxSKzt7qar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t1Op9MqFQipOnaeD+eDZUQ0GXyUW - p7dnZ3fZNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jMy/PliEQXsH47oM4F4AXxUPCXxcdn - tMvlXf1q9RPPpk3ErdEQN8MQl1RKXCIRcUVHE9e8eWRAKPS3JSV52bg4uqF1IBO8DGYVD0WgXbfk8r5x - CNoh5gBOQIUDREWR3uzsvwypqd8h/32QBeYkHghbTY3JplZb7ALBi+KAjvUnJz8xa7UP0hITy7EkFsxN - 3Gc07nMVFLjscD+b+Bh4BCwZGaRXpxt4Z/nyHCz9z9dKINxGY4tz7Vq3PSbmBXEnek/bRcVHwXBkJOkH - N3Jy/H0VFfcMLEvbNHsR7/bthxwlJZ5wzp0SCXlcUvL33ZQUPxUfCor3gE5gZpjpPvpPZiuCo9hiLyjw - 2oXCgPBzbVmwgAyWlv5+fOXK7p7168ctaWnkV4jeBjfAj7QAZdmy6ZsGw50CuZy+IJ+96j21tc1U3BFO - PCGBDK5a9Uc9w7QjtaEkM/PdgbKyB4PofUi8A7SBVlpEpZrurqjo+UyrpRvPBxE8z4YNVkdcXHjnEK9T - KNqQaAJ0I8W78/Pf6tXrR+5kZT0V/x58Cy6DvvT0J7f1+vvIXQz4PLtON+iRycI6NzHMD0iqD4qHvlT8 - PYWFbG9FxciVrCz/v8W/joggt1HArNGMIa8IxPO6q6s3j2s0bjdEQ877i4v/3BlePBT8xvz8PGzs8K3s - bHIpKG7Oy5v+UqN5XLp06afImSmAmN9WWXnYWlbmvceyfuq8UanswPhs4qHgG1esUNB24OvnN6vV0xfh - /BWB4CzmdGCmRYgIJikptkev/+JhWZnzQmEhTl9kI8YZIKTzNGmW4F+uqnrvrk43dkOrdZWkpNBvgR5I - AT2uT9fSH3FACdYFr3N9/F8C9GjSk7MevAHCPnDzAHVMP9b0Su/nEtQEff+/GoQWDBrj8f4B7pXZMs39 - OqoAAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVBSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC39sVN + xfEiRQu2VNAxomZUXEHxBSWK0RhNNIrJkn3YPmwftsxkxi3RZVEmjJoxgUyROnFBQIe1lL4XLntJFunZ + /5RWZywbT/LL7T3nOf/n3+ecey+HhqW4OM6ckvLp9SVLRq5JJJ+sF4tfw3BMcPJ/YlihUP0mkXSPLl48 + cCk11YChOBAVnKQxaDLxbxYXfz3c0kLGzp0jfc3NgWsqVZdSIFiO6djZrMgxxDBv27dsecSeOUOm29rI + SFWV96P09AZJfHwipmeLdC1b9vH9ffvIw+PHydixY8SGZOvBg6RdpeqWCgRvIiVikVG1+h2IW9mzZwmL + tWxrK5k+epTc12p934rFHyJFCGI410UiGxV/2NxMHh0+TGxImjh1itxuaQlcUyh+yhEI0pD4XBG0pcRe + XW2jzqn4NMTZAwfINIz5a2uJOSnpHtJUQMDpTE4+P1JXR8ZQwApshw4ROxY4T54kFrSrU6Xqzebz05Ec + LDIsk5VMVFXZpk6fJizMTMMUi3XTe/eSyZ07SbtC4d4qEl1EahkQcvRLl77eLpd3927fHniMpHEwATdO + 7IkHLXPt30+uo0g+n59xm2GKxw0G+9SJE8GWsHDMor1sUxOZgrg5P9+bxuN9AWEjkAK64ZxYRiRKR5Fe + 69atxL5rF3E0NhLnnj3EjWI+CN1tbAz0qdV3HlRU2NgjR4KOWcyxyGUbGp6KZ/B456FXB7JC4tEgGNx8 + oTDje5msp7+mJuCorycu4DGZiBcCfhTz795NpuB2igrjnsV9UBzt7ZHLfVI+n4pT55lgIXh2VEPBVQiF + mZ25ub32TZuIa9s24gE+o5H44XASBSexgZMVFcS/di3xq9XEC3qUSn+eQHAB63cA6pwHXhAPB3dFYmJW + p1TaO6hUPvFu3kw8Gg3xMAxxi8XELRAQd2wscS9YQO7y+YGOlBSfKiGBbmg9yAYvgznFwxFs102p1DIB + QQfEnMAFqHCQmBgykJv7lyE9/QryPwA5YF7iwbDX1prsarXVweO9KA7o2GBq6hOzVvsgIzm5AkviwfzE + /UbjfndhodsB93OJj4NHwJqVRQZ0unvvrlyZh6X/+VoJhsdobHWtW+dxxMW9IO5C72m7qPgYGImOJoOg + Oy8vYKms/NWgUtE2zV3Et2PHYWdpqTeSc5dIRB6Xlv5tSUsLUPHhkHg/6AFmhpmx0H8yVxEcxVZHYaHP + wecHhZ9ry6JFZKis7PcTq1f39W/YMGHNyCB3IXoL3AA/0gKUFStmfjYY7hRKpfQF+exV762ra6Hizkji + SUlkaM2aPxoYphOpjaXZ2e/dKy9/MITeh8W7QAdop0UUipm+ysr+z7VauvFcEMXxbtxocyYkRHYO8XqZ + rAOJJkA3UrinoOCtAb1+9E5OzlPxq+A7cBlYMjOf3NLr7yN3KeByHDrdkFciiejcxDA/IKkhJB7+UnH3 + FhWpBiorR6/m5AT+Lf5NVBS5hQJmjWYcecUgkdNXU7NlQqPxeCAadj5YUvLnrsji4eA2FRQosbEjN3Nz + yaWQuFmpnPlKo3lctnz5Z8iZLYBY2FFVdcRWXu4bVKkC1HmTXN6F8bnEw8E1rlolo+24gnVmtXrmIpy/ + wuO1YU4HZluEiGJSUuL79fovH5aXuy4UFeH0RTdhnAF8Ok+T5gju5erq93/R6cZvaLXu0rQ0+i3QAzGg + x/XpWvojAcjB+tB1vo//S4AeTXpyNoA3QMQHbgGgjunHml7p/XyCmqDv/1dD0IIhYxzOP1xr2RYPr44i + AAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3 - FeVFihZsqaBjRM2ouILiC0oUozGaaHxJluzD9mH7sGUmM26JLsuGwqiZE5jKQAcGAR3WUvpeuOwlWaRn - /1NanbFsPMkvt/ec5/yff59z7r08Gn1FRTHmpKRPri1aNHxVJvt4nVT6GoajApP/E0MqFfubTHZ9ZOHC - 3kvJyQYMxYCIwCSNfpNJeKuo6Kuh5mYyeu4cudnU5L/Ksh15ItFSTEfPZIWPQYZ527Z58yPu9GkydfYs - Ga6s9HyYmlovi42Nx/RMkY4lSz66v3cveXjsGBk9epRYkWw5cIC0sux1uUj0JlLCFhlRq9dA3MKdOUM4 - rOVaWsjUkSPkvlbr/UYq/QApYhDFuyaRWKn4w6Ym8ujQIWJF0vjJk6Svudl/VaX6KUskSkHic0XQlmJb - VZWVOqfiUxDn9u8nUzDmq6kh5oSEAaSxQMRrT0w8P1xbS0ZRwAKsBw8SGxY4Tpwgd9GudpbtyhQKU5Ec - KDKkUBSPV1ZaJ0+dIhzMTMEUh3VTe/aQiR07SKtK5doikVxEaikQ8/SLF7/eqlRe/3nbNv9jJI2Bcbhx - YE/caJlz3z5yDUVyhcK0XximaMxgsE0ePx5oCQfHHNrLNTaSSYibc3M9KQLB5xA2AjmgG86LZiSSVBTp - smzZQmw7dxJ7QwNx7N5NXCjmhdBAQ4O/W62+86C83ModPhxwzGGOQy5XX/9UPE0gOA+9WpARFI8EgeDn - isVpVxSKzt7qar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t1Op9MqFQipOnaeD+eDZUQ0GXyUW - p7dnZ3fZNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jMy/PliEQXsH47oM4F4AXxUPCXxcdn - tMvlXf1q9RPPpk3ErdEQN8MQl1RKXCIRcUVHE9e8eWRAKPS3JSV52bg4uqF1IBO8DGYVD0WgXbfk8r5x - CNoh5gBOQIUDREWR3uzsvwypqd8h/32QBeYkHghbTY3JplZb7ALBi+KAjvUnJz8xa7UP0hITy7EkFsxN - 3Gc07nMVFLjscD+b+Bh4BCwZGaRXpxt4Z/nyHCz9z9dKINxGY4tz7Vq3PSbmBXEnek/bRcVHwXBkJOkH - N3Jy/H0VFfcMLEvbNHsR7/bthxwlJZ5wzp0SCXlcUvL33ZQUPxUfCor3gE5gZpjpPvpPZiuCo9hiLyjw - 2oXCgPBzbVmwgAyWlv5+fOXK7p7168ctaWnkV4jeBjfAj7QAZdmy6ZsGw50CuZy+IJ+96j21tc1U3BFO - PCGBDK5a9Uc9w7QjtaEkM/PdgbKyB4PofUi8A7SBVlpEpZrurqjo+UyrpRvPBxE8z4YNVkdcXHjnEK9T - KNqQaAJ0I8W78/Pf6tXrR+5kZT0V/x58Cy6DvvT0J7f1+vvIXQz4PLtON+iRycI6NzHMD0iqD4qHvlT8 - PYWFbG9FxciVrCz/v8W/joggt1HArNGMIa8IxPO6q6s3j2s0bjdEQ877i4v/3BlePBT8xvz8PGzs8K3s - bHIpKG7Oy5v+UqN5XLp06afImSmAmN9WWXnYWlbmvceyfuq8UanswPhs4qHgG1esUNB24OvnN6vV0xfh - /BWB4CzmdGCmRYgIJikptkev/+JhWZnzQmEhTl9kI8YZIKTzNGmW4F+uqnrvrk43dkOrdZWkpNBvgR5I - AT2uT9fSH3FACdYFr3N9/F8C9GjSk7MevAHCPnDzAHVMP9b0Su/nEtQEff+/GoQWDBrj8f4B7pXZMs39 - OqoAAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVBSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC39sVN + xfEiRQu2VNAxomZUXEHxBSWK0RhNNIrJkn3YPmwftsxkxi3RZVEmjJoxgUyROnFBQIe1lL4XLntJFunZ + /5RWZywbT/LL7T3nOf/n3+ecey+HhqW4OM6ckvLp9SVLRq5JJJ+sF4tfw3BMcPJ/YlihUP0mkXSPLl48 + cCk11YChOBAVnKQxaDLxbxYXfz3c0kLGzp0jfc3NgWsqVZdSIFiO6djZrMgxxDBv27dsecSeOUOm29rI + SFWV96P09AZJfHwipmeLdC1b9vH9ffvIw+PHydixY8SGZOvBg6RdpeqWCgRvIiVikVG1+h2IW9mzZwmL + tWxrK5k+epTc12p934rFHyJFCGI410UiGxV/2NxMHh0+TGxImjh1itxuaQlcUyh+yhEI0pD4XBG0pcRe + XW2jzqn4NMTZAwfINIz5a2uJOSnpHtJUQMDpTE4+P1JXR8ZQwApshw4ROxY4T54kFrSrU6Xqzebz05Ec + LDIsk5VMVFXZpk6fJizMTMMUi3XTe/eSyZ07SbtC4d4qEl1EahkQcvRLl77eLpd3927fHniMpHEwATdO + 7IkHLXPt30+uo0g+n59xm2GKxw0G+9SJE8GWsHDMor1sUxOZgrg5P9+bxuN9AWEjkAK64ZxYRiRKR5Fe + 69atxL5rF3E0NhLnnj3EjWI+CN1tbAz0qdV3HlRU2NgjR4KOWcyxyGUbGp6KZ/B456FXB7JC4tEgGNx8 + oTDje5msp7+mJuCorycu4DGZiBcCfhTz795NpuB2igrjnsV9UBzt7ZHLfVI+n4pT55lgIXh2VEPBVQiF + mZ25ub32TZuIa9s24gE+o5H44XASBSexgZMVFcS/di3xq9XEC3qUSn+eQHAB63cA6pwHXhAPB3dFYmJW + p1TaO6hUPvFu3kw8Gg3xMAxxi8XELRAQd2wscS9YQO7y+YGOlBSfKiGBbmg9yAYvgznFwxFs102p1DIB + QQfEnMAFqHCQmBgykJv7lyE9/QryPwA5YF7iwbDX1prsarXVweO9KA7o2GBq6hOzVvsgIzm5AkviwfzE + /UbjfndhodsB93OJj4NHwJqVRQZ0unvvrlyZh6X/+VoJhsdobHWtW+dxxMW9IO5C72m7qPgYGImOJoOg + Oy8vYKms/NWgUtE2zV3Et2PHYWdpqTeSc5dIRB6Xlv5tSUsLUPHhkHg/6AFmhpmx0H8yVxEcxVZHYaHP + wecHhZ9ry6JFZKis7PcTq1f39W/YMGHNyCB3IXoL3AA/0gKUFStmfjYY7hRKpfQF+exV762ra6Hizkji + SUlkaM2aPxoYphOpjaXZ2e/dKy9/MITeh8W7QAdop0UUipm+ysr+z7VauvFcEMXxbtxocyYkRHYO8XqZ + rAOJJkA3UrinoOCtAb1+9E5OzlPxq+A7cBlYMjOf3NLr7yN3KeByHDrdkFciiejcxDA/IKkhJB7+UnH3 + FhWpBiorR6/m5AT+Lf5NVBS5hQJmjWYcecUgkdNXU7NlQqPxeCAadj5YUvLnrsji4eA2FRQosbEjN3Nz + yaWQuFmpnPlKo3lctnz5Z8iZLYBY2FFVdcRWXu4bVKkC1HmTXN6F8bnEw8E1rlolo+24gnVmtXrmIpy/ + wuO1YU4HZluEiGJSUuL79fovH5aXuy4UFeH0RTdhnAF8Ok+T5gju5erq93/R6cZvaLXu0rQ0+i3QAzGg + x/XpWvojAcjB+tB1vo//S4AeTXpyNoA3QMQHbgGgjunHml7p/XyCmqDv/1dD0IIhYxzOP1xr2RYPr44i + AAAAAElFTkSuQmCC diff --git a/SCrawler.YouTube/My Project/AssemblyInfo.vb b/SCrawler.YouTube/My Project/AssemblyInfo.vb index cf910bd..d48214c 100644 --- a/SCrawler.YouTube/My Project/AssemblyInfo.vb +++ b/SCrawler.YouTube/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler.YouTube/My Project/Resources.Designer.vb b/SCrawler.YouTube/My Project/Resources.Designer.vb index c8826fb..3b601a1 100644 --- a/SCrawler.YouTube/My Project/Resources.Designer.vb +++ b/SCrawler.YouTube/My Project/Resources.Designer.vb @@ -150,6 +150,16 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property StartPic_Green_16() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("StartPic_Green_16", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Drawing.Bitmap. ''' diff --git a/SCrawler.YouTube/My Project/Resources.resx b/SCrawler.YouTube/My Project/Resources.resx index 7d3d570..6f8c584 100644 --- a/SCrawler.YouTube/My Project/Resources.resx +++ b/SCrawler.YouTube/My Project/Resources.resx @@ -145,6 +145,9 @@ ..\Content\Pictures\SettingsPic_16.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Content\Pictures\StartPic_Green_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Content\Pictures\VideoCamera_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/SCrawler.YouTube/SCrawler.YouTube.vbproj b/SCrawler.YouTube/SCrawler.YouTube.vbproj index 8cc5d22..abc229f 100644 --- a/SCrawler.YouTube/SCrawler.YouTube.vbproj +++ b/SCrawler.YouTube/SCrawler.YouTube.vbproj @@ -314,5 +314,8 @@ + + + \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb b/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb index b9a296a..e83e5cd 100644 --- a/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb +++ b/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler/API/Base/M3U8Base.vb b/SCrawler/API/Base/M3U8Base.vb index a712f4a..4ebfff5 100644 --- a/SCrawler/API/Base/M3U8Base.vb +++ b/SCrawler/API/Base/M3U8Base.vb @@ -33,40 +33,55 @@ Namespace API.Base End If End Function Friend Shared Function Download(ByVal URLs As List(Of String), ByVal DestinationFile As SFile, Optional ByVal Responser As Responser = Nothing, - Optional ByVal Token As CancellationToken = Nothing, Optional ByVal Progress As MyProgress = Nothing) As SFile + Optional ByVal Token As CancellationToken = Nothing, Optional ByVal Progress As MyProgress = Nothing, + Optional ByVal UsePreProgress As Boolean = True) As SFile Dim Cache As CacheKeeper = Nothing - Try - If URLs.ListExists Then - Dim ConcatFile As SFile = DestinationFile - If ConcatFile.Name.IsEmptyString Then ConcatFile.Name = "PlayListFile" - ConcatFile.Extension = "mp4" - Cache = New CacheKeeper($"{DestinationFile.PathWithSeparator}_{TempCacheFolderName}\") - Dim cache2 As CacheKeeper = Cache.NewInstance - If cache2.RootDirectory.Exists(SFO.Path) Then - Dim progressExists As Boolean = Not Progress Is Nothing - If progressExists Then Progress.Maximum += URLs.Count - Dim p As SFileNumbers = SFileNumbers.Default(ConcatFile.Name) - ConcatFile = SFile.IndexReindex(ConcatFile,,, p, EDP.ReturnValue) - Dim i% - Dim dFile As SFile = cache2.RootDirectory - dFile.Extension = "ts" - Using w As New DownloadObjects.WebClient2(Responser) - For i = 0 To URLs.Count - 1 - If progressExists Then Progress.Perform() - Token.ThrowIfCancellationRequested() - dFile.Name = $"ConPart_{i}" - w.DownloadFile(URLs(i), dFile) - cache2.AddFile(dFile, True) - Next - End Using - DestinationFile = FFMPEG.ConcatenateFiles(cache2, Settings.FfmpegFile.File, ConcatFile, Settings.CMDEncoding, p, EDP.ThrowException) - Return DestinationFile + Using tmpPr As New PreProgress(Progress) + Try + If URLs.ListExists Then + Dim ConcatFile As SFile = DestinationFile + If ConcatFile.Name.IsEmptyString Then ConcatFile.Name = "PlayListFile" + ConcatFile.Extension = "mp4" + Cache = New CacheKeeper($"{DestinationFile.PathWithSeparator}_{TempCacheFolderName}\") + Dim cache2 As CacheKeeper = Cache.NewInstance + If cache2.RootDirectory.Exists(SFO.Path) Then + Dim progressExists As Boolean = Not Progress Is Nothing + If progressExists Then + If UsePreProgress Then + tmpPr.ChangeMax(URLs.Count) + Else + Progress.Maximum += URLs.Count + End If + End If + Dim p As SFileNumbers = SFileNumbers.Default(ConcatFile.Name) + ConcatFile = SFile.IndexReindex(ConcatFile,,, p, EDP.ReturnValue) + Dim i% + Dim dFile As SFile = cache2.RootDirectory + dFile.Extension = "ts" + Using w As New DownloadObjects.WebClient2(Responser) + For i = 0 To URLs.Count - 1 + If progressExists Then + If UsePreProgress Then + tmpPr.Perform() + Else + Progress.Perform() + End If + End If + Token.ThrowIfCancellationRequested() + dFile.Name = $"ConPart_{i}" + w.DownloadFile(URLs(i), dFile) + cache2.AddFile(dFile, True) + Next + End Using + DestinationFile = FFMPEG.ConcatenateFiles(cache2, Settings.FfmpegFile.File, ConcatFile, Settings.CMDEncoding, p, EDP.ThrowException) + Return DestinationFile + End If End If - End If - Return Nothing - Finally - Cache.DisposeIfReady - End Try + Return Nothing + Finally + Cache.DisposeIfReady + End Try + End Using End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Base/UserDataBase.vb b/SCrawler/API/Base/UserDataBase.vb index e6e3c85..a37d02e 100644 --- a/SCrawler/API/Base/UserDataBase.vb +++ b/SCrawler/API/Base/UserDataBase.vb @@ -146,7 +146,18 @@ Namespace API.Base Return HOST.Name End Get End Property + Private _Progress As MyProgress Friend Property Progress As MyProgress + Get + Return _Progress + End Get + Set(ByVal p As MyProgress) + _Progress = p + If Not ProgressPre Is Nothing Then ProgressPre.Reset() : ProgressPre.Dispose() + ProgressPre = New PreProgress(_Progress) + End Set + End Property + Protected Property ProgressPre As PreProgress = Nothing #End Region #Region "User name, ID, exist, suspend" Friend User As UserInfo @@ -566,6 +577,8 @@ BlockNullPicture: #Region "Plugins Support" Protected Event ProgressChanged As IPluginContentProvider.ProgressChangedEventHandler Implements IPluginContentProvider.ProgressChanged Protected Event ProgressMaximumChanged As IPluginContentProvider.ProgressMaximumChangedEventHandler Implements IPluginContentProvider.ProgressMaximumChanged + Protected Event ProgressPreChanged As IPluginContentProvider.ProgressChangedEventHandler Implements IPluginContentProvider.ProgressPreChanged + Protected Event ProgressPreMaximumChanged As IPluginContentProvider.ProgressMaximumChangedEventHandler Implements IPluginContentProvider.ProgressPreMaximumChanged Private Property IPluginContentProvider_Settings As ISiteSettings Implements IPluginContentProvider.Settings Get Return HOST.Source @@ -911,6 +924,7 @@ BlockNullPicture: Private _PictureExists As Boolean Private _EnvirInvokeUserUpdated As Boolean = False Protected Sub EnvirDownloadSet() + ProgressPre.Reset() UpdateDataFiles() _DownloadInProgress = True _DescriptionChecked = False @@ -962,10 +976,12 @@ BlockNullPicture: If Not DownloadMissingOnly Then ThrowAny(Token) DownloadDataF(Token) + ProgressPre.Done() ThrowAny(Token) - If Settings.ReparseMissingInTheRoutine Then ReparseMissing(Token) : ThrowAny(Token) + If Settings.ReparseMissingInTheRoutine Then ReparseMissing(Token) : ProgressPre.Done() : ThrowAny(Token) Else ReparseMissing(Token) + ProgressPre.Done() End If If _TempMediaList.Count > 0 Then @@ -976,9 +992,10 @@ BlockNullPicture: End If ReparseVideo(Token) + ProgressPre.Done() ThrowAny(Token) - If UseMD5Comparison Then ValidateMD5(Token) : ThrowAny(Token) + If UseMD5Comparison Then ValidateMD5(Token) : ProgressPre.Done() : ThrowAny(Token) If _TempPostsList.Count > 0 And Not DownloadMissingOnly And __SaveData Then _ TextSaver.SaveTextToFile(_TempPostsList.ListToString(Environment.NewLine), MyFilePosts, True,, EDP.None) @@ -1031,6 +1048,7 @@ BlockNullPicture: DownloadMissingOnly = False _ForceSaveUserData = False _ForceSaveUserInfo = False + ProgressPre.Done() End Try End Sub Protected Sub UpdateDataFiles() @@ -1064,8 +1082,6 @@ BlockNullPicture: If Not HOST Is Nothing AndAlso Not HOST.Responser Is Nothing Then Responser.Copy(HOST.Responser) SeparateVideoFolder = False IsSingleObjectDownload = True - UseInternalDownloadFileFunction_UseProgress = True - UseInternalM3U8Function_UseProgress = True DownloadSingleObject_GetPosts(Data, Token) DownloadSingleObject_CreateMedia(Data, Token) DownloadSingleObject_Download(Data, Token) @@ -1157,15 +1173,17 @@ BlockNullPicture: ImgFormat = GetImageFormat(__data.File) End If If ImgFormat Is Nothing Then ImgFormat = Imaging.ImageFormat.Jpeg - If IsUrl Then - hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL_BASE.IfNullOrEmpty(__data.URL), ErrMD5), ImgFormat, ErrMD5)) + If IsUrl And Not __isGif Then + hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL.IfNullOrEmpty(__data.URL_BASE), ErrMD5), ImgFormat, ErrMD5)) + ElseIf IsUrl And __isGif Then + hash = ByteArrayToString(GetMD5FromBytes(SFile.GetBytesFromNet(__data.URL.IfNullOrEmpty(__data.URL_BASE), ErrMD5), ErrMD5)) Else hash = ByteArrayToString(GetMD5(SFile.GetBytes(__data.File, ErrMD5), ImgFormat, ErrMD5)) End If If hash.IsEmptyString And Not __isGif Then If ImgFormat Is Imaging.ImageFormat.Jpeg Then ImgFormat = Imaging.ImageFormat.Png Else ImgFormat = Imaging.ImageFormat.Jpeg If IsUrl Then - hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL_BASE.IfNullOrEmpty(__data.URL), ErrMD5), ImgFormat, ErrMD5)) + hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL.IfNullOrEmpty(__data.URL_BASE), ErrMD5), ImgFormat, ErrMD5)) Else hash = ByteArrayToString(GetMD5(SFile.GetBytes(__data.File, ErrMD5), ImgFormat, ErrMD5)) End If @@ -1186,7 +1204,9 @@ BlockNullPicture: _ForceSaveUserInfo = True If existingFiles.Count > 0 Then Dim h$ + ProgressPre.ChangeMax(existingFiles.Count) For i = existingFiles.Count - 1 To 0 Step -1 + ProgressPre.Perform() h = __getMD5(New UserMedia With {.File = existingFiles(i)}, False) If Not h.IsEmptyString Then If hashList.ContainsKey(h) Then @@ -1200,8 +1220,10 @@ BlockNullPicture: Next End If End If + ProgressPre.ChangeMax(_ContentList.Count) For i = 0 To _ContentList.Count - 1 data = _ContentList(i) + ProgressPre.Perform() If (data.Type = UTypes.GIF Or data.Type = UTypes.Picture) Then If data.MD5.IsEmptyString Then ThrowAny(Token) @@ -1215,8 +1237,10 @@ BlockNullPicture: End If Next If existingFiles.Count > 0 Then + ProgressPre.ChangeMax(existingFiles.Count) For i = 0 To existingFiles.Count - 1 f = existingFiles(i) + ProgressPre.Perform() data = New UserMedia(f.File) With { .State = UStates.Downloaded, .Type = IIf(f.Extension = "gif", UTypes.GIF, UTypes.Picture), @@ -1238,7 +1262,9 @@ BlockNullPicture: End With End If + ProgressPre.ChangeMax(_TempMediaList.Count) For i = _TempMediaList.Count - 1 To 0 Step -1 + ProgressPre.Perform() If limit > 0 And itemsCount >= limit Then _TempMediaList.RemoveAt(i) Else @@ -1262,6 +1288,8 @@ BlockNullPicture: Catch iex As ArgumentOutOfRangeException When Disposed Catch ex As Exception ProcessException(ex, Token, "ValidateMD5",, VALIDATE_MD5_ERROR) + Finally + ProgressPre.Done() End Try End Sub #End Region @@ -1434,13 +1462,11 @@ BlockNullPicture: End Try End Sub Protected UseInternalM3U8Function As Boolean = False - Protected UseInternalM3U8Function_UseProgress As Boolean = False Protected Overridable Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile Return Nothing End Function Protected UseInternalDownloadFileFunction As Boolean = False - Protected UseInternalDownloadFileFunction_UseProgress As Boolean = False Protected Overridable Function DownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile Return Nothing @@ -1798,6 +1824,7 @@ BlockNullPicture: LatestData.Clear() _TempMediaList.Clear() _TempPostsList.Clear() + If Not ProgressPre Is Nothing Then ProgressPre.Reset() : ProgressPre.Dispose() If Not Responser Is Nothing Then Responser.Dispose() If Not BTT_CONTEXT_DOWN Is Nothing Then BTT_CONTEXT_DOWN.Dispose() If Not BTT_CONTEXT_EDIT Is Nothing Then BTT_CONTEXT_EDIT.Dispose() diff --git a/SCrawler/API/Instagram/UserData.vb b/SCrawler/API/Instagram/UserData.vb index ad78fdd..35a38e8 100644 --- a/SCrawler/API/Instagram/UserData.vb +++ b/SCrawler/API/Instagram/UserData.vb @@ -207,19 +207,21 @@ Namespace API.Instagram If dt.Invoke And Not LastCursor.IsEmptyString Then s = IIf(IsSavedPosts, Sections.SavedPosts, Sections.Timeline) DownloadData(LastCursor, s, Token) + ProgressPre.Done() ThrowAny(Token) If Not HasError Then FirstLoadingDone = True End If If dt.Invoke And Not HasError Then s = IIf(IsSavedPosts, Sections.SavedPosts, Sections.Timeline) DownloadData(String.Empty, s, Token) + ProgressPre.Done() ThrowAny(Token) If Not HasError Then FirstLoadingDone = True End If If FirstLoadingDone Then LastCursor = String.Empty If Not IsSavedPosts AndAlso MySiteSettings.BaseAuthExists() Then - If CBool(MySiteSettings.DownloadStories.Value) And GetStories Then s = Sections.Stories : DownloadData(String.Empty, s, Token) - If CBool(MySiteSettings.DownloadTagged.Value) And ACheck(MySiteSettings.HashTagged.Value) And GetTaggedData Then s = Sections.Tagged : DownloadData(String.Empty, s, Token) + If CBool(MySiteSettings.DownloadStories.Value) And GetStories Then s = Sections.Stories : DownloadData(String.Empty, s, Token) : ProgressPre.Done() + If CBool(MySiteSettings.DownloadTagged.Value) And ACheck(MySiteSettings.HashTagged.Value) And GetTaggedData Then s = Sections.Tagged : DownloadData(String.Empty, s, Token) : ProgressPre.Done() End If If WaitNotificationMode = WNM.SkipTemp Or WaitNotificationMode = WNM.SkipCurrent Then WaitNotificationMode = WNM.Notify Catch eex As ExitException @@ -229,9 +231,24 @@ Namespace API.Instagram Finally E560Thrown = False UpdateResponser() + ValidateExtension() If Not errorFound Then LoadSavePostsKV(False) End Try End Sub + Private Sub ValidateExtension() + Try + Const heic$ = "heic" + If _TempMediaList.Count > 0 AndAlso _TempMediaList.Exists(Function(mm) mm.File.Extension = heic) Then + Dim m As UserMedia + For i% = 0 To _TempMediaList.Count - 1 + m = _TempMediaList(i) + If m.Type = UTypes.Picture AndAlso Not m.File.Extension.IsEmptyString AndAlso m.File.Extension = heic Then _ + m.File.Extension = "jpg" : _TempMediaList(i) = m + Next + End If + Catch ex As Exception + End Try + End Sub Private Sub UpdateResponser() Try If _DownloadingInProgress AndAlso Not Responser Is Nothing AndAlso Not Responser.Disposed Then @@ -470,7 +487,9 @@ Namespace API.Instagram HasNextPage = False End If If If(.Item("edges")?.Count, 0) > 0 Then + ProgressPre.ChangeMax(.Item("edges").Count) For Each nn In .Item("edges") + ProgressPre.Perform() PostIDKV = New PostKV(Section) If nn.Count > 0 AndAlso nn(0).Count > 0 Then With nn(0) @@ -527,6 +546,7 @@ Namespace API.Instagram Dim URL$ = String.Empty Dim dValue% = 1 Dim _Index% = 0 + If PostsToReparse.Count > 0 Then ProgressPre.ChangeMax(PostsToReparse.Count) Try Do While dValue = 1 ThrowAny(Token) @@ -538,7 +558,9 @@ Namespace API.Instagram Dim j As EContainer, jj As EContainer If PostsToReparse.Count > 0 And _Index <= PostsToReparse.Count - 1 Then Dim e As New ErrorsDescriber(EDP.ThrowException) + If Index > 0 Then ProgressPre.ChangeMax(1) For i% = _Index To PostsToReparse.Count - 1 + ProgressPre.Perform() _Index = i URL = $"https://www.instagram.com/api/v1/media/{PostsToReparse(i).ID}/info/" ThrowAny(Token) @@ -611,7 +633,9 @@ Namespace API.Instagram Case Else : SpecFolder = String.Empty End Select End If + ProgressPre.ChangeMax(Items.Count) For Each nn In Items + ProgressPre.Perform() With nn PostIDKV = New PostKV(.Value("code"), .Value("id"), Section) Pinned = .Contains("timeline_pinned_user_ids") @@ -799,7 +823,9 @@ Namespace API.Instagram If Not r.IsEmptyString Then Using j As EContainer = JsonDocument.Parse(r).XmlIfNothing If j.Contains("reels") Then + ProgressPre.ChangeMax(j("reels").Count) For Each jj In j("reels") + ProgressPre.Perform() i += 1 sFolder = jj.Value("title").StringRemoveWinForbiddenSymbols storyID = jj.Value("id").Replace("highlight:", String.Empty) diff --git a/SCrawler/API/LPSG/UserData.vb b/SCrawler/API/LPSG/UserData.vb index 918bab9..949eba9 100644 --- a/SCrawler/API/LPSG/UserData.vb +++ b/SCrawler/API/LPSG/UserData.vb @@ -73,7 +73,9 @@ Namespace API.LPSG Dim r As Func(Of String, Integer, String) Dim indx% = 0 Dim ude As New ErrorsDescriber(EDP.ReturnValue) + ProgressPre.ChangeMax(l.Count) For Each url$ In l + ProgressPre.Perform() If Not url.IsEmptyString Then u = SymbolsConverter.Decode(url, {Converters.HTML, Converters.ASCII}, ude) Else u = String.Empty If Not u.IsEmptyString Then exists = Not IsEmptyString(RegexReplace(u, FileExistsRegEx)) diff --git a/SCrawler/API/Mastodon/SiteSettings.vb b/SCrawler/API/Mastodon/SiteSettings.vb index 4cd056e..2ff6805 100644 --- a/SCrawler/API/Mastodon/SiteSettings.vb +++ b/SCrawler/API/Mastodon/SiteSettings.vb @@ -124,6 +124,15 @@ Namespace API.Mastodon If _SiteEditorFormOpened Then Dim tf$ = GifsSpecialFolder.Value If Not tf.IsEmptyString Then tf = tf.StringTrim("\") : GifsSpecialFolder.Value = tf + Dim md$ = AConvert(Of String)(MyDomain.Value, String.Empty) + If Not md.IsEmptyString AndAlso Not Domains.Domains.Contains(md) AndAlso Not Domains.DomainsTemp.Contains(md) Then + If Domains.Changed Then + Domains.DomainsTemp.Add(md) + Else + Domains.Domains.Add(md) + Domains.Save() + End If + End If End If MyBase.Update() End Sub diff --git a/SCrawler/API/Mastodon/UserData.vb b/SCrawler/API/Mastodon/UserData.vb index 3c7a4ad..d8f4487 100644 --- a/SCrawler/API/Mastodon/UserData.vb +++ b/SCrawler/API/Mastodon/UserData.vb @@ -120,7 +120,9 @@ Namespace API.Mastodon If Not r.IsEmptyString Then Using j As EContainer = JsonDocument.Parse(r) If If(j?.Count, 0) > 0 Then + ProgressPre.ChangeMax(j.Count) For Each jj As EContainer In j + ProgressPre.Perform() With jj If Not IsSavedPosts And POST.IsEmptyString And Not .Item("account") Is Nothing Then With .Item("account") @@ -166,7 +168,7 @@ Namespace API.Mastodon If If(.Item("media_attachments")?.Count, 0) > 0 Then s = .Item("media_attachments") Else - s = .Item({"reblog", "account"}, "media_attachments") + s = .Item({"reblog"}, "media_attachments") End If If s.ListExists Then For Each ss In s : ObtainMedia(ss, PostID, PostDate) : Next @@ -268,7 +270,7 @@ Namespace API.Mastodon If Responser.Status = Net.WebExceptionStatus.NameResolutionFailure Then MyMainLOG = $"User domain ({UserDomain}) not found: {ToStringForLog()}" Return 1 - ElseIf Responser.StatusCode = Net.HttpStatusCode.NotFound Then + ElseIf Responser.StatusCode = Net.HttpStatusCode.NotFound Or Responser.StatusCode = Net.HttpStatusCode.Forbidden Then UserExists = False Return 1 ElseIf Responser.StatusCode = Net.HttpStatusCode.Unauthorized Then diff --git a/SCrawler/API/Pinterest/SiteSettings.vb b/SCrawler/API/Pinterest/SiteSettings.vb index 7eaedd7..6516d83 100644 --- a/SCrawler/API/Pinterest/SiteSettings.vb +++ b/SCrawler/API/Pinterest/SiteSettings.vb @@ -62,8 +62,7 @@ Namespace API.Pinterest Return New UserData End Function Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean - Return Settings.GalleryDLFile.Exists And (Not What = ISiteSettings.Download.SavedPosts OrElse - (Responser.CookiesExists And ACheck(SavedPostsUserName.Value))) + Return Settings.GalleryDLFile.Exists And (Not What = ISiteSettings.Download.SavedPosts OrElse ACheck(SavedPostsUserName.Value)) End Function #End Region #Region "IsMyUser, IsMyImageVideo, GetUserUrl, GetUserPostUrl" diff --git a/SCrawler/API/Pinterest/UserData.vb b/SCrawler/API/Pinterest/UserData.vb index c46b2fe..a829522 100644 --- a/SCrawler/API/Pinterest/UserData.vb +++ b/SCrawler/API/Pinterest/UserData.vb @@ -113,6 +113,8 @@ Namespace API.Pinterest End If Catch ex As Exception ProcessException(ex, Token, $"data downloading error [{URL}]") + Finally + ProgressPre.Done() End Try End Sub #End Region @@ -129,7 +131,9 @@ Namespace API.Pinterest Dim urls As List(Of String) = GetDataFromGalleryDL(URL, True) If urls.ListExists Then urls.RemoveAll(Function(__url) Not __url.Contains("BoardsResource/get/")) If urls.ListExists Then + ProgressPre.ChangeMax(urls.Count) For Each URL In urls + ProgressPre.Perform() ThrowAny(Token) r = Responser.GetResponse(URL,, EDP.ReturnValue) If Not r.IsEmptyString Then @@ -176,14 +180,18 @@ Namespace API.Pinterest Dim l As List(Of String) = GetDataFromGalleryDL(Board.URL, False) If l.ListExists Then l.RemoveAll(Function(ll) Not ll.Contains("BoardFeedResource/get/")) If l.ListExists Then + ProgressPre.ChangeMax(l.Count) For Each bUrl In l + ProgressPre.Perform() ThrowAny(Token) r = Responser.GetResponse(bUrl,, EDP.ReturnValue) If Not r.IsEmptyString Then j = JsonDocument.Parse(r, jErr) If Not j Is Nothing Then If If(j(rootNode)?.Count, 0) > 0 Then + ProgressPre.ChangeMax(j(rootNode).Count) For Each jj In j(rootNode) + ProgressPre.Perform() With jj If .Contains("images") Then images = .Item("images").Select(imgSelector).ToList diff --git a/SCrawler/API/PornHub/Declarations.vb b/SCrawler/API/PornHub/Declarations.vb index 4ceeec5..30800e5 100644 --- a/SCrawler/API/PornHub/Declarations.vb +++ b/SCrawler/API/PornHub/Declarations.vb @@ -13,9 +13,13 @@ Namespace API.PornHub Private ReadOnly UnicodeHexConverter As Func(Of String, String) = Function(Input) SymbolsConverter.UnicodeHex.Decode(Input, EDP.ReturnValue) #End Region #Region "Declarations video" - Friend ReadOnly RegexVideo_FlashVarsBlock As RParams = RParams.DM("(?<=flashvars_\['[nN]ext[vV]ideo'\];[\r\n]*?)(.+?)(?=;flashvars_\d+?)", 0, EDP.ReturnValue) + Friend ReadOnly RegexVideo_FlashVarsBlocks As RParams = RParams.DM("(?<=(flashvars_\['[nN]ext[vV]ideo'\]|flashvars_\d+[^ ]+? = media_\d+?);[\r\n]*?)(.+?)(?=;flashvars_\d+?)", + 0, RegexReturn.List, EDP.ReturnValue) + 'TODELETE: PornHub old 'RegexVideo_FlashVarsBlock' declaration + 'Friend ReadOnly RegexVideo_FlashVarsBlock As RParams = RParams.DM("(?<=flashvars_\['[nN]ext[vV]ideo'\];[\r\n]*?)(.+?)(?=;flashvars_\d+?)", 0, EDP.ReturnValue) Friend ReadOnly RegexVideo_FlashVars_Vars As RParams = RParams.DM("var ([\w\d]{10,})=("".+?)(?=(;|\Z))", 0, RegexReturn.List) Friend ReadOnly RegexVideo_FlashVars_Compiler As RParams = RParams.DM("(?<=\*/)([\w\d\S]{10,})", 0, RegexReturn.List) + Friend ReadOnly RegexVideo_FlashVars_UrlResolution As RParams = RParams.DMS("/(\d+)[^/]+\.mp4", 1, EDP.ReturnValue) Friend ReadOnly RegexVideo_Video_All As RParams = RParams.DM("div class=""thumbnail-info-wrapper clearfix.+?[\r\n\s]*?\ 1080)) + If files.ListExists Then + files.Sort() + file = files(0).Data + Else + file = RegexReplace(r, Regex_M3U8_FirstFileRegEx) + End If If Not file.IsEmptyString Then Dim NewUrl$ = M3U8Base.CreateUrl(appender, file) If Not NewUrl.IsEmptyString Then @@ -37,9 +45,9 @@ Namespace API.PornHub End If Return Nothing End Function - Friend Shared Function Download(ByVal URL As String, ByVal Responser As Responser, ByVal Destination As SFile, - ByVal Token As CancellationToken, ByVal Progress As MyProgress) As SFile - Return M3U8Base.Download(GetUrlsList(URL, Responser), Destination, Responser, Token, Progress) + Friend Shared Function Download(ByVal URL As String, ByVal Responser As Responser, ByVal Destination As SFile, ByVal DownloadUHD As Boolean, + ByVal Token As CancellationToken, ByVal Progress As MyProgress, ByVal UsePreProgress As Boolean) As SFile + Return M3U8Base.Download(GetUrlsList(URL, Responser, DownloadUHD), Destination, Responser, Token, Progress, UsePreProgress) End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/PornHub/SiteSettings.vb b/SCrawler/API/PornHub/SiteSettings.vb index 76b6e44..e3d6969 100644 --- a/SCrawler/API/PornHub/SiteSettings.vb +++ b/SCrawler/API/PornHub/SiteSettings.vb @@ -25,6 +25,8 @@ Namespace API.PornHub Return My.Resources.SiteResources.PornHubPic_16 End Get End Property + + Friend Property DownloadUHD As PropertyValue Friend ReadOnly Property DownloadGifs As PropertyValue @@ -41,6 +43,7 @@ Namespace API.PornHub MyBase.New("PornHub", "pornhub.com") With Responser : .CurlSslNoRevoke = True : .CurlInsecure = True : End With + DownloadUHD = New PropertyValue(False) DownloadGifsAsMp4 = New PropertyValue(True) DownloadGifs = New PropertyValue(CInt(CheckState.Indeterminate), GetType(Integer)) DownloadPhotoOnlyFromModelHub = New PropertyValue(True) diff --git a/SCrawler/API/PornHub/UserData.vb b/SCrawler/API/PornHub/UserData.vb index da31b0c..c2f21d7 100644 --- a/SCrawler/API/PornHub/UserData.vb +++ b/SCrawler/API/PornHub/UserData.vb @@ -23,6 +23,7 @@ Namespace API.PornHub Private Const Name_NameTrue As String = "NameTrue" Private Const Name_VideoPageModel As String = "VideoPageModel" Private Const Name_PhotoPageModel As String = "PhotoPageModel" + Private Const Name_DownloadUHD As String = "DownloadUHD" Private Const Name_DownloadGifs As String = "DownloadGifs" Private Const Name_DownloadPhotoOnlyFromModelHub As String = "DownloadPhotoOnlyFromModelHub" #End Region @@ -112,6 +113,7 @@ Namespace API.PornHub #Region "Advanced fields" Friend Property VideoPageModel As VideoPageModels = VideoPageModels.Undefined Private Property PhotoPageModel As PhotoPageModels = PhotoPageModels.Undefined + Friend Property DownloadUHD As Boolean = False Friend Property DownloadGifs As Boolean Friend Property DownloadPhotoOnlyFromModelHub As Boolean = True #End Region @@ -122,6 +124,7 @@ Namespace API.PornHub Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object) If Not Obj Is Nothing AndAlso TypeOf Obj Is UserExchangeOptions Then With DirectCast(Obj, UserExchangeOptions) + DownloadUHD = .DownloadUHD DownloadGifs = .DownloadGifs DownloadPhotoOnlyFromModelHub = .DownloadPhotoOnlyFromModelHub End With @@ -157,6 +160,7 @@ Namespace API.PornHub NameTrue = .Value(Name_NameTrue) VideoPageModel = .Value(Name_VideoPageModel).FromXML(Of Integer)(VideoPageModels.Undefined) PhotoPageModel = .Value(Name_PhotoPageModel).FromXML(Of Integer)(PhotoPageModels.Undefined) + DownloadUHD = .Value(Name_DownloadUHD).FromXML(Of Boolean)(False) DownloadGifs = .Value(Name_DownloadGifs).FromXML(Of Integer)(False) DownloadPhotoOnlyFromModelHub = .Value(Name_DownloadPhotoOnlyFromModelHub).FromXML(Of Boolean)(True) SetNames.Invoke() @@ -166,6 +170,7 @@ Namespace API.PornHub .Add(Name_NameTrue, NameTrue) .Add(Name_VideoPageModel, CInt(VideoPageModel)) .Add(Name_PhotoPageModel, CInt(PhotoPageModel)) + .Add(Name_DownloadUHD, DownloadUHD.BoolToInteger) .Add(Name_DownloadGifs, DownloadGifs.BoolToInteger) .Add(Name_DownloadPhotoOnlyFromModelHub, DownloadPhotoOnlyFromModelHub.BoolToInteger) End If @@ -224,6 +229,7 @@ Namespace API.PornHub Finally Responser.Mode = Responser.Modes.Default Responser.Method = "GET" + ProgressPre.Done() End Try End Sub #End Region @@ -246,6 +252,7 @@ Namespace API.PornHub Const VideoUrlPattern$ = "https://www.pornhub.com/{0}/{1}{2}{3}" Const HtmlPageNotFoundVideo$ = "Error Page Not Found" Dim URL$ = String.Empty + ProgressPre.ChangeMax(1) Try Dim p$ If PersonType = PersonTypeUser Then @@ -289,6 +296,8 @@ Namespace API.PornHub End If Catch ex As Exception Return ProcessException(ex, Token, $"videos downloading error [{URL}]") + Finally + ProgressPre.Perform() End Try End Function #End Region @@ -306,11 +315,13 @@ Namespace API.PornHub Dim l3 As List(Of String) = Nothing If l.ListExists Then l2 = l.Select(Function(ll) $"gif/{ll.Arr(0).Replace("gif", String.Empty)}").ToList If l2.ListExists Then + ProgressPre.ChangeMax(l2.Count) For Each gif$ In l2 If Not _TempPostsList.Contains(gif) Then _TempPostsList.Add(gif) URL = $"https://www.pornhub.com/{gif}" m = New UserMedia(URL, UTypes.Video) With {.Post = gif, .SpecialFolder = "GIFs\"} + ProgressPre.Perform() ThrowAny(Token) Try r = Responser.GetResponse(URL) @@ -385,8 +396,10 @@ Namespace API.PornHub Dim l As List(Of PhotoBlock) = RegexFields(Of PhotoBlock)(r, {Regex_Photo_ModelHub_PhotoBlocks}, {1, 2}) If l.ListExists Then l.RemoveAll(Function(ll) ll.Data.IsEmptyString) If l.ListExists Then + ProgressPre.ChangeMax(l.Count) Dim albumRegex As RParams = RParams.DMS("", 1, EDP.ReturnValue) For Each block As PhotoBlock In l + ProgressPre.Perform() If Not _TempPostsList.Contains(block.AlbumID) Then _TempPostsList.Add(block.AlbumID) Else Continue For albumRegex.Pattern = "
  • [\r\n\s]*?
    [\r\n\s]*?\<[^\>]*?alt=""([^""]*)""" albumName = StringTrim(RegexReplace(r, albumRegex)) @@ -421,7 +434,9 @@ Namespace API.PornHub Dim l As List(Of PhotoBlock) = RegexFields(Of PhotoBlock)(r, {Regex_Photo_PornHub_PhotoBlocks}, {2, 1}) If l.ListExists Then l.RemoveAll(Function(ll) ll.AlbumID.IsEmptyString) If l.ListExists Then + ProgressPre.ChangeMax(l.Count) For Each block As PhotoBlock In l + ProgressPre.Perform() If Not _TempPostsList.Contains(block.AlbumID) Then _TempPostsList.Add(block.AlbumID) Else Continue For albumName = block.Data If albumName.IsEmptyString Then @@ -449,7 +464,9 @@ Namespace API.PornHub Dim l As List(Of String) = RegexReplace(r, Regex_Photo_PornHub_AlbumPhotoArr) If l.ListExists Then l.RemoveAll(Function(_url) _url.IsEmptyString) If l.ListExists Then + ProgressPre.ChangeMax(l.Count) For Each url$ In l + ProgressPre.Perform() ThrowAny(Token) Try r = Responser.GetResponse(url) @@ -492,7 +509,9 @@ Namespace API.PornHub Dim lBefore% = l2.Count If _TempPostsList.Count > 0 Then l2.RemoveAll(Function(media) _TempPostsList.Contains(media.Post.ID)) If l2.Count > 0 Then + ProgressPre.ChangeMax(l2.Count) For i% = 0 To l2.Count - 1 + ProgressPre.Perform() m = l2(i) ThrowAny(Token) Try @@ -537,7 +556,9 @@ Namespace API.PornHub If _TempMediaList.Count > 0 AndAlso _TempMediaList.Exists(Function(tm) tm.Type = UTypes.VideoPre) Then Dim m As UserMedia Dim r$, NewUrl$, tmpName$ + ProgressPre.ChangeMax(_TempMediaList.Count) For i% = _TempMediaList.Count - 1 To 0 Step -1 + ProgressPre.Perform() If _TempMediaList(i).Type = UTypes.VideoPre Then m = _TempMediaList(i) ThrowAny(Token) @@ -588,7 +609,9 @@ Namespace API.PornHub Dim m As UserMedia Dim r$ Dim eCurl As New ErrorsDescriber(EDP.ReturnValue) + ProgressPre.ChangeMax(_ContentList.Count) For i% = 0 To _ContentList.Count - 1 + ProgressPre.Perform() m = _ContentList(i) If m.State = UserMedia.States.Missing AndAlso Not m.URL_BASE.IsEmptyString Then ThrowAny(Token) @@ -619,31 +642,91 @@ Namespace API.PornHub DownloadContentDefault(Token) End Sub Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile - Return M3U8.Download(URL, Responser, DestinationFile, Token, If(UseInternalM3U8Function_UseProgress, Progress, Nothing)) + Return M3U8.Download(URL, Responser, DestinationFile, DownloadUHD, Token, Progress, Not IsSingleObjectDownload) End Function #End Region #Region "CreateVideoURL" + 'TODELETE: PornHub old 'CreateVideoURL' function + 'Private Function CreateVideoURL(ByVal r As String) As String + ' Try + ' Dim OutStr$ = String.Empty + ' If Not r.IsEmptyString Then + ' Dim _VarBlock$ = RegexReplace(r, RegexVideo_FlashVarsBlock) + ' If Not _VarBlock.IsEmptyString Then + ' Dim vars As List(Of FlashVar) = RegexFields(Of FlashVar)(_VarBlock, {RegexVideo_FlashVars_Vars}, {1, 2}) + ' Dim compiler As List(Of String) = RegexReplace(_VarBlock, RegexVideo_FlashVars_Compiler) + ' If vars.ListExists And compiler.ListExists Then + ' Dim v$ + ' Dim i% + ' For Each var$ In compiler + ' i = vars.IndexOf(var) + ' If i >= 0 Then + ' v = vars(i).Value + ' If Not v.IsEmptyString Then OutStr &= v + ' End If + ' Next + ' End If + ' End If + ' End If + ' Return OutStr + ' Catch ex As Exception + ' Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.PornHub.UserData.CreateVideoURL]", String.Empty) + ' End Try + 'End Function Private Function CreateVideoURL(ByVal r As String) As String Try Dim OutStr$ = String.Empty + Dim OutList As New List(Of String) + Dim tmpUrl$ + Dim i% If Not r.IsEmptyString Then - Dim _VarBlock$ = RegexReplace(r, RegexVideo_FlashVarsBlock) - If Not _VarBlock.IsEmptyString Then - Dim vars As List(Of FlashVar) = RegexFields(Of FlashVar)(_VarBlock, {RegexVideo_FlashVars_Vars}, {1, 2}) - Dim compiler As List(Of String) = RegexReplace(_VarBlock, RegexVideo_FlashVars_Compiler) - If vars.ListExists And compiler.ListExists Then - Dim v$ - Dim i% - For Each var$ In compiler - i = vars.IndexOf(var) - If i >= 0 Then - v = vars(i).Value - If Not v.IsEmptyString Then OutStr &= v - End If - Next + Dim _VarBlock$, var$, v$ + Dim vars As List(Of FlashVar) + Dim compiler As List(Of String) + Dim _VarBlocks As List(Of String) = RegexReplace(r, RegexVideo_FlashVarsBlocks) + If _VarBlocks.ListExists Then + For Each _VarBlock In _VarBlocks + tmpUrl = String.Empty + vars = RegexFields(Of FlashVar)(_VarBlock, {RegexVideo_FlashVars_Vars}, {1, 2}) + compiler = RegexReplace(_VarBlock, RegexVideo_FlashVars_Compiler) + If vars.ListExists And compiler.ListExists Then + For Each var In compiler + i = vars.IndexOf(var) + If i >= 0 Then + v = vars(i).Value + If Not v.IsEmptyString Then tmpUrl &= v + End If + Next + vars.Clear() + compiler.Clear() + End If + If Not tmpUrl.IsEmptyString Then OutList.Add(tmpUrl) + Next + End If + End If + + If outList.Count > 0 Then outList.RemoveAll(Function(u) u.IsEmptyString) + If outList.Count > 0 Then + i = OutList.FindIndex(Function(u) u.Contains("urlset")) + If i >= 0 Then + OutStr = OutList(i) + Else + Dim newUrls As New List(Of Sizes) + Dim tmpSize%? + For Each tmpUrl In OutList + tmpSize = AConvert(Of Integer)(RegexReplace(tmpUrl, RegexVideo_FlashVars_UrlResolution), AModes.Var, Nothing) + If tmpSize.HasValue Then newUrls.Add(New Sizes(tmpSize.Value, tmpUrl)) + Next + If newUrls.Count > 0 Then + newUrls.Sort() + OutStr = newUrls(0).Data + newUrls.Clear() + Else + OutStr = OutList(0) End If End If End If + OutList.Clear() Return OutStr Catch ex As Exception Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.PornHub.UserData.CreateVideoURL]", String.Empty) diff --git a/SCrawler/API/PornHub/UserExchangeOptions.vb b/SCrawler/API/PornHub/UserExchangeOptions.vb index 8906308..f2d1556 100644 --- a/SCrawler/API/PornHub/UserExchangeOptions.vb +++ b/SCrawler/API/PornHub/UserExchangeOptions.vb @@ -9,18 +9,22 @@ Imports SCrawler.Plugin.Attributes Namespace API.PornHub Friend Class UserExchangeOptions + + Friend Property DownloadUHD As Boolean Friend Property DownloadGifs As Boolean Friend Property DownloadPhotoOnlyFromModelHub As Boolean Private ReadOnly Property MySettings As SiteSettings Friend Sub New(ByVal u As UserData) + DownloadUHD = u.DownloadUHD DownloadGifs = u.DownloadGifs DownloadPhotoOnlyFromModelHub = u.DownloadPhotoOnlyFromModelHub MySettings = u.HOST.Source End Sub Friend Sub New(ByVal s As SiteSettings) Dim v As CheckState = CInt(s.DownloadGifs.Value) + DownloadUHD = s.DownloadUHD.Value DownloadGifs = Not v = CheckState.Unchecked DownloadPhotoOnlyFromModelHub = s.DownloadPhotoOnlyFromModelHub.Value MySettings = s diff --git a/SCrawler/API/Reddit/M3U8.vb b/SCrawler/API/Reddit/M3U8.vb index 4581c8f..0c9b331 100644 --- a/SCrawler/API/Reddit/M3U8.vb +++ b/SCrawler/API/Reddit/M3U8.vb @@ -59,8 +59,10 @@ Namespace API.Reddit Private ReadOnly CacheFiles As CacheKeeper Private ReadOnly Property Progress As MyProgress Private ReadOnly ProgressExists As Boolean + Private ReadOnly Property ProgressPre As PreProgress + Private ReadOnly UsePreProgress As Boolean #End Region - Private Sub New(ByVal URL As String, ByVal OutFile As SFile, ByVal Progress As MyProgress) + Private Sub New(ByVal URL As String, ByVal OutFile As SFile, ByVal Progress As MyProgress, ByVal UsePreProgress As Boolean) PlayListURL = URL BaseURL = RegexReplace(URL, BaseUrlPattern) Video = New List(Of String) @@ -70,6 +72,8 @@ Namespace API.Reddit Me.OutFile.Extension = "mp4" Me.Progress = Progress ProgressExists = Not Me.Progress Is Nothing + ProgressPre = New PreProgress(Progress) + Me.UsePreProgress = UsePreProgress Cache = New CacheKeeper($"{OutFile.PathWithSeparator}_{Base.M3U8Base.TempCacheFolderName}\") CacheFiles = Cache.NewInstance End Sub @@ -142,12 +146,24 @@ Namespace API.Reddit If tmpCache.Validate Then Dim i% Dim dFile As SFile = tmpCache.RootDirectory - If ProgressExists Then Progress.Maximum += Urls.Count + If ProgressExists Then + If UsePreProgress Then + ProgressPre.ChangeMax(Urls.Count) + Else + Progress.Maximum += Urls.Count + End If + End If dFile.Extension = New SFile(Urls(0)).Extension If dFile.Extension.IsEmptyString Then dFile.Extension = "ts" Using w As New WebClient For i = 0 To Urls.Count - 1 - If ProgressExists Then Progress.Perform() + If ProgressExists Then + If UsePreProgress Then + ProgressPre.Perform() + Else + Progress.Perform() + End If + End If Token.ThrowIfCancellationRequested() dFile.Name = $"ConPart_{i}" w.DownloadFile(Urls(i), dFile) @@ -185,8 +201,9 @@ Namespace API.Reddit End Function #End Region #Region "Statics" - Friend Shared Function Download(ByVal URL As String, ByVal f As SFile, ByVal Token As CancellationToken, ByVal Progress As MyProgress) As SFile - Using m As New M3U8(URL, f, Progress) : Return m.Download(Token) : End Using + Friend Shared Function Download(ByVal URL As String, ByVal f As SFile, ByVal Token As CancellationToken, + ByVal Progress As MyProgress, ByVal UsePreProgress As Boolean) As SFile + Using m As New M3U8(URL, f, Progress, UsePreProgress) : Return m.Download(Token) : End Using End Function #End Region #Region "IDisposable Support" @@ -197,6 +214,7 @@ Namespace API.Reddit Video.Clear() Audio.Clear() Cache.Dispose() + ProgressPre.Dispose() End If disposedValue = True End If diff --git a/SCrawler/API/Reddit/UserData.vb b/SCrawler/API/Reddit/UserData.vb index 87cbd73..e02837e 100644 --- a/SCrawler/API/Reddit/UserData.vb +++ b/SCrawler/API/Reddit/UserData.vb @@ -14,6 +14,7 @@ Imports SCrawler.API.YouTube.Objects Imports SCrawler.Plugin.Hosts Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions +Imports PersonalUtilities.Tools.ImageRenderer Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Documents.JSON Imports UStates = SCrawler.API.Base.UserMedia.States @@ -222,6 +223,7 @@ Namespace API.Reddit GetUserInfo() DownloadDataUser(String.Empty, Token) End If + ProgressPre.Done() End Sub #End Region #Region "Download Functions (User, Channel)" @@ -247,7 +249,6 @@ Namespace API.Reddit Dim ExistsDetected As Boolean = False Dim IsCrossPost As Predicate(Of EContainer) = Function(e) Not e.Value(Node_CrosspostRootId).IsEmptyString Or Not e.Value(Node_CrosspostParentId).IsEmptyString Or Not e.Value(Node_CrosspostParent).IsEmptyString Dim CheckNode As Predicate(Of EContainer) = Function(e) Not ParseUserMediaOnly OrElse If(e("author")?.Value, "/").ToLower.Equals(TrueName.StringToLower) - Dim UPicType As Func(Of String, UTypes) = Function(input) IIf(input = "image", UTypes.Picture, UTypes.GIF) Dim _PostID As Func(Of String) = Function() PostTmp.IfNullOrEmpty(PostID) URL = $"https://gateway.reddit.com/desktopapi/v1/user/{TrueName}/posts?rtj=only&allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic" @@ -258,7 +259,9 @@ Namespace API.Reddit If w.Count > 0 Then n = w.GetNode(JsonNodesJson) If Not n Is Nothing AndAlso n.Count > 0 Then + ProgressPre.ChangeMax(n.Count) For Each nn In n + ProgressPre.Perform() ThrowAny(Token) If nn.Count > 0 Then If CheckNode(nn) Then @@ -340,7 +343,9 @@ Namespace API.Reddit If w.Count > 0 Then n = w.GetNode(ChannelJsonNodes) If Not n Is Nothing AndAlso n.Count > 0 Then + ProgressPre.ChangeMax(n.Count) For Each nn In n + ProgressPre.Perform() ThrowAny(Token) s = nn.ItemF({eCount}) If If(s?.Count, 0) > 0 Then @@ -467,8 +472,40 @@ Namespace API.Reddit Select Case t Case "gallery" : If DownloadGallery(.Self, PostID, PostDate) Then _TotalPostsDownloaded += 1 Else added = False Case "image", "gifvideo" + + Dim resolution As Sizes = Nothing + Dim content As Sizes = Nothing + Dim chosenVal$ = String.Empty + ParseResolutions(e("media"), e("preview"), resolution) If .Contains("content") Then - _TempMediaList.ListAddValue(MediaFromData(UPicType(t), .Value("content"), PostID, PostDate, UserID), LNC) + content = CreateSize(.Self, "content") + If content.HasError Or content.Data.IsEmptyString Then content = Nothing + End If + + If UPicType(t) = UTypes.Picture Then + If Not content.Data.IsEmptyString Then + If Not resolution.Data.IsEmptyString Then + If content.Value >= resolution.Value AndAlso TryImage(content.Data) Then + chosenVal = content.Data + Else + chosenVal = resolution.Data + End If + Else + chosenVal = content.Data + End If + Else + chosenVal = resolution.Data + End If + Else + If Not resolution.Data.IsEmptyString Then + chosenVal = resolution.Data + ElseIf Not content.Data.IsEmptyString Then + chosenVal = content.Data + End If + End If + + If Not chosenVal.IsEmptyString Then + _TempMediaList.ListAddValue(MediaFromData(UPicType(t), chosenVal, PostID, PostDate, UserID), LNC) _TotalPostsDownloaded += 1 Else added = False @@ -512,15 +549,17 @@ Namespace API.Reddit added = ParseContainer(e.ItemF({"crosspost_parent_list", 0}), PostID, PostDate, UserID, True) Else Dim tPostId$ = e.Value(Node_CrosspostParent).IfNullOrEmpty(e.Value(Node_CrosspostParentId)).IfNullOrEmpty(e.Value(Node_CrosspostRootId)) - Dim r$ = Responser.GetResponse($"https://www.reddit.com/comments/{tPostId.Split("_").LastOrDefault}/.json",, EDP.ReturnValue) - If Not r.IsEmptyString Then - Using j As EContainer = JsonDocument.Parse(r, EDP.ReturnValue) - If j.ListExists Then - With j.ItemF({0, "data", "children", 0, "data"}) - If .ListExists Then added = ParseContainer(.Self, PostID, PostDate, UserID, False) - End With - End If - End Using + If Not PostID.IsEmptyString Then + Dim r$ = Responser.GetResponse($"https://www.reddit.com/comments/{tPostId.Split("_").LastOrDefault}/.json",, EDP.ReturnValue) + If Not r.IsEmptyString Then + Using j As EContainer = JsonDocument.Parse(r, EDP.ReturnValue) + If j.ListExists Then + With j.ItemF({0, "data", "children", 0, "data"}) + If .ListExists Then added = ParseContainer(.Self, PostID, PostDate, UserID, False) + End With + End If + End Using + End If End If End If End If @@ -557,6 +596,19 @@ Namespace API.Reddit Return False End If End Function + Private Function TryImage(ByVal URL As String) As Boolean + Try + Dim img As Image = GetImage(SFile.GetBytesFromNet(URL, EDP.ThrowException), EDP.ThrowException) + If Not img Is Nothing Then + img.Dispose() + Return True + Else + Return False + End If + Catch + Return False + End Try + End Function #End Region #Region "Download Base Functions" Private Function CreateImgurMedia(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, @@ -640,26 +692,7 @@ Namespace API.Reddit If Not Node Is Nothing Then Dim n As EContainer = Node.ItemF({"preview", "images", 0}) Dim DestNode$() = Nothing - If If(n?.Count, 0) > 0 Then - If If(n("resolutions")?.Count, 0) > 0 Then - DestNode = {"resolutions"} - ElseIf If(n({"variants", "nsfw", "resolutions"})?.Count, 0) > 0 Then - DestNode = {"variants", "nsfw", "resolutions"} - End If - If Not DestNode Is Nothing Then - With n(DestNode) - Dim sl As List(Of Sizes) = .Select(Function(e) New Sizes(e.Value("width"), e.Value("url"))). - ListWithRemove(Function(ss) ss.HasError Or ss.Data.IsEmptyString) - If sl.ListExists Then - Dim s As Sizes - sl.Sort() - s = sl.First - sl.Clear() - Return s.Data - End If - End With - End If - End If + If If(n?.Count, 0) > 0 Then Return ParseResolutions(n) End If Return String.Empty Catch ex As Exception @@ -667,6 +700,46 @@ Namespace API.Reddit Return String.Empty End Try End Function + Private Function ParseResolutions(ByVal Node As EContainer, Optional ByVal PreviewNode As EContainer = Nothing, + Optional ByRef SResult As Sizes = Nothing) As String + Try + If If(Node?.Count, 0) > 0 Then + Dim DestNode$() = Nothing + If If(Node("resolutions")?.Count, 0) > 0 Then + DestNode = {"resolutions"} + ElseIf If(Node({"variants", "nsfw", "resolutions"})?.Count, 0) > 0 Then + DestNode = {"variants", "nsfw", "resolutions"} + End If + If Not DestNode Is Nothing Then + With Node(DestNode) + Dim sl As List(Of Sizes) = .Select(Function(e) CreateSize(e)). + ListWithRemove(Function(ss) ss.HasError Or ss.Data.IsEmptyString) + If If(PreviewNode?.Count, 0) > 0 Then + Dim sp As Sizes = CreateSize(PreviewNode) + If Not sp.HasError And Not sp.Data.IsEmptyString Then + If sl Is Nothing Then sl = New List(Of Sizes) + sl.Add(sp) + End If + End If + If sl.ListExists Then + Dim s As Sizes + sl.Sort() + s = sl.First + sl.Clear() + SResult = s + Return s.Data + End If + End With + End If + End If + Return String.Empty + Catch ex As Exception + Return String.Empty + End Try + End Function + Private Function CreateSize(ByVal Node As EContainer, Optional ByVal UrlNodeName As String = "url") As Sizes + Return New Sizes(Node.Value("width"), Node.Value(UrlNodeName)) + End Function #End Region #Region "ReparseVideo" Protected Overrides Sub ReparseVideo(ByVal Token As CancellationToken) @@ -681,8 +754,10 @@ Namespace API.Reddit Dim RedGifsHost As SettingsHost = Settings(RedGifs.RedGifsSiteKey) Dim _repeatForRedgifs As Boolean RedGifsResponser = RedGifsHost.Responser.Copy + ProgressPre.ChangeMax(_TempMediaList.Count) For i% = _TempMediaList.Count - 1 To 0 Step -1 ThrowAny(Token) + ProgressPre.Perform() If _TempMediaList(i).Type = UTypes.VideoPre Or _TempMediaList(i).Type = v2 Then m = _TempMediaList(i) If _TempMediaList(i).Type = UTypes.VideoPre Then @@ -728,6 +803,7 @@ Namespace API.Reddit ProcessException(ex, Token, "video reparsing error", False) Finally If Not RedGifsResponser Is Nothing Then RedGifsResponser.Dispose() + ProgressPre.Done() End Try End Sub #End Region @@ -744,8 +820,10 @@ Namespace API.Reddit Dim r$ Dim j As EContainer Dim lastCount%, li% + ProgressPre.ChangeMax(_ContentList.Count) For i% = 0 To _ContentList.Count - 1 m = _ContentList(i) + ProgressPre.Perform() If m.State = UStates.Missing AndAlso Not m.Post.ID.IsEmptyString Then ThrowAny(Token) r = Responser.GetResponse($"https://www.reddit.com/comments/{m.Post.ID.Split("_").LastOrDefault}/.json",, EDP.ReturnValue) @@ -781,6 +859,7 @@ Namespace API.Reddit For i% = rList.Count - 1 To 0 Step -1 : _ContentList.RemoveAt(rList(i)) : Next rList.Clear() End If + ProgressPre.Done() End Try End Sub #End Region @@ -804,17 +883,13 @@ Namespace API.Reddit _URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern)) Dim m As New UserMedia(_URL, t) With {.Post = New UserPost With {.ID = PostID, .UserID = _UserID}} If t = UTypes.Picture Or t = UTypes.GIF Then m.File = CreateFileFromUrl(m.URL) Else m.File = Nothing - If ReplacePreview And m.URL.Contains("preview") Then m.URL = $"https://i.redd.it/{m.File.File}" + If ReplacePreview And m.URL.Contains("preview") And Not t = UTypes.Picture Then m.URL = $"https://i.redd.it/{m.File.File}" If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, DateTrueProvider(IsChannel), Nothing) Else m.Post.Date = Nothing Return m End Function Private Function TryFile(ByVal URL As String) As Boolean Try - If Not URL.IsEmptyString AndAlso URL.StringContains({".jpg", ".png", ".jpeg"}) Then - Return Not CreateFileFromUrl(URL).IsEmptyString - Else - Return False - End If + Return Not URL.IsEmptyString AndAlso Not CreateFileFromUrl(URL).IsEmptyString Catch ex As Exception Return False End Try @@ -861,7 +936,7 @@ Namespace API.Reddit Return URL.Contains(SiteRedGifsKey) End Function Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile - Return M3U8.Download(URL, DestinationFile, Token, IIf(IsSingleObjectDownload, Progress, Nothing)) + Return M3U8.Download(URL, DestinationFile, Token, Progress, Not IsSingleObjectDownload) End Function Protected Overrides Function ChangeFileNameByProvider(ByVal f As SFile, ByVal m As UserMedia) As SFile If Not IsChannel Or Not SaveToCache Then diff --git a/SCrawler/API/Redgifs/UserData.vb b/SCrawler/API/Redgifs/UserData.vb index db4c4e9..9a54290 100644 --- a/SCrawler/API/Redgifs/UserData.vb +++ b/SCrawler/API/Redgifs/UserData.vb @@ -50,7 +50,9 @@ Namespace API.RedGifs Using j As EContainer = JsonDocument.Parse(r).XmlIfNothing If j.Contains("gifs") Then pTotal = j.Value("pages").FromXML(Of Integer)(0) + ProgressPre.ChangeMax(j("gifs").Count) For Each g As EContainer In j("gifs") + ProgressPre.Perform() postDate = g.Value("createDate") Select Case CheckDatesLimit(postDate, UnixDate32Provider) Case DateResult.Skip : Continue For @@ -102,11 +104,13 @@ Namespace API.RedGifs Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken) Dim rList As New List(Of Integer) Try - If _ContentList.Exists(MissingFinder) Then + If ContentMissingExists Then Dim url$, r$ Dim u As UserMedia Dim j As EContainer + ProgressPre.ChangeMax(_ContentList.Count) For i% = 0 To _ContentList.Count - 1 + ProgressPre.Perform() If _ContentList(i).State = UStates.Missing Then ThrowAny(Token) u = _ContentList(i) diff --git a/SCrawler/API/ThisVid/UserData.vb b/SCrawler/API/ThisVid/UserData.vb index b7f8aa0..6958ede 100644 --- a/SCrawler/API/ThisVid/UserData.vb +++ b/SCrawler/API/ThisVid/UserData.vb @@ -129,6 +129,7 @@ Namespace API.ThisVid Private Overloads Sub DownloadData(ByVal Page As Integer, ByVal IsPublic As Boolean, ByVal Token As CancellationToken) Dim URL$ = String.Empty Try + ProgressPre.ChangeMax(1) Dim p$ = IIf(Page = 1, String.Empty, $"{Page}/") If IsSavedPosts Then URL = $"https://thisvid.com/my_favourite_videos/{p}" @@ -136,6 +137,7 @@ Namespace API.ThisVid URL = $"https://thisvid.com/members/{ID}/{IIf(IsPublic, "public", "private")}_videos/{p}" End If ThrowAny(Token) + ProgressPre.Perform() Dim r$ = Responser.GetResponse(URL) Dim cBefore% = _TempMediaList.Count If Not r.IsEmptyString Then @@ -182,7 +184,9 @@ Namespace API.ThisVid __continue = True If albums.ListExists Then If albums.Count < 20 Then __continue = False + ProgressPre.ChangeMax(albums.Count) For Each a As Album In albums + ProgressPre.Perform() If Not a.URL.IsEmptyString Then ThrowAny(Token) r = Responser.GetResponse(a.URL,, rErr) @@ -191,7 +195,9 @@ Namespace API.ThisVid If a.Title.IsEmptyString Then a.Title = albumId images = RegexReplace(r, RegExAlbumImagesList) If images.ListExists Then + ProgressPre.ChangeMax(images.Count) For Each img In images + ProgressPre.Perform() ThrowAny(Token) r = Responser.GetResponse(img,, rErr) If Not r.IsEmptyString Then @@ -242,7 +248,9 @@ Namespace API.ThisVid Dim cookieFile As SFile = DirectCast(HOST.Source, SiteSettings).CookiesNetscapeFile Dim command$ Dim e As EContainer + ProgressPre.ChangeMax(_TempMediaList.Count) For i% = _TempMediaList.Count - 1 To 0 Step -1 + ProgressPre.Perform() u = _TempMediaList(i) If u.Type = UserMedia.Types.VideoPre Then ThrowAny(Token) diff --git a/SCrawler/API/Twitter/UserData.vb b/SCrawler/API/Twitter/UserData.vb index 069d567..4b62129 100644 --- a/SCrawler/API/Twitter/UserData.vb +++ b/SCrawler/API/Twitter/UserData.vb @@ -125,36 +125,40 @@ Namespace API.Twitter End With End If - 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() + With If(IsSavedPosts, w({"globalObjects", "tweets"}).XmlIfNothing, w) + ProgressPre.ChangeMax(.Count) + For Each nn In .Self + ProgressPre.Perform() + ThrowAny(Token) + If nn.Count > 0 Then + PostID = nn.Value("id") + If ID.IsEmptyString Then + ID = UID(nn) + If Not ID.IsEmptyString Then UpdateUserInformation() + End If + + 'Date Pattern: + 'Sat Jan 01 01:10:15 +0000 2000 + If nn.Contains("created_at") Then PostDate = nn("created_at").Value Else PostDate = String.Empty + Select Case CheckDatesLimit(PostDate, Declarations.DateProvider) + Case DateResult.Skip : Continue For + Case DateResult.Exit : Exit Sub + End Select + + If Not _TempPostsList.Contains(PostID) Then + NewPostDetected = True + _TempPostsList.Add(PostID) + Else + ExistsDetected = True + Continue For + End If + + If Not ParseUserMediaOnly OrElse + (Not nn.Contains("retweeted_status") OrElse (Not ID.IsEmptyString AndAlso UID(nn("retweeted_status")) = ID)) Then _ + ObtainMedia(nn, PostID, PostDate) End If - - 'Date Pattern: - 'Sat Jan 01 01:10:15 +0000 2000 - If nn.Contains("created_at") Then PostDate = nn("created_at").Value Else PostDate = String.Empty - Select Case CheckDatesLimit(PostDate, Declarations.DateProvider) - Case DateResult.Skip : Continue For - Case DateResult.Exit : Exit Sub - End Select - - If Not _TempPostsList.Contains(PostID) Then - NewPostDetected = True - _TempPostsList.Add(PostID) - Else - ExistsDetected = True - Continue For - End If - - If Not ParseUserMediaOnly OrElse - (Not nn.Contains("retweeted_status") OrElse (Not ID.IsEmptyString AndAlso UID(nn("retweeted_status")) = ID)) Then _ - ObtainMedia(nn, PostID, PostDate) - End If - Next + Next + End With End If End Using @@ -174,7 +178,9 @@ Namespace API.Twitter Dim j As EContainer, jj As EContainer Dim jErr As New ErrorsDescriber(EDP.ReturnValue) Dim rPattern As RParams = RParams.DM("(?<=tweet-)(\d+)\Z", 0, EDP.ReturnValue) + ProgressPre.ChangeMax(urls.Count) For Each url$ In urls + ProgressPre.Perform() r = Responser.GetResponse(url) If Not r.IsEmptyString Then j = JsonDocument.Parse(r, jErr) @@ -187,7 +193,9 @@ Namespace API.Twitter Next If postIds.Count > 0 Then postIds.RemoveAll(Function(pid) pid.IsEmptyString OrElse (_TempPostsList.Contains(pid) Or _DataNames.Contains(pid))) If postIds.Count > 0 Then + ProgressPre.ChangeMax(postIds.Count) For Each __id$ In postIds + ProgressPre.Perform() _TempPostsList.Add(__id) r = Responser.GetResponse(String.Format(SinglePostUrl, __id),, EDP.ReturnValue) If Not r.IsEmptyString Then @@ -329,7 +337,9 @@ Namespace API.Twitter Dim m As UserMedia Dim r$, PostDate$ Dim j As EContainer + ProgressPre.ChangeMax(_ContentList.Count) For i% = 0 To _ContentList.Count - 1 + ProgressPre.Perform() If _ContentList(i).State = UStates.Missing Then m = _ContentList(i) If Not m.Post.ID.IsEmptyString Then diff --git a/SCrawler/API/XVIDEOS/M3U8.vb b/SCrawler/API/XVIDEOS/M3U8.vb index 00b87ce..f5209e0 100644 --- a/SCrawler/API/XVIDEOS/M3U8.vb +++ b/SCrawler/API/XVIDEOS/M3U8.vb @@ -14,7 +14,7 @@ Namespace API.XVIDEOS Private Sub New() End Sub Friend Shared Function Download(ByVal URL As String, ByVal Appender As String, ByVal f As SFile, - ByVal Token As CancellationToken, ByVal Progress As MyProgress) As SFile + ByVal Token As CancellationToken, ByVal Progress As MyProgress, ByVal UsePreProgress As Boolean) As SFile Try If Not URL.IsEmptyString Then Using w As New WebClient @@ -22,7 +22,7 @@ Namespace API.XVIDEOS If Not r.IsEmptyString Then Dim l As List(Of String) = ListAddList(Nothing, r.StringFormatLines.StringToList(Of String)(vbNewLine).ListWithRemove(Function(v) v.Trim.StartsWith("#")), New ListAddParams With {.Converter = Function(Input) $"{Appender}/{Input.ToString.Trim}"}) - If l.ListExists Then Return Base.M3U8Base.Download(l, f,, Token, Progress) + If l.ListExists Then Return Base.M3U8Base.Download(l, f,, Token, Progress, UsePreProgress) End If End Using End If diff --git a/SCrawler/API/XVIDEOS/UserData.vb b/SCrawler/API/XVIDEOS/UserData.vb index 05b04c7..b1354e8 100644 --- a/SCrawler/API/XVIDEOS/UserData.vb +++ b/SCrawler/API/XVIDEOS/UserData.vb @@ -91,8 +91,10 @@ Namespace API.XVIDEOS If .Contains("videos") Then With .Item("videos") If .Count > 0 Then + ProgressPre.ChangeMax(.Count) NextPage += 1 For Each jj In .Self + ProgressPre.Perform() p = New UserMedia With { .Post = jj.Value("id"), .URL = $"https://www.xvideos.com/{jj.Value(n).StringTrimStart("/")}" @@ -123,7 +125,9 @@ Namespace API.XVIDEOS If Not j Is Nothing Then j.Dispose() If _TempMediaList.Count > 0 Then + ProgressPre.ChangeMax(_TempMediaList.Count) For i% = 0 To _TempMediaList.Count - 1 + ProgressPre.Perform() ThrowAny(Token) _TempMediaList(i) = GetVideoData(_TempMediaList(i)) Next @@ -180,7 +184,9 @@ Namespace API.XVIDEOS Loop While NextPage < 100 And __continue If _TempMediaList.Count > 0 Then + ProgressPre.ChangeMax(_TempMediaList.Count) For i% = 0 To _TempMediaList.Count - 1 + ProgressPre.Perform() ThrowAny(Token) _TempMediaList(i) = GetVideoData(_TempMediaList(i)) Next @@ -244,7 +250,7 @@ Namespace API.XVIDEOS If Not m.URL.IsEmptyString Then _TempMediaList.Add(m) End Sub Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile - Return M3U8.Download(Media.URL, Media.PictureOption, DestinationFile, Token, If(UseInternalM3U8Function_UseProgress, Progress, Nothing)) + Return M3U8.Download(Media.URL, Media.PictureOption, DestinationFile, Token, Progress, Not IsSingleObjectDownload) End Function Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, Optional ByVal EObj As Object = Nothing) As Integer diff --git a/SCrawler/API/Xhamster/M3U8.vb b/SCrawler/API/Xhamster/M3U8.vb index 6701f62..9b4dcb9 100644 --- a/SCrawler/API/Xhamster/M3U8.vb +++ b/SCrawler/API/Xhamster/M3U8.vb @@ -75,8 +75,8 @@ Namespace API.Xhamster End Try End Function Friend Shared Function Download(ByVal Media As UserMedia, ByVal Responser As Responser, ByVal UHD As Boolean, - ByVal Token As CancellationToken, ByVal Progress As MyProgress) As SFile - Return M3U8Base.Download(ObtainUrls(Media.URL, Responser, UHD), Media.File, Responser, Token, Progress) + ByVal Token As CancellationToken, ByVal Progress As MyProgress, ByVal UsePreProgress As Boolean) As SFile + Return M3U8Base.Download(ObtainUrls(Media.URL, Responser, UHD), Media.File, Responser, Token, Progress, UsePreProgress) End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Xhamster/UserData.vb b/SCrawler/API/Xhamster/UserData.vb index ee3a087..b794546 100644 --- a/SCrawler/API/Xhamster/UserData.vb +++ b/SCrawler/API/Xhamster/UserData.vb @@ -112,7 +112,9 @@ Namespace API.Xhamster With j(listNode) If .ListExists Then + ProgressPre.ChangeMax(.Count) For Each e As EContainer In .Self + ProgressPre.Perform() m = ExtractMedia(e, Type) If Not m.URL.IsEmptyString Then If m.File.IsEmptyString Then Continue For @@ -160,7 +162,9 @@ Namespace API.Xhamster Try If _TempMediaList.Count > 0 AndAlso _TempMediaList.Exists(Function(tm) tm.Type = UTypes.VideoPre) Then Dim m As UserMedia, m2 As UserMedia + ProgressPre.ChangeMax(_TempMediaList.Count) For i% = _TempMediaList.Count - 1 To 0 Step -1 + ProgressPre.Perform() If _TempMediaList(i).Type = UTypes.VideoPre Then m = _TempMediaList(i) If Not m.URL_BASE.IsEmptyString Then @@ -182,7 +186,8 @@ Namespace API.Xhamster End Sub Private Overloads Sub ReparsePhoto(ByVal Token As CancellationToken) If _TempPhotoData.Count > 0 Then - For i% = 0 To _TempPhotoData.Count - 1 : ReparsePhoto(i, 1, Token) : Next + ProgressPre.ChangeMax(_TempPhotoData.Count) + For i% = 0 To _TempPhotoData.Count - 1 : ProgressPre.Perform() : ReparsePhoto(i, 1, Token) : Next _TempPhotoData.Clear() End If End Sub @@ -235,7 +240,9 @@ Namespace API.Xhamster Try If ContentMissingExists Then Dim m As UserMedia, m2 As UserMedia + ProgressPre.ChangeMax(_ContentList.Count) For i% = 0 To _ContentList.Count - 1 + ProgressPre.Perform() m = _ContentList(i) If m.State = UserMedia.States.Missing AndAlso Not m.URL_BASE.IsEmptyString Then ThrowAny(Token) @@ -297,7 +304,7 @@ Namespace API.Xhamster End Sub Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile Media.File = DestinationFile - Return M3U8.Download(Media, Responser, MySettings.DownloadUHD.Value, Token, If(UseInternalM3U8Function_UseProgress, Progress, Nothing)) + Return M3U8.Download(Media, Responser, MySettings.DownloadUHD.Value, Token, Progress, Not IsSingleObjectDownload) End Function #End Region #Region "Create media" diff --git a/SCrawler/API/YouTube/UserData.vb b/SCrawler/API/YouTube/UserData.vb index bd7c1a0..dea39bd 100644 --- a/SCrawler/API/YouTube/UserData.vb +++ b/SCrawler/API/YouTube/UserData.vb @@ -111,6 +111,7 @@ Namespace API.YouTube #Region "Download" 'Playlist reconfiguration implemented only for channels + music Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) + Dim pr As New YTPreProgress(ProgressPre) Try Dim container As IYouTubeMediaContainer = Nothing Dim list As New List(Of IYouTubeMediaContainer) @@ -154,7 +155,7 @@ Namespace API.YouTube maxDate = Nothing LastDownloadDatePlaylist = nDate(LastDownloadDatePlaylist) url = $"https://{IIf(IsMusic, "music", "www")}.youtube.com/playlist?list={ID}" - container = YouTubeFunctions.Parse(url, YTUseCookies, Token,, True, False,, LastDownloadDatePlaylist) + container = YouTubeFunctions.Parse(url, YTUseCookies, Token, pr, True, False,, LastDownloadDatePlaylist) applySpecFolder.Invoke(String.Empty, False) If fillList.Invoke(LastDownloadDatePlaylist) Then LastDownloadDatePlaylist = If(maxDate, Now) ElseIf YTMediaType = YouTubeMediaType.Channel Then @@ -162,7 +163,7 @@ Namespace API.YouTube maxDate = Nothing LastDownloadDateVideos = nDate(LastDownloadDateVideos) url = $"https://{IIf(IsMusic, "music", "www")}.youtube.com/{IIf(IsMusic Or IsChannelUser, $"{YouTubeFunctions.UserChannelOption}/", "@")}{ID}" - container = YouTubeFunctions.Parse(url, YTUseCookies, Token,, True, False,, LastDownloadDateVideos) + container = YouTubeFunctions.Parse(url, YTUseCookies, Token, pr, True, False,, LastDownloadDateVideos) applySpecFolder.Invoke(IIf(IsMusic, String.Empty, "Videos"), False) If fillList.Invoke(LastDownloadDateVideos) Then LastDownloadDateVideos = If(maxDate, Now) End If @@ -170,7 +171,7 @@ Namespace API.YouTube maxDate = Nothing LastDownloadDateShorts = nDate(LastDownloadDateShorts) url = $"https://www.youtube.com/{IIf(IsChannelUser, $"{YouTubeFunctions.UserChannelOption}/", "@")}{ID}/shorts" - container = YouTubeFunctions.Parse(url, YTUseCookies, Token,, True, False,, LastDownloadDateShorts) + container = YouTubeFunctions.Parse(url, YTUseCookies, Token, pr, True, False,, LastDownloadDateShorts) applySpecFolder.Invoke("Shorts", False) If fillList.Invoke(LastDownloadDateShorts) Then LastDownloadDateShorts = If(maxDate, Now) End If @@ -178,7 +179,7 @@ Namespace API.YouTube maxDate = Nothing LastDownloadDatePlaylist = nDate(LastDownloadDatePlaylist) url = $"https://www.youtube.com/{IIf(IsChannelUser, $"{YouTubeFunctions.UserChannelOption}/", "@")}{ID}/playlists" - container = YouTubeFunctions.Parse(url, YTUseCookies, Token,, True, False,, LastDownloadDatePlaylist) + container = YouTubeFunctions.Parse(url, YTUseCookies, Token, pr, True, False,, LastDownloadDatePlaylist) applySpecFolder.Invoke("Playlists", True) If fillList.Invoke(LastDownloadDatePlaylist) Then LastDownloadDatePlaylist = If(maxDate, Now) End If @@ -196,6 +197,8 @@ Namespace API.YouTube End If Catch ex As Exception ProcessException(ex, Token, "data downloading error") + Finally + pr.Dispose() End Try End Sub Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) diff --git a/SCrawler/API/YouTube/YTPreProgress.vb b/SCrawler/API/YouTube/YTPreProgress.vb new file mode 100644 index 0000000..43c776e --- /dev/null +++ b/SCrawler/API/YouTube/YTPreProgress.vb @@ -0,0 +1,38 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' 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.Toolbars +Namespace API.YouTube + Friend Class YTPreProgress : Inherits MyProgress + Private ReadOnly AssocProgress As PreProgress + Friend Sub New(ByRef ExtProgress As PreProgress) + AssocProgress = ExtProgress + End Sub + Public Overrides Property Maximum As Double + Get + Return _Maximum + End Get + Set(ByVal Max As Double) + _Maximum = Max + AssocProgress.ChangeMax(Max, False) + End Set + End Property + Public Overrides Sub Perform(Optional ByVal Value As Double = 1) + AssocProgress.Perform(Value) + End Sub + Public Overrides Sub Done() + AssocProgress.Done() + End Sub + Public Overrides Property Visible(Optional ByVal ProgressBar As Boolean = True, Optional ByVal Label As Boolean = True) As Boolean + Get + Return True + End Get + Set : End Set + End Property + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Download/ActiveDownloadingProgress.vb b/SCrawler/Download/ActiveDownloadingProgress.vb index bb0f86e..0fc4489 100644 --- a/SCrawler/Download/ActiveDownloadingProgress.vb +++ b/SCrawler/Download/ActiveDownloadingProgress.vb @@ -63,6 +63,7 @@ Namespace DownloadObjects .RowCount += 1 JobsList.Add(New DownloadProgress(j)) AddHandler JobsList.Last.ProgressMaximumChanged, AddressOf Jobs_ProgressMaximumChanged + AddHandler JobsList.Last.ProgressMaximum0Changed, AddressOf Jobs_ProgressMaximum0Changed .Controls.Add(JobsList.Last.Get, 0, .RowStyles.Count - 1) End With Next @@ -90,5 +91,9 @@ Namespace DownloadObjects If MainProgress.Value > 0 Then MainProgress.Perform() End If End Sub + Private Sub Jobs_ProgressMaximum0Changed() + If JobsList.Count > 0 And Not DisableProgressChange Then _ + MainProgress.Maximum0 = JobsList.Sum(Function(j) CLng(DirectCast(j.Job.Progress, MyProgressExt).Maximum0)) + End Sub End Class End Namespace \ No newline at end of file diff --git a/SCrawler/Download/DownloadProgress.vb b/SCrawler/Download/DownloadProgress.vb index ca37e6f..d00973e 100644 --- a/SCrawler/Download/DownloadProgress.vb +++ b/SCrawler/Download/DownloadProgress.vb @@ -14,6 +14,7 @@ Namespace DownloadObjects #Region "Events" Friend Event DownloadDone As NotificationEventHandler Friend Event ProgressMaximumChanged() + Friend Event ProgressMaximum0Changed() #End Region #Region "Declarations" #Region "Controls" @@ -114,10 +115,12 @@ Namespace DownloadObjects End If With Job - .Progress = New MyProgress(PR_MAIN, LBL_INFO) With {.ResetProgressOnMaximumChanges = False} - With .Progress + .Progress = New MyProgressExt(PR_MAIN, LBL_INFO) With {.ResetProgressOnMaximumChanges = False} + With DirectCast(.Progress, MyProgressExt) AddHandler .ProgressChanged, AddressOf JobProgress_ProgressChanged AddHandler .MaximumChanged, AddressOf JobProgress_MaximumChanged + AddHandler .Maximum0Changed, AddressOf JobProgress_Maximum0Changed + AddHandler .Progress0Changed, AddressOf JobProgress_Progress0Changed End With End With @@ -183,8 +186,18 @@ Namespace DownloadObjects Private Sub JobProgress_MaximumChanged(ByVal Sender As Object, ByVal e As ProgressEventArgs) RaiseEvent ProgressMaximumChanged() End Sub + Private Sub JobProgress_Maximum0Changed(ByVal Sender As Object, ByVal e As ProgressEventArgs) + RaiseEvent ProgressMaximum0Changed() + End Sub Private Sub JobProgress_ProgressChanged(ByVal Sender As Object, ByVal e As ProgressEventArgs) - If Not Job.Type = Download.SavedPosts Then MainProgress.Perform() + If Not Job.Type = Download.SavedPosts Then + MainProgress.Value = DirectCast(Sender, MyProgressExt).Value + MainProgress.Perform(0) + End If + End Sub + Private Sub JobProgress_Progress0Changed(ByVal Sender As Object, ByVal e As ProgressEventArgs) + MainProgress.Value0 = DirectCast(Sender, MyProgressExt).Value0 + MainProgress.Perform0(0) End Sub #End Region #Region "IDisposable Support" diff --git a/SCrawler/Editors/UserCreatorForm.Designer.vb b/SCrawler/Editors/UserCreatorForm.Designer.vb index 738c1eb..1d427e9 100644 --- a/SCrawler/Editors/UserCreatorForm.Designer.vb +++ b/SCrawler/Editors/UserCreatorForm.Designer.vb @@ -105,10 +105,10 @@ Namespace Editors 'BTT_OTHER_SETTINGS ' Me.BTT_OTHER_SETTINGS.Dock = System.Windows.Forms.DockStyle.Fill - Me.BTT_OTHER_SETTINGS.Location = New System.Drawing.Point(2, 2) + Me.BTT_OTHER_SETTINGS.Location = New System.Drawing.Point(1, 1) Me.BTT_OTHER_SETTINGS.Margin = New System.Windows.Forms.Padding(1) Me.BTT_OTHER_SETTINGS.Name = "BTT_OTHER_SETTINGS" - Me.BTT_OTHER_SETTINGS.Size = New System.Drawing.Size(101, 24) + Me.BTT_OTHER_SETTINGS.Size = New System.Drawing.Size(101, 26) Me.BTT_OTHER_SETTINGS.TabIndex = 1 Me.BTT_OTHER_SETTINGS.Text = "Options (F2)" TT_MAIN.SetToolTip(Me.BTT_OTHER_SETTINGS, "Other settings") @@ -177,7 +177,6 @@ Namespace Editors ' 'TP_SITE ' - Me.TP_SITE.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] Me.TP_SITE.ColumnCount = 2 Me.TP_SITE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 103.0!)) Me.TP_SITE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) @@ -209,10 +208,10 @@ Namespace Editors Me.CMB_SITE.Columns.Add(ListColumn1) Me.CMB_SITE.Columns.Add(ListColumn2) Me.CMB_SITE.Dock = System.Windows.Forms.DockStyle.Fill - Me.CMB_SITE.Location = New System.Drawing.Point(108, 3) - Me.CMB_SITE.Margin = New System.Windows.Forms.Padding(3, 2, 3, 3) + Me.CMB_SITE.Location = New System.Drawing.Point(103, 3) + Me.CMB_SITE.Margin = New System.Windows.Forms.Padding(0, 3, 3, 3) Me.CMB_SITE.Name = "CMB_SITE" - Me.CMB_SITE.Size = New System.Drawing.Size(340, 21) + Me.CMB_SITE.Size = New System.Drawing.Size(346, 22) Me.CMB_SITE.TabIndex = 0 Me.CMB_SITE.TextBoxBorderStyle = System.Windows.Forms.BorderStyle.FixedSingle ' diff --git a/SCrawler/Editors/UsersInfoForm.Designer.vb b/SCrawler/Editors/UsersInfoForm.Designer.vb new file mode 100644 index 0000000..2e5611a --- /dev/null +++ b/SCrawler/Editors/UsersInfoForm.Designer.vb @@ -0,0 +1,287 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' 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 Editors + + Partial Friend Class UsersInfoForm : 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() + Me.components = New System.ComponentModel.Container() + Dim SEP_1 As System.Windows.Forms.ToolStripSeparator + Dim CONTEXT_SEP_1 As System.Windows.Forms.ToolStripSeparator + Dim MENU_SEP_1 As System.Windows.Forms.ToolStripSeparator + Dim MENU_SEP_2 As System.Windows.Forms.ToolStripSeparator + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(UsersInfoForm)) + Me.Toolbar_TOP = New System.Windows.Forms.ToolStrip() + Me.BTT_START = New System.Windows.Forms.ToolStripButton() + Me.BTT_CANCEL = New System.Windows.Forms.ToolStripButton() + Me.MENU_VIEW = New System.Windows.Forms.ToolStripDropDownButton() + Me.OPT_DATE = New System.Windows.Forms.ToolStripMenuItem() + Me.OPT_SIZE = New System.Windows.Forms.ToolStripMenuItem() + Me.OPT_AMOUNT = New System.Windows.Forms.ToolStripMenuItem() + Me.OPT_ASC = New System.Windows.Forms.ToolStripMenuItem() + Me.OPT_DESC = New System.Windows.Forms.ToolStripMenuItem() + Me.CH_GROUP_DRIVE = New System.Windows.Forms.ToolStripMenuItem() + Me.CH_GROUP_COL = New System.Windows.Forms.ToolStripMenuItem() + Me.Toolbar_BOTTOM = New System.Windows.Forms.StatusStrip() + Me.PR_MAIN = New System.Windows.Forms.ToolStripProgressBar() + Me.LBL_STATUS = New System.Windows.Forms.ToolStripStatusLabel() + Me.LIST_DATA = New System.Windows.Forms.ListView() + Me.COL_DEFAULT = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader) + Me.CONTEXT_LIST = New System.Windows.Forms.ContextMenuStrip(Me.components) + Me.CONTEXT_BTT_FIND = New System.Windows.Forms.ToolStripMenuItem() + Me.CONTEXT_BTT_INFO = New System.Windows.Forms.ToolStripMenuItem() + Me.CONTEXT_BTT_OPEN_FOLDER = New System.Windows.Forms.ToolStripMenuItem() + Me.CONTEXT_BTT_OPEN_SITE = New System.Windows.Forms.ToolStripMenuItem() + SEP_1 = New System.Windows.Forms.ToolStripSeparator() + CONTEXT_SEP_1 = New System.Windows.Forms.ToolStripSeparator() + MENU_SEP_1 = New System.Windows.Forms.ToolStripSeparator() + MENU_SEP_2 = New System.Windows.Forms.ToolStripSeparator() + Me.Toolbar_TOP.SuspendLayout() + Me.Toolbar_BOTTOM.SuspendLayout() + Me.CONTEXT_LIST.SuspendLayout() + Me.SuspendLayout() + ' + 'SEP_1 + ' + SEP_1.Name = "SEP_1" + SEP_1.Size = New System.Drawing.Size(6, 25) + ' + 'CONTEXT_SEP_1 + ' + CONTEXT_SEP_1.Name = "CONTEXT_SEP_1" + CONTEXT_SEP_1.Size = New System.Drawing.Size(166, 6) + ' + 'MENU_SEP_1 + ' + MENU_SEP_1.Name = "MENU_SEP_1" + MENU_SEP_1.Size = New System.Drawing.Size(175, 6) + ' + 'MENU_SEP_2 + ' + MENU_SEP_2.Name = "MENU_SEP_2" + MENU_SEP_2.Size = New System.Drawing.Size(175, 6) + ' + 'Toolbar_TOP + ' + Me.Toolbar_TOP.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden + Me.Toolbar_TOP.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_START, Me.BTT_CANCEL, SEP_1, Me.MENU_VIEW}) + Me.Toolbar_TOP.Location = New System.Drawing.Point(0, 0) + Me.Toolbar_TOP.Name = "Toolbar_TOP" + Me.Toolbar_TOP.ShowItemToolTips = False + Me.Toolbar_TOP.Size = New System.Drawing.Size(284, 25) + Me.Toolbar_TOP.TabIndex = 0 + ' + 'BTT_START + ' + Me.BTT_START.AutoToolTip = False + Me.BTT_START.Image = Global.SCrawler.My.Resources.Resources.StartPic_Green_16 + Me.BTT_START.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_START.Name = "BTT_START" + Me.BTT_START.Size = New System.Drawing.Size(76, 22) + Me.BTT_START.Text = "Calculate" + ' + 'BTT_CANCEL + ' + Me.BTT_CANCEL.AutoToolTip = False + Me.BTT_CANCEL.Enabled = False + Me.BTT_CANCEL.Image = Global.SCrawler.My.Resources.Resources.DeletePic_24 + Me.BTT_CANCEL.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_CANCEL.Name = "BTT_CANCEL" + Me.BTT_CANCEL.Size = New System.Drawing.Size(63, 22) + Me.BTT_CANCEL.Text = "Cancel" + ' + 'MENU_VIEW + ' + Me.MENU_VIEW.DropDownItems.AddRange(New System.Windows.Forms.ToolStripItem() {Me.OPT_DATE, Me.OPT_SIZE, Me.OPT_AMOUNT, MENU_SEP_1, Me.OPT_ASC, Me.OPT_DESC, MENU_SEP_2, Me.CH_GROUP_DRIVE, Me.CH_GROUP_COL}) + Me.MENU_VIEW.Image = CType(resources.GetObject("MENU_VIEW.Image"), System.Drawing.Image) + Me.MENU_VIEW.ImageTransparentColor = System.Drawing.Color.Magenta + Me.MENU_VIEW.Name = "MENU_VIEW" + Me.MENU_VIEW.Size = New System.Drawing.Size(61, 22) + Me.MENU_VIEW.Text = "View" + ' + 'OPT_DATE + ' + Me.OPT_DATE.CheckOnClick = True + Me.OPT_DATE.Name = "OPT_DATE" + Me.OPT_DATE.Size = New System.Drawing.Size(178, 22) + Me.OPT_DATE.Text = "Sort by date" + ' + 'OPT_SIZE + ' + Me.OPT_SIZE.CheckOnClick = True + Me.OPT_SIZE.Name = "OPT_SIZE" + Me.OPT_SIZE.Size = New System.Drawing.Size(178, 22) + Me.OPT_SIZE.Text = "Sort by size" + ' + 'OPT_AMOUNT + ' + Me.OPT_AMOUNT.CheckOnClick = True + Me.OPT_AMOUNT.Name = "OPT_AMOUNT" + Me.OPT_AMOUNT.Size = New System.Drawing.Size(178, 22) + Me.OPT_AMOUNT.Text = "Sort by amount" + ' + 'OPT_ASC + ' + Me.OPT_ASC.CheckOnClick = True + Me.OPT_ASC.Name = "OPT_ASC" + Me.OPT_ASC.Size = New System.Drawing.Size(178, 22) + Me.OPT_ASC.Text = "Ascending" + ' + 'OPT_DESC + ' + Me.OPT_DESC.CheckOnClick = True + Me.OPT_DESC.Name = "OPT_DESC" + Me.OPT_DESC.Size = New System.Drawing.Size(178, 22) + Me.OPT_DESC.Text = "Descending" + ' + 'CH_GROUP_DRIVE + ' + Me.CH_GROUP_DRIVE.CheckOnClick = True + Me.CH_GROUP_DRIVE.Name = "CH_GROUP_DRIVE" + Me.CH_GROUP_DRIVE.Size = New System.Drawing.Size(178, 22) + Me.CH_GROUP_DRIVE.Text = "Group by drive" + ' + 'CH_GROUP_COL + ' + Me.CH_GROUP_COL.CheckOnClick = True + Me.CH_GROUP_COL.Name = "CH_GROUP_COL" + Me.CH_GROUP_COL.Size = New System.Drawing.Size(178, 22) + Me.CH_GROUP_COL.Text = "Group by collection" + ' + 'Toolbar_BOTTOM + ' + Me.Toolbar_BOTTOM.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.PR_MAIN, Me.LBL_STATUS}) + Me.Toolbar_BOTTOM.Location = New System.Drawing.Point(0, 239) + Me.Toolbar_BOTTOM.Name = "Toolbar_BOTTOM" + Me.Toolbar_BOTTOM.Size = New System.Drawing.Size(284, 22) + Me.Toolbar_BOTTOM.TabIndex = 1 + ' + 'PR_MAIN + ' + Me.PR_MAIN.Name = "PR_MAIN" + Me.PR_MAIN.Size = New System.Drawing.Size(200, 16) + Me.PR_MAIN.Visible = False + ' + 'LBL_STATUS + ' + Me.LBL_STATUS.Name = "LBL_STATUS" + Me.LBL_STATUS.Size = New System.Drawing.Size(0, 17) + ' + 'LIST_DATA + ' + Me.LIST_DATA.Columns.AddRange(New System.Windows.Forms.ColumnHeader() {Me.COL_DEFAULT}) + Me.LIST_DATA.ContextMenuStrip = Me.CONTEXT_LIST + Me.LIST_DATA.Dock = System.Windows.Forms.DockStyle.Fill + Me.LIST_DATA.FullRowSelect = True + Me.LIST_DATA.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None + Me.LIST_DATA.HideSelection = False + Me.LIST_DATA.Location = New System.Drawing.Point(0, 25) + Me.LIST_DATA.MultiSelect = False + Me.LIST_DATA.Name = "LIST_DATA" + Me.LIST_DATA.Size = New System.Drawing.Size(284, 214) + Me.LIST_DATA.TabIndex = 2 + Me.LIST_DATA.UseCompatibleStateImageBehavior = False + Me.LIST_DATA.View = System.Windows.Forms.View.Details + ' + 'COL_DEFAULT + ' + Me.COL_DEFAULT.Text = "User" + Me.COL_DEFAULT.Width = 280 + ' + 'CONTEXT_LIST + ' + Me.CONTEXT_LIST.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.CONTEXT_BTT_FIND, Me.CONTEXT_BTT_INFO, CONTEXT_SEP_1, Me.CONTEXT_BTT_OPEN_FOLDER, Me.CONTEXT_BTT_OPEN_SITE}) + Me.CONTEXT_LIST.Name = "CONTEXT_LIST" + Me.CONTEXT_LIST.Size = New System.Drawing.Size(170, 98) + ' + 'CONTEXT_BTT_FIND + ' + Me.CONTEXT_BTT_FIND.Image = Global.SCrawler.My.Resources.Resources.InfoPic_32 + Me.CONTEXT_BTT_FIND.Name = "CONTEXT_BTT_FIND" + Me.CONTEXT_BTT_FIND.Size = New System.Drawing.Size(169, 22) + Me.CONTEXT_BTT_FIND.Text = "Find user" + ' + 'CONTEXT_BTT_INFO + ' + Me.CONTEXT_BTT_INFO.Image = Global.SCrawler.My.Resources.Resources.InfoPic_32 + Me.CONTEXT_BTT_INFO.Name = "CONTEXT_BTT_INFO" + Me.CONTEXT_BTT_INFO.Size = New System.Drawing.Size(169, 22) + Me.CONTEXT_BTT_INFO.Text = "Show information" + ' + 'CONTEXT_BTT_OPEN_FOLDER + ' + Me.CONTEXT_BTT_OPEN_FOLDER.Image = Global.SCrawler.My.Resources.Resources.FolderPic_32 + Me.CONTEXT_BTT_OPEN_FOLDER.Name = "CONTEXT_BTT_OPEN_FOLDER" + Me.CONTEXT_BTT_OPEN_FOLDER.Size = New System.Drawing.Size(169, 22) + Me.CONTEXT_BTT_OPEN_FOLDER.Text = "Open folder" + ' + 'CONTEXT_BTT_OPEN_SITE + ' + Me.CONTEXT_BTT_OPEN_SITE.Image = Global.SCrawler.My.Resources.Resources.GlobePic_32 + Me.CONTEXT_BTT_OPEN_SITE.Name = "CONTEXT_BTT_OPEN_SITE" + Me.CONTEXT_BTT_OPEN_SITE.Size = New System.Drawing.Size(169, 22) + Me.CONTEXT_BTT_OPEN_SITE.Text = "Open site" + ' + 'UsersInfoForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(284, 261) + Me.Controls.Add(Me.LIST_DATA) + Me.Controls.Add(Me.Toolbar_BOTTOM) + Me.Controls.Add(Me.Toolbar_TOP) + Me.Icon = Global.SCrawler.My.Resources.Resources.UsersIcon_32 + Me.KeyPreview = True + Me.MinimumSize = New System.Drawing.Size(300, 300) + Me.Name = "UsersInfoForm" + Me.Text = "Users info" + Me.Toolbar_TOP.ResumeLayout(False) + Me.Toolbar_TOP.PerformLayout() + Me.Toolbar_BOTTOM.ResumeLayout(False) + Me.Toolbar_BOTTOM.PerformLayout() + Me.CONTEXT_LIST.ResumeLayout(False) + Me.ResumeLayout(False) + Me.PerformLayout() + + End Sub + + Private WithEvents Toolbar_TOP As ToolStrip + Private WithEvents Toolbar_BOTTOM As StatusStrip + Private WithEvents PR_MAIN As ToolStripProgressBar + Private WithEvents LBL_STATUS As ToolStripStatusLabel + Private WithEvents LIST_DATA As ListView + Private WithEvents BTT_START As ToolStripButton + Private WithEvents BTT_CANCEL As ToolStripButton + Private WithEvents COL_DEFAULT As ColumnHeader + Private WithEvents CONTEXT_LIST As ContextMenuStrip + Private WithEvents CONTEXT_BTT_FIND As ToolStripMenuItem + Private WithEvents CONTEXT_BTT_INFO As ToolStripMenuItem + Private WithEvents CONTEXT_BTT_OPEN_FOLDER As ToolStripMenuItem + Private WithEvents CONTEXT_BTT_OPEN_SITE As ToolStripMenuItem + Private WithEvents MENU_VIEW As ToolStripDropDownButton + Private WithEvents OPT_DATE As ToolStripMenuItem + Private WithEvents OPT_SIZE As ToolStripMenuItem + Private WithEvents OPT_AMOUNT As ToolStripMenuItem + Private WithEvents OPT_ASC As ToolStripMenuItem + Private WithEvents OPT_DESC As ToolStripMenuItem + Private WithEvents CH_GROUP_DRIVE As ToolStripMenuItem + Private WithEvents CH_GROUP_COL As ToolStripMenuItem + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Editors/UsersInfoForm.resx b/SCrawler/Editors/UsersInfoForm.resx new file mode 100644 index 0000000..5f92ba7 --- /dev/null +++ b/SCrawler/Editors/UsersInfoForm.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + False + + + 17, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABkSURBVDhPY6AKyO86WFDQfeg/iIYKkQZAmkNbnvyXta76 + DxViYGFi+Y8PQ5VBAMhmkGYgJs8FAw9GA5EKILFiWUFixfL/IBoqRBoAafYsOvpf0jiTvEAE2QzSLGmU + MeQCkYEBAD3tUdo+/cEPAAAAAElFTkSuQmCC + + + + 138, 17 + + + 286, 17 + + \ No newline at end of file diff --git a/SCrawler/Editors/UsersInfoForm.vb b/SCrawler/Editors/UsersInfoForm.vb new file mode 100644 index 0000000..13823b3 --- /dev/null +++ b/SCrawler/Editors/UsersInfoForm.vb @@ -0,0 +1,504 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' 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 System.Threading +Imports System.ComponentModel +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Toolbars +Imports SCrawler.API.Base +Imports UTypes = SCrawler.API.Base.UserMedia.Types +Namespace Editors + Friend Class UsersInfoForm +#Region "Declarations" + Private ReadOnly MyView As FormView + Private ReadOnly MyProgress As MyProgress + Private MyThread As Thread = Nothing + Private TokenSource As CancellationTokenSource = Nothing + Private Token As CancellationToken = Nothing + Private ReadOnly MyUsers As List(Of UserOpt) + Private ReadOnly LetterGroups As Dictionary(Of String, ListViewGroup) + Private ReadOnly MyNumberProvider As ANumbers + Private ReadOnly SizeNumberProvider As ANumbers + Private Enum EComparers As Integer + Size = 0 + [Date] = 1 + Amount = 2 + End Enum +#Region "Comparers declarations" + Private ReadOnly MyComparerDate As New ComparerDate + Private ReadOnly MyComparerSize As New ComparerSize + Private ReadOnly MyComparerAmount As New ComparerAmount +#End Region +#Region "Comparers classes" + Private Class ComparerDate : Implements IComparer(Of UserOpt) + Protected _Order As Integer = -1 + Friend Property Order As SortOrder + Get + Return IIf(_Order = -1, SortOrder.Descending, SortOrder.Ascending) + End Get + Set(ByVal _Order As SortOrder) + If _Order = SortOrder.Descending Then Me._Order = -1 Else Me._Order = 1 + End Set + End Property + Friend Overridable Function Compare(ByVal x As UserOpt, ByVal y As UserOpt) As Integer Implements IComparer(Of UserOpt).Compare + Dim xd& = If(x.User.LastUpdated, New Date).Ticks + Dim yd& = If(y.User.LastUpdated, New Date).Ticks + Return xd.CompareTo(yd) * _Order + End Function + End Class + Private Class ComparerSize : Inherits ComparerDate + Friend Overrides Function Compare(ByVal x As UserOpt, ByVal y As UserOpt) As Integer + Return x.TotalSize.CompareTo(y.TotalSize) * _Order + End Function + End Class + Private Class ComparerAmount : Inherits ComparerDate + Friend Overrides Function Compare(ByVal x As UserOpt, ByVal y As UserOpt) As Integer + Return x.Files.Count.CompareTo(y.Files.Count) * _Order + End Function + End Class +#End Region +#Region "Classes" + Private Structure FileOpt + Friend File As SFile + Friend Size As Double + Friend Type As UTypes + Friend Sub New(ByVal f As SFile, Optional ByVal CalculateSize As Boolean = False) + File = f + If CalculateSize Then Size = File.Size + Type = UTypes.Undefined + If Not f.Extension.IsEmptyString Then + Select Case f.Extension + Case "jpg", "jped", "png", "webp" : Type = UTypes.Picture + Case "gif" : Type = UTypes.GIF + Case "mp4", "mkv" : Type = UTypes.Video + End Select + End If + End Sub + Public Shared Widening Operator CType(ByVal f As SFile) As FileOpt + Return New FileOpt(f) + End Operator + Public Shared Widening Operator CType(ByVal f As FileOpt) As SFile + Return f.File + End Operator + Public Shared Narrowing Operator CType(ByVal f As FileOpt) As Double + Return f.Size + End Operator + End Structure + Private NotInheritable Class UserOpt : Implements IComparable(Of UserOpt), IDisposable + Friend Property User As UserDataBase + Friend Property UserPath As SFile + Friend Property Letter As String + Friend ReadOnly Property Files As List(Of FileOpt) + Friend Property TotalSize As Double = 0 + Friend Property CollectionName As String + Friend Property Name As String + Friend Property Site As String + Friend Property Key As String + Private ReadOnly NumberProvider As New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral, .DecimalDigits = 2, .TrimDecimalDigits = True} + Friend Sub New(ByVal User As UserDataBase) + Me.User = User + Files = New List(Of FileOpt) + CollectionName = User.CollectionName + Site = User.Site + Name = User.FriendlyName.IfNullOrEmpty(User.Name) + Key = User.LVIKey + UserPath = User.User.File.CutPath + Letter = UserPath.Segments.FirstOrDefault.StringToUpper.StringTrimEnd(":") + End Sub + Friend Sub GetFiles() + If UserPath.Exists(SFO.Path, False) Then + Dim files As List(Of SFile) = SFile.GetFiles(UserPath,, IO.SearchOption.AllDirectories, EDP.ReturnValue) + If files.ListExists Then + For Each f As SFile In files : Me.Files.Add(New FileOpt(f, True)) : Next + TotalSize = Me.Files.Sum(Function(ff) ff.Size) + End If + End If + End Sub + Friend Function GetLVI(ByVal LetterGroup As ListViewGroup, ByVal CollectionGroup As Boolean) As ListViewItem + Dim lvi As New ListViewItem + Dim s$ = String.Empty + If Not CollectionName.IsEmptyString Then s = $"{IIf(CollectionGroup, " ", String.Empty)}{CollectionName}" + s.StringAppend(Site, ".") + s.StringAppend(Name, ".") + s &= $" [{GetSizeStr(TotalSize)}]" + If Not User.UserExists Then + s &= " DELETED" + ElseIf User.UserSuspended Then + s &= " SUSPENDED" + End If + s &= ": " + Dim infoStr$ = String.Empty + infoStr.StringAppend(GetInfoStr(UTypes.Picture), "; ") + infoStr.StringAppend(GetInfoStr(UTypes.GIF), "; ") + infoStr.StringAppend(GetInfoStr(UTypes.Video), "; ") + infoStr.StringAppend(GetInfoStr(UTypes.Undefined), "; ") + If Not infoStr.IsEmptyString Then infoStr &= "; " + If User.LastUpdated.HasValue Then + infoStr &= $"({User.LastUpdated.Value.ToStringDate(ADateTime.Formats.BaseDate)})" + Else + infoStr &= "(not downloaded yet)" + End If + s &= infoStr + lvi.Text = s + lvi.Name = Key + lvi.Tag = Me + lvi.Group = LetterGroup + Return lvi + End Function + Private Function GetSizeStr(ByVal Value As Double) As String + If Value > 0 Then + Dim sizeText$ = "Mb" + Dim sizeValue# = Value / 1024 / 1024 + If sizeValue > 1000 Then sizeValue /= 1024 : sizeText = "Gb" + Return $"{sizeValue.RoundVal(2).NumToString(NumberProvider)}{sizeText}" + Else + Return "0Kb" + End If + End Function + Private Function GetInfoStr(ByVal t As UTypes, Optional ByVal Separator As String = " ") As String + Dim OutStr$ = String.Empty + Dim d As IEnumerable(Of FileOpt) = Files.Where(Function(f) f.Type = t) + If d.ListExists Then + Return $"{t} ({d.Count.NumToString(NumberProvider)}){Separator}[{GetSizeStr(d.Sum(Function(dd) dd.Size))}]" + Else + Return String.Empty + End If + End Function + Friend Function GetInfornation() As String + Dim s$ = String.Empty + + If Not CollectionName.IsEmptyString Then s &= $"Collection: {CollectionName}" + s.StringAppendLine(Site) + s.StringAppendLine(Name) + s.StringAppendLine($"Total size: {GetSizeStr(TotalSize)}") + + s &= vbNewLine + + s.StringAppendLine(GetInfoStr(UTypes.Picture, ": ")) + s.StringAppendLine(GetInfoStr(UTypes.GIF, ": ")) + s.StringAppendLine(GetInfoStr(UTypes.Video, ": ")) + s.StringAppendLine(GetInfoStr(UTypes.Undefined, ": ")) + + If Not User.UserExists Then + s.StringAppendLine("User DELETED") + ElseIf User.UserSuspended Then + s.StringAppendLine("User SUSPENDED") + End If + + s.StringAppendLine("Last download date: ") + If User.LastUpdated.HasValue Then + s &= User.LastUpdated.Value.ToStringDate(ADateTime.Formats.BaseDate) + Else + s &= "not downloaded yet" + End If + + Return s + End Function +#Region "IComparable Support" + Private Function CompareTo(ByVal Other As UserOpt) As Integer Implements IComparable(Of UserOpt).CompareTo + Return TotalSize.CompareTo(Other.TotalSize) * -1 + End Function +#End Region +#Region "IDisposable Support" + Private disposedValue As Boolean = False + Protected Overloads Sub Dispose(ByVal disposing As Boolean) + If Not disposedValue Then + If disposing Then Files.Clear() + 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 Region +#End Region +#Region "Initializer" + Friend Sub New() + InitializeComponent() + MyView = New FormView(Me, Settings.Design) + MyProgress = New MyProgress(Toolbar_BOTTOM, PR_MAIN, LBL_STATUS) + MyUsers = New List(Of UserOpt) + LetterGroups = New Dictionary(Of String, ListViewGroup) + MyNumberProvider = New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral} + SizeNumberProvider = New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral, .DecimalDigits = 2, .TrimDecimalDigits = True} + End Sub +#End Region +#Region "Form handlers" + Private Sub UsersInfoForm_Load(sender As Object, e As EventArgs) Handles Me.Load + MyView.Import() + MyView.SetFormSize() + + OPT_DATE.Tag = CInt(EComparers.Date) + OPT_SIZE.Tag = CInt(EComparers.Size) + OPT_AMOUNT.Tag = CInt(EComparers.Amount) + Select Case Settings.UMetrics_What.Value + Case EComparers.Date : OPT_DATE.Checked = True + Case EComparers.Amount : OPT_AMOUNT.Checked = True + Case Else : OPT_SIZE.Checked = True + End Select + + OPT_ASC.Tag = CInt(SortOrder.Ascending) + OPT_DESC.Tag = CInt(SortOrder.Descending) + If Settings.UMetrics_Order.Value = SortOrder.Ascending Then + OPT_ASC.Checked = True + Else + OPT_DESC.Checked = True + End If + + CH_GROUP_DRIVE.Checked = Settings.UMetrics_ShowDrives + CH_GROUP_COL.Checked = Settings.UMetrics_ShowCollections + LIST_DATA.ShowGroups = CH_GROUP_DRIVE.Checked + + COL_DEFAULT.Width = -2 + End Sub + Private Sub UsersInfoForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + e.Cancel = True + Hide() + End Sub + Private Sub UsersInfoForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + Abort() + MyProgress.Dispose() + MyView.Dispose() + End Sub + Private Sub UsersInfoForm_ResizeEnd(sender As Object, e As EventArgs) Handles Me.ResizeEnd + Try : ControlInvokeFast(LIST_DATA, Sub() COL_DEFAULT.Width = -2, EDP.None) : Catch : End Try + End Sub +#End Region +#Region "Calculating" + Private Sub Abort() + Try + If If(MyThread?.IsAlive, False) Then TokenSource.Cancel() : MyThread.Abort() + Catch ex As Exception + End Try + End Sub + Private _CalculationInProgress As Boolean = False + Private Sub BTT_START_Click(sender As Object, e As EventArgs) Handles BTT_START.Click + If Not If(MyThread?.IsAlive, False) Then + _CalculationInProgress = True + MyUsers.ListClearDispose + LetterGroups.Clear() + LIST_DATA.Groups.Clear() + LIST_DATA.Items.Clear() + If Not TokenSource Is Nothing Then TokenSource.Dispose() + TokenSource = New CancellationTokenSource + Token = TokenSource.Token + ChangeControlsEnabled(True) + MyThread = New Thread(New ThreadStart(AddressOf Calculate)) + MyThread.SetApartmentState(ApartmentState.MTA) + MyThread.IsBackground = True + MyThread.Start() + Else + MsgBoxE({"The calculating is already underway", "Calculating"}, vbCritical) + End If + End Sub + Private Sub BTT_CANCEL_Click(sender As Object, e As EventArgs) Handles BTT_CANCEL.Click + TokenSource.Cancel() + ControlInvokeFast(Toolbar_TOP, BTT_CANCEL, Sub() BTT_CANCEL.Enabled = False, EDP.None) + End Sub + Private Sub ChangeControlsEnabled(ByVal Working As Boolean) + Try + ControlInvokeFast(Toolbar_TOP, BTT_START, Sub() + BTT_START.Enabled = Not Working + BTT_CANCEL.Enabled = Working + End Sub, EDP.None) + If Not Working Then MainFrameObj.UpdateLogButton() + Catch + End Try + End Sub + Private Sub Calculate() + Try + MyProgress.Visible = True + MyProgress.Reset() + If Settings.Users.Count > 0 Then + With Settings.Users.SelectMany(Function(ByVal u As IUserData) As IEnumerable(Of IUserData) + If u.IsCollection Then + With DirectCast(u, API.UserDataBind) + If .Count > 0 Then Return .Collections Else Return New UserDataBase() {} + End With + Else + Return {u} + End If + End Function) + If .ListExists Then .ToList.ForEach(Sub(u As UserDataBase) MyUsers.Add(New UserOpt(u))) + End With + End If + + If MyUsers.Count > 0 Then + MyProgress.Maximum += MyUsers.Count + Dim i% = 0 + + Dim letters As IEnumerable(Of String) = MyUsers.Select(Function(u) u.Letter).Distinct + LetterGroups.Clear() + If letters.ListExists(2) Then + ControlInvokeFast(LIST_DATA, Sub() + For Each l$ In letters + LetterGroups.Add(l, New ListViewGroup(l, $"Drive {l}")) + LIST_DATA.Groups.Add(LetterGroups.Last.Value) + Next + End Sub, EDP.None) + End If + + MyProgress.Information = "Calculating of user metrics" + For Each user As UserOpt In MyUsers + Token.ThrowIfCancellationRequested() + i += 1 + MyProgress.Perform() + user.GetFiles() + Next + + _CalculationInProgress = False + RefillList() + End If + MyProgress.Done() + MyProgress.InformationTemporary = "All user metrics have been calculated." + Catch oex As OperationCanceledException + MyProgress.Done() + MyProgress.InformationTemporary = "Operation canceled" + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, "[UsersInfoForm.Calculate]") + MyProgress.Done() + MyProgress.InformationTemporary = "An error occurred while calculating user metrics." + Finally + MyProgress.Visible(, False) = False + ChangeControlsEnabled(False) + _CalculationInProgress = False + End Try + End Sub + Private _RefillInProgress As Boolean = False + Private Sub RefillList() + If Not _CalculationInProgress AndAlso Not _RefillInProgress AndAlso MyUsers.Count > 0 Then + _RefillInProgress = True + ControlInvokeFast(LIST_DATA, Sub() LIST_DATA.Items.Clear(), EDP.None) + If MyUsers.Count > 0 Then + Dim i% = 0 + Dim g As Func(Of UserOpt, ListViewGroup) = Function(u) If(LetterGroups.Count > 1, LetterGroups(u.Letter), Nothing) + Dim comparer As IComparer(Of UserOpt) + + Select Case True + Case OPT_DATE.Checked : comparer = MyComparerDate + Case OPT_AMOUNT.Checked : comparer = MyComparerAmount + Case Else : comparer = MyComparerSize + End Select + DirectCast(comparer, ComparerDate).Order = IIf(OPT_ASC.Checked, SortOrder.Ascending, SortOrder.Descending) + + MyUsers.Sort(comparer) + ControlInvokeFast(LIST_DATA, Sub() + Dim user As UserOpt + Dim gg As Boolean = CH_GROUP_COL.Checked + Dim colUsers As New Dictionary(Of String, List(Of UserOpt)) + Dim colUsersNo As New List(Of UserOpt) + Dim lvi As ListViewItem + Dim s# + Dim sn$ + + For Each user In MyUsers + If gg And Not user.CollectionName.IsEmptyString Then + If colUsers.ContainsKey(user.CollectionName) Then + colUsers(user.CollectionName).Add(user) + Else + colUsers.Add(user.CollectionName, New List(Of UserOpt) From {user}) + End If + Else + colUsersNo.Add(user) + End If + Next + + If colUsers.Count > 0 Then + For Each kv As KeyValuePair(Of String, List(Of UserOpt)) In colUsers + sn = "Mb" + s = kv.Value.Sum(Function(v) v.TotalSize) / 1024 / 1024 + If s > 1000 Then s /= 1024 : sn = "Gb" + lvi = New ListViewItem($"Collection: {kv.Key}: {s.RoundVal(2).NumToString(SizeNumberProvider)}{sn}") With { + .Tag = kv.Value(0), + .Name = Settings.GetUser(kv.Value(0).User, True).Key, + .Group = g(kv.Value(0)) + } + LIST_DATA.Items.Add(lvi) + For Each user In kv.Value : LIST_DATA.Items.Add(user.GetLVI(g(user), gg)) : Next + Next + End If + + If colUsersNo.Count > 0 Then + For Each user In colUsersNo : LIST_DATA.Items.Add(user.GetLVI(g(user), gg)) : Next + End If + + COL_DEFAULT.Width = -2 + End Sub, EDP.None) + End If + _RefillInProgress = False + End If + End Sub +#End Region +#Region "View" + Private Sub OPT_SORT_Click(ByVal Sender As ToolStripMenuItem, ByVal e As EventArgs) Handles OPT_DATE.Click, OPT_SIZE.Click, OPT_AMOUNT.Click + If Not Sender.Checked Then + Sender.Checked = True + Else + Settings.UMetrics_What.Value = Sender.Tag + For Each obj As ToolStripMenuItem In {OPT_DATE, OPT_SIZE, OPT_AMOUNT} + If Not obj Is Sender Then obj.Checked = False + Next + RefillList() + End If + End Sub + Private Sub OPT_ASC_DESC_Click(ByVal Sender As ToolStripMenuItem, ByVal e As EventArgs) Handles OPT_ASC.Click, OPT_DESC.Click + If Not Sender.Checked Then + Sender.Checked = True + Else + Settings.UMetrics_Order.Value = Sender.Tag + For Each obj As ToolStripMenuItem In {OPT_ASC, OPT_DESC} + If Not obj Is Sender Then obj.Checked = False + Next + RefillList() + End If + End Sub + Private Sub CH_GROUP_DRIVE_Click(ByVal Sender As ToolStripMenuItem, ByVal e As EventArgs) Handles CH_GROUP_DRIVE.Click + LIST_DATA.ShowGroups = Sender.Checked + Settings.UMetrics_ShowDrives.Value = Sender.Checked + End Sub + Private Sub CH_GROUP_COL_Click(ByVal Sender As ToolStripMenuItem, ByVal e As EventArgs) Handles CH_GROUP_COL.Click + Settings.UMetrics_ShowCollections.Value = Sender.Checked + RefillList() + End Sub +#End Region +#Region "Context handlers" + Private Function GetUserFromList() As UserOpt + Try + If LIST_DATA.SelectedItems.Count > 0 Then + Dim i As ListViewItem = LIST_DATA.SelectedItems(0) + If Not i Is Nothing Then Return i.Tag + End If + Catch ex As Exception + End Try + Return Nothing + End Function + Private Sub CONTEXT_BTT_FIND_Click(sender As Object, e As EventArgs) Handles CONTEXT_BTT_FIND.Click + MainFrameObj.FocusUser(If(GetUserFromList()?.Key, String.Empty), True) + End Sub + Private Sub CONTEXT_BTT_INFO_Click(sender As Object, e As EventArgs) Handles CONTEXT_BTT_INFO.Click + Dim info$ = If(GetUserFromList()?.GetInfornation(), String.Empty) + If Not info.IsEmptyString Then MsgBoxE({info, "User information"}) + End Sub + Private Sub CONTEXT_BTT_OPEN_FOLDER_Click(sender As Object, e As EventArgs) Handles CONTEXT_BTT_OPEN_FOLDER.Click + Dim u As UserOpt = GetUserFromList() + If Not u Is Nothing Then u.User.OpenFolder() + End Sub + Private Sub CONTEXT_BTT_OPEN_SITE_Click(sender As Object, e As EventArgs) Handles CONTEXT_BTT_OPEN_SITE.Click + Dim u As UserOpt = GetUserFromList() + If Not u Is Nothing Then u.User.OpenSite() + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/MainFrame.Designer.vb b/SCrawler/MainFrame.Designer.vb index 16272b5..ff4c00e 100644 --- a/SCrawler/MainFrame.Designer.vb +++ b/SCrawler/MainFrame.Designer.vb @@ -51,7 +51,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_EDIT_USER = New System.Windows.Forms.ToolStripButton() Me.BTT_DELETE_USER = New System.Windows.Forms.ToolStripButton() Me.BTT_REFRESH = New System.Windows.Forms.ToolStripButton() - Me.BTT_SHOW_INFO = New System.Windows.Forms.ToolStripButton() + Me.BTT_SHOW_INFO = New PersonalUtilities.Forms.Controls.KeyClick.ToolStripButtonKeyClick() Me.BTT_FEED = New System.Windows.Forms.ToolStripButton() Me.BTT_CHANNELS = New System.Windows.Forms.ToolStripButton() Me.BTT_DOWN_SAVED = New System.Windows.Forms.ToolStripButton() @@ -327,8 +327,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Me.BTT_SHOW_INFO.Name = "BTT_SHOW_INFO" Me.BTT_SHOW_INFO.Size = New System.Drawing.Size(48, 22) Me.BTT_SHOW_INFO.Text = "Info" - Me.BTT_SHOW_INFO.ToolTipText = "Left-click: open the 'Info' form (show download summary)." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Right click: open the " & - "'Missing' form (show information about missing posts)." + Me.BTT_SHOW_INFO.ToolTipText = resources.GetString("BTT_SHOW_INFO.ToolTipText") ' 'BTT_FEED ' @@ -940,7 +939,7 @@ Partial Public Class MainFrame : Inherits System.Windows.Forms.Form Private WithEvents BTT_CONTEXT_COL_MERGE As ToolStripMenuItem Private WithEvents LBL_JOBS_COUNT As ToolStripStatusLabel Private WithEvents BTT_DOWN_VIDEO As ToolStripMenuItem - Private WithEvents BTT_SHOW_INFO As ToolStripButton + Private WithEvents BTT_SHOW_INFO As PersonalUtilities.Forms.Controls.KeyClick.ToolStripButtonKeyClick Private WithEvents BTT_CHANNELS As ToolStripButton Private WithEvents LIST_PROFILES As ListView Private WithEvents MENU_VIEW As ToolStripDropDownButton diff --git a/SCrawler/MainFrame.resx b/SCrawler/MainFrame.resx index a16ffcf..56e777a 100644 --- a/SCrawler/MainFrame.resx +++ b/SCrawler/MainFrame.resx @@ -183,6 +183,11 @@ 132, 17 + + Left-click: open the 'Info' form (show download summary). +Right click: open the 'Missing' form (show information about missing posts). +Ctrl+Shift+Click: open the "User metrics' form (show information about the user's metrics (such as size, number of files, etc.)). + diff --git a/SCrawler/MainFrame.vb b/SCrawler/MainFrame.vb index f90fdf8..549452f 100644 --- a/SCrawler/MainFrame.vb +++ b/SCrawler/MainFrame.vb @@ -27,6 +27,7 @@ Public Class MainFrame Private MyMissingPosts As MissingPostsForm Private MyFeed As DownloadFeedForm Private MySearch As UserSearchForm + Private MyUserMetrics As UsersInfoForm = Nothing Private _UFinit As Boolean = True #End Region #Region "Initializer" @@ -57,7 +58,7 @@ Public Class MainFrame YouTube.MyCache = Settings.Cache YouTube.MyYouTubeSettings = New YouTube.YTSettings_Internal UpdateYouTubeSettings() - MainProgress = New Toolbars.MyProgress(Toolbar_BOTTOM, PR_MAIN, LBL_STATUS, "Downloading profiles' data") With { + MainProgress = New MyProgressExt(Toolbar_BOTTOM, PR_MAIN, LBL_STATUS, "Downloading profiles' data") With { .ResetProgressOnMaximumChanges = False, .Visible = False} Downloader = New TDownloader InfoForm = New DownloadedInfoForm @@ -158,6 +159,7 @@ Public Class MainFrame VideoDownloader.DisposeIfReady() MySavedPosts.DisposeIfReady() MySearch.DisposeIfReady() + MyUserMetrics.DisposeIfReady() MyView.Dispose(Settings.Design) Settings.Dispose() Else @@ -401,12 +403,17 @@ CloseResume: End Sub #End Region #Region "Info, Feed, Channels, Saved posts" - Private Sub BTT_SHOW_INFO_MouseDown(sender As Object, e As MouseEventArgs) Handles BTT_SHOW_INFO.MouseDown - If e.Button = MouseButtons.Right Then + Private Sub BTT_SHOW_INFO_KeyClick(ByVal Sender As Object, ByVal e As Controls.KeyClick.KeyClickEventArgs) Handles BTT_SHOW_INFO.KeyClick + If e.MouseButton = MouseButtons.Right Then If MyMissingPosts Is Nothing Then MyMissingPosts = New MissingPostsForm If MyMissingPosts.Visible Then MyMissingPosts.BringToFront() Else MyMissingPosts.Show() - ElseIf e.Button = MouseButtons.Left Then - InfoForm.FormShow() + ElseIf e.MouseButton = MouseButtons.Left Then + If e.Control And e.Shift Then + If MyUserMetrics Is Nothing Then MyUserMetrics = New UsersInfoForm + MyUserMetrics.FormShowS + Else + InfoForm.FormShow() + End If End If End Sub Private Sub ShowFeed() Handles BTT_FEED.Click, BTT_TRAY_FEED_SHOW.Click @@ -1206,26 +1213,31 @@ CloseResume: FocusUser(Key, True) End Sub Friend Overloads Sub FocusUser(ByVal Key As String, Optional ByVal ActivateMe As Boolean = False) - Dim a As Action = Sub() - Dim i% = LIST_PROFILES.Items.IndexOfKey(Key) - If i < 0 Then - Dim u As IUserData = Settings.GetUser(Key, True) - If Not u Is Nothing Then - UserListUpdate(u, True) - i = LIST_PROFILES.Items.IndexOfKey(u.Key) + If Not Key.IsEmptyString Then + Dim a As Action = Sub() + Dim i% = LIST_PROFILES.Items.IndexOfKey(Key) + If i < 0 Then + Dim u As IUserData = Settings.GetUser(Key, True) + If Not u Is Nothing Then + i = LIST_PROFILES.Items.IndexOfKey(u.Key) + If i < 0 Then + UserListUpdate(u, True) + i = LIST_PROFILES.Items.IndexOfKey(u.Key) + End If + End If End If - End If - If i >= 0 Then - LIST_PROFILES.Select() - LIST_PROFILES.SelectedIndices.Clear() - With LIST_PROFILES.Items(i) : .Selected = True : .Focused = True : End With - LIST_PROFILES.EnsureVisible(i) - If ActivateMe Then - If Visible Then BringToFront() Else Visible = True + If i >= 0 Then + LIST_PROFILES.Select() + LIST_PROFILES.SelectedIndices.Clear() + With LIST_PROFILES.Items(i) : .Selected = True : .Focused = True : End With + LIST_PROFILES.EnsureVisible(i) + If ActivateMe Then + If Visible Then BringToFront() Else Visible = True + End If End If - End If - End Sub - If LIST_PROFILES.InvokeRequired Then LIST_PROFILES.Invoke(a) Else a.Invoke + End Sub + If LIST_PROFILES.InvokeRequired Then LIST_PROFILES.Invoke(a) Else a.Invoke + End If End Sub #End Region #Region "Toolbar bottom" diff --git a/SCrawler/MainMod.vb b/SCrawler/MainMod.vb index 11ef5c9..875cc00 100644 --- a/SCrawler/MainMod.vb +++ b/SCrawler/MainMod.vb @@ -90,7 +90,7 @@ Friend Module MainMod End Sub End Class #End Region - Friend Property MainProgress As MyProgress + Friend Property MainProgress As MyProgressExt Friend Function GetLviGroupName(ByVal Host As SettingsHost, ByVal IsCollection As Boolean) As ListViewGroup() Dim l As New List(Of ListViewGroup) Dim t$ diff --git a/SCrawler/My Project/AssemblyInfo.vb b/SCrawler/My Project/AssemblyInfo.vb index 0c9c701..a880f13 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/MyProgressExt.vb b/SCrawler/MyProgressExt.vb new file mode 100644 index 0000000..d51795b --- /dev/null +++ b/SCrawler/MyProgressExt.vb @@ -0,0 +1,176 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' 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.Toolbars +Friend Class PreProgress : Implements IDisposable + Private ReadOnly Progress As MyProgressExt = Nothing + Private ReadOnly ProgressExists As Boolean = False + Private ReadOnly Property Ready As Boolean + Get + Return ProgressExists And Not disposedValue + End Get + End Property + Friend Sub New(ByVal PR As MyProgress) + If Not PR Is Nothing AndAlso TypeOf PR Is MyProgressExt Then + Progress = PR + ProgressExists = True + End If + End Sub + Private _Maximum As Integer = 0 + Friend Sub ChangeMax(ByVal Value As Integer, Optional ByVal Add As Boolean = True) + If Ready Then + If Add Then + _Maximum += Value + If Value > 0 Then Progress.Maximum0 += Value + Else + _Maximum = Value + Progress.Maximum0 = Value + End If + End If + End Sub + Private CumulVal As Integer = 0 + Friend Sub Perform(Optional ByVal Value As Integer = 1) + If Ready Then + CumulVal += Value + Progress.Perform0(Value) + End If + End Sub + Friend Sub Reset() + _Maximum = 0 + CumulVal = 0 + End Sub + Friend Sub Done() + If Ready Then + Dim v# = _Maximum - CumulVal + If v > 0 Then + With Progress + If v + .Value0 > .Maximum0 Then v = .Maximum0 - .Value0 + If v < 0 Then v = 0 + .Perform0(v) + Reset() + End With + End If + End If + End Sub +#Region "IDisposable Support" + Private disposedValue As Boolean = False + Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean) + If Not disposedValue Then + If disposing Then Done() + 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 +Friend Class MyProgressExt : Inherits MyProgress + Private ReadOnly _Progress0ChangedEventHandlers As List(Of EventHandler(Of ProgressEventArgs)) + Friend Custom Event Progress0Changed As EventHandler(Of ProgressEventArgs) + AddHandler(ByVal h As EventHandler(Of ProgressEventArgs)) + If Not _Progress0ChangedEventHandlers.Contains(h) Then _Progress0ChangedEventHandlers.Add(h) + End AddHandler + RemoveHandler(ByVal h As EventHandler(Of ProgressEventArgs)) + _Progress0ChangedEventHandlers.Remove(h) + End RemoveHandler + RaiseEvent(ByVal Sender As Object, ByVal e As ProgressEventArgs) + If _Progress0ChangedEventHandlers.Count > 0 Then + Try + For i% = 0 To _Progress0ChangedEventHandlers.Count - 1 + Try : _Progress0ChangedEventHandlers(i).Invoke(Sender, e) : Catch : End Try + Next + Catch + End Try + End If + End RaiseEvent + End Event + Private ReadOnly _Maximum0ChangedEventHandlers As List(Of EventHandler(Of ProgressEventArgs)) + Friend Custom Event Maximum0Changed As EventHandler(Of ProgressEventArgs) + AddHandler(ByVal h As EventHandler(Of ProgressEventArgs)) + If Not _Maximum0ChangedEventHandlers.Contains(h) Then _Maximum0ChangedEventHandlers.Add(h) + End AddHandler + RemoveHandler(ByVal h As EventHandler(Of ProgressEventArgs)) + _Maximum0ChangedEventHandlers.Remove(h) + End RemoveHandler + RaiseEvent(ByVal Sender As Object, ByVal e As ProgressEventArgs) + If _Maximum0ChangedEventHandlers.Count > 0 Then + Try + For i% = 0 To _Maximum0ChangedEventHandlers.Count - 1 + Try : _Maximum0ChangedEventHandlers(i).Invoke(Sender, e) : Catch : End Try + Next + Catch + End Try + End If + End RaiseEvent + End Event + Friend Sub New() + _Progress0ChangedEventHandlers = New List(Of EventHandler(Of ProgressEventArgs)) + _Maximum0ChangedEventHandlers = New List(Of EventHandler(Of ProgressEventArgs)) + End Sub + Friend Sub New(ByRef StatusStrip As StatusStrip, ByRef ProgressBar As ToolStripProgressBar, ByRef Label As ToolStripStatusLabel, + Optional ByVal Information As String = Nothing) + MyBase.New(StatusStrip, ProgressBar, Label, Information) + _Progress0ChangedEventHandlers = New List(Of EventHandler(Of ProgressEventArgs)) + _Maximum0ChangedEventHandlers = New List(Of EventHandler(Of ProgressEventArgs)) + End Sub + Friend Sub New(ByRef ProgressBar As ProgressBar, ByRef Label As Label, Optional ByVal Information As String = Nothing) + MyBase.New(ProgressBar, Label, Information) + _Progress0ChangedEventHandlers = New List(Of EventHandler(Of ProgressEventArgs)) + _Maximum0ChangedEventHandlers = New List(Of EventHandler(Of ProgressEventArgs)) + End Sub + Private _Maximum0 As Double = 0 + Friend Property Maximum0 As Double + Get + Return _Maximum0 + End Get + Set(ByVal v As Double) + Dim b As Boolean = Not _Maximum0 = v + _Maximum0 = v + If ResetProgressOnMaximumChanges Then Value0 = 0 + If b Then RaiseEvent Maximum0Changed(Me, Nothing) + End Set + End Property + Friend Property Value0 As Double = 0 + Friend Sub Perform0(Optional ByVal Value As Double = 1) + Value0 += Value + If Perform(0, 10, False, False) Then RaiseEvent Progress0Changed(Me, Nothing) + End Sub + Public Overloads Overrides Sub Perform(Optional ByVal Value As Double = 1) + If Perform(Value, PerformMod, True, True) Then OnProgressChanged() + End Sub + Public Overloads Function Perform(ByVal Value As Double, ByVal pm As Integer, ByVal SetText As Boolean, ByVal InvokeProgressChangeHandler As Boolean) As Boolean + Me.Value += Value + If Me.Value < 0 Then Me.Value = 0 + Dim v# = Me.Value + Value0 + Dim m# = Maximum + Maximum0 + If pm = 0 OrElse (v Mod pm) = 0 OrElse v = m Then PerformImpl(GetPercentage(v, m), SetText, InvokeProgressChangeHandler) : Return True + Return False + End Function + Public Overrides Sub Done() + Value0 = Maximum0 + MyBase.Done() + End Sub + Public Overrides Sub Reset() + MyBase.Reset() + Value0 = 0 + Maximum0 = 0 + End Sub + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + If Not disposedValue And disposing Then + _Progress0ChangedEventHandlers.Clear() + _Maximum0ChangedEventHandlers.Clear() + End If + MyBase.Dispose(disposing) + End Sub +End Class \ No newline at end of file diff --git a/SCrawler/PluginsEnvironment/Hosts/UserDataHost.vb b/SCrawler/PluginsEnvironment/Hosts/UserDataHost.vb index 2181708..a5ba1fa 100644 --- a/SCrawler/PluginsEnvironment/Hosts/UserDataHost.vb +++ b/SCrawler/PluginsEnvironment/Hosts/UserDataHost.vb @@ -27,6 +27,8 @@ Namespace Plugin.Hosts UseInternalDownloader = Not ExternalPlugin.GetType.GetCustomAttribute(Of Attributes.UseInternalDownloader)() Is Nothing AddHandler ExternalPlugin.ProgressChanged, AddressOf ExternalPlugin_ProgressChanged AddHandler ExternalPlugin.ProgressMaximumChanged, AddressOf ExternalPlugin_ProgressMaximumChanged + AddHandler ExternalPlugin.ProgressPreChanged, AddressOf ExternalPlugin_Progress0Changed + AddHandler ExternalPlugin.ProgressPreMaximumChanged, AddressOf ExternalPlugin_Progress0MaximumChanged End Sub Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) If Loading Then @@ -111,6 +113,12 @@ Namespace Plugin.Hosts Private Sub ExternalPlugin_ProgressMaximumChanged(ByVal Value As Integer, ByVal Add As Boolean) Progress.Maximum = Value + If(Add, Progress.Maximum, 0) End Sub + Private Sub ExternalPlugin_Progress0Changed(ByVal Value As Integer) + ProgressPre.Perform(Value) + End Sub + Private Sub ExternalPlugin_Progress0MaximumChanged(ByVal Value As Integer, ByVal Add As Boolean) + ProgressPre.ChangeMax(Value, Add) + End Sub Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing And Not disposedValue Then With ExternalPlugin diff --git a/SCrawler/SCrawler.vbproj b/SCrawler/SCrawler.vbproj index 644a4c0..5a6cbd3 100644 --- a/SCrawler/SCrawler.vbproj +++ b/SCrawler/SCrawler.vbproj @@ -224,6 +224,7 @@ + ActiveDownloadingProgress.vb @@ -309,6 +310,12 @@ UserControl + + UsersInfoForm.vb + + + Form + @@ -316,6 +323,7 @@ True Resources.resx + @@ -523,6 +531,9 @@ UserCreatorForm.vb + + UsersInfoForm.vb + MainFrame.vb diff --git a/SCrawler/SettingsCLS.vb b/SCrawler/SettingsCLS.vb index 27a520c..0f982c2 100644 --- a/SCrawler/SettingsCLS.vb +++ b/SCrawler/SettingsCLS.vb @@ -140,6 +140,12 @@ Friend Class SettingsCLS : Implements IDownloaderSettings, IDisposable SearchInDescription = New XMLValue(Of Boolean)("SearchInDescription", False, MyXML, n) SearchInLabel = New XMLValue(Of Boolean)("SearchInLabel", False, MyXML, n) + n = {"Metrics"} + UMetrics_What = New XMLValue(Of Integer)("What", -1, MyXML, n) + UMetrics_Order = New XMLValue(Of Integer)("Order", SortOrder.Descending, MyXML, n) + UMetrics_ShowDrives = New XMLValue(Of Boolean)("ShowDrives", True, MyXML, n) + UMetrics_ShowCollections = New XMLValue(Of Boolean)("ShowCollections", True, MyXML, n) + n = {"Defaults"} DefaultTemporary = New XMLValue(Of Boolean)("Temporary", False, MyXML, n) DefaultDownloadImages = New XMLValue(Of Boolean)("DownloadImages", True, MyXML, n) @@ -310,7 +316,7 @@ Friend Class SettingsCLS : Implements IDownloaderSettings, IDisposable End Using Dim NeedUpdate As Boolean = False - Dim i%, indx% ', c% + Dim i%, indx% Dim UsersListInitialCount% = UsersList.Count Dim iUser As UserInfo Dim userFileExists As Boolean, pluginFound As Boolean @@ -349,9 +355,7 @@ Friend Class SettingsCLS : Implements IDownloaderSettings, IDisposable End If 'Check paths - 'c = IIf((Not .IncludedInCollection Or (.Merged Or .IsVirtual)) And Not .Plugin = PathPlugin.PluginKey, 1, 2) - 'URGENT: changed user file validation - userFileExists = .File.Exists ' SFile.GetPath(.File.CutPath(c - 1).Path).Exists(SFO.Path, False) + userFileExists = .File.Exists If Not pluginFound Or Not userFileExists Then If Not .IsProtected Then If userFileExists Then @@ -705,6 +709,12 @@ Friend Class SettingsCLS : Implements IDownloaderSettings, IDisposable Friend ReadOnly Property STDownloader_RemoveYTVideosOnClear As XMLValue(Of Boolean) Friend ReadOnly Property STDownloader_LoadYTVideos As XMLValue(Of Boolean) #End Region +#Region "User metrics" + Friend ReadOnly Property UMetrics_What As XMLValue(Of Integer) + Friend ReadOnly Property UMetrics_Order As XMLValue(Of Integer) + Friend ReadOnly Property UMetrics_ShowDrives As XMLValue(Of Boolean) + Friend ReadOnly Property UMetrics_ShowCollections As XMLValue(Of Boolean) +#End Region #Region "User data" Friend ReadOnly Property FromChannelDownloadTop As XMLValue(Of Integer) Friend ReadOnly Property FromChannelDownloadTopUse As XMLValue(Of Boolean)