Skip to content

Commit

Permalink
Add console concurrent mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
x-stars committed Nov 3, 2018
1 parent 2e6f5ab commit 77cc06f
Show file tree
Hide file tree
Showing 12 changed files with 680 additions and 164 deletions.
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,53 @@

## YandereSpider (WPF)

新版 YandereSpider,使用 WPF 作为框架。
新版 YandereSpider,使用 WPF 作为框架。面向对象,便于功能扩展。

内含 `YandereSpider.YanderePage` 类,包含 <https://yande.re/> 页面的常见链接提取功能。
面向对象,便于功能扩展。

以内置浏览器作为导航,所有提取链接的操作均针对当前浏览器显示的页面。

能够提取和遍历 Posts 页面(包括搜索结果)和 Pools 页面。

第一次启动时会请求修改内置浏览器版本为 Internet Explorer 11,请以管理员身份启动。

目前处于开发中,未来可能会加入下载功能(可能性不大)。

### 控制台模式

本应用程序支持控制台启动,相对于窗口启动,主要优势在于**多线程并发访问**
窗口模式则因设计理念(浏览器导航)原因,不提供并发访问功能。

控制台模式的启动参数说明如下。

YandereSpider.exe PageLink [-e PageCount] [-t MaxThreads] [-h]

| 名称 | 内容 | 说明 |
| ---- | ---------- | --------------------------------------------------------------------------------------- |
| | PageLink | 要提取链接的 yande.re 页面的 URL。仅支持完整 URL 输入,必须以 <https://yande.re> 开始。 |
| -e | PageCount | 指定要遍历页面的数量;为 0 则不进行遍历,为 -1 则遍历至最后一页。 |
| -t | MaxThreads | 指定 HTTP 访问的最大线程数。 |
| -h | | 显示简要的帮助信息(英文)。 |

完成图片链接的提取后,会将所有链接以换行符分隔并复制到剪贴板。

### `YandereSpider.YanderePage`

此类包含了 <https://yande.re/> 页面的常见链接提取功能,
实现接口:

* `System.IDisposable`
* `System.Collections.Generic.IReadOnlyList<YandereSpider.YanderePage>`
* `System.IEquatable<YandereSpider.YanderePage>`

包含了 <https://yande.re/> 页面的常见链接提取功能,
包括图片链接、(Pools 页面的)Pool 链接和上一页面与下一页面的链接。

同时实现了 `System.Collections.Generic.IEnumerable<YandereSpider.YanderePage>` 接口,
能通过 `foreach` 控制语句从当前页面遍历至最后一页。
访问级别为公共 `public`,可通过引用 YandereSpider.exe 来访问此类。

## YandereSpider.WinForm

旧版 YandereSpider,已停止更新,未来不会开发新功能和修复错误,仅保留源代码作为参考
旧版 YandereSpider,使用 WinForm 作为框架。早期作品,基本面向过程

使用 WinForm 作为框架。早期作品,基本面向过程
已停止更新,未来不会开发新功能和修复错误,仅保留源代码作为参考

只能遍历 Posts 页面(包括搜索结果)和提取单个 Pool 页面,不支持遍历 Pools 页面。
2 changes: 1 addition & 1 deletion YandereSpider/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YandereSpider"
StartupUri="MainWindow.xaml">
Startup="Application_Startup">
<Application.Resources>

</Application.Resources>
Expand Down
16 changes: 16 additions & 0 deletions YandereSpider/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,21 @@ namespace YandereSpider
/// </summary>
public partial class App : Application
{
/// <summary>
/// 应用程序启动的事件处理。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">提供事件数据的对象。</param>
private void Application_Startup(object sender, StartupEventArgs e)
{
if (e.Args.Length == 0)
{
new MainWindow().Show();
}
else
{
ConsoleWindow.Show(e.Args);
}
}
}
}
152 changes: 152 additions & 0 deletions YandereSpider/ConsoleWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using XstarS;

namespace YandereSpider
{
/// <summary>
/// 控制台模式的交互逻辑。
/// </summary>
public static class ConsoleWindow
{
/// <summary>
/// 当前正在工作的后台线程的数量。
/// </summary>
private static int WorkingThreadCount = -1;

/// <summary>
/// 所有图片的链接。
/// </summary>
private static readonly ICollection<string> ImageLinks = new HashSet<string>();

/// <summary>
/// 显示控制台窗口,并传递程序启动参数。
/// </summary>
/// <param name="args">程序启动参数。</param>
public static void Show(string[] args)
{
ConsoleManager.Show();
var param = new ParamReader(args, true, new[] { "-e", "-t" }, new[] { "-h" });

if (param.GetSwitch("-h"))
{
ConsoleWindow.ShowHelp();
}
else
{
var page = new YanderePage(param.GetParam(0) ?? YanderePage.IndexPageLink);
if (!YanderePage.IsYanderePage(page))
{
throw new ArgumentException(new ArgumentException().Message, "PageLink");
}

int enumCount = int.Parse(param.GetParam("-e") ?? 0.ToString());

if (!(param.GetParam("-t") is null))
{
int maxThreads = int.Parse(param.GetParam("-t"));
if (maxThreads < 1) { throw new ArgumentOutOfRangeException("-t MaxThreads"); }
ThreadPool.SetMaxThreads(maxThreads, maxThreads);
}

if (enumCount == 0) { ConsoleWindow.ExtractPage(page); }
else { ConsoleWindow.EnumeratePages(page, enumCount); }

while (ConsoleWindow.WorkingThreadCount != 0) { Thread.Sleep(10); }
Clipboard.SetText(string.Join(Environment.NewLine, ConsoleWindow.ImageLinks));
Console.WriteLine();
Console.WriteLine("Completed.");
Console.ReadKey();
}
}

/// <summary>
/// 隐藏命令行窗口,释放标准输入流和错误流。
/// </summary>
public static void Hide()
{
ConsoleManager.Hide();
}

/// <summary>
/// 将帮助信息输出到控制台。
/// </summary>
private static void ShowHelp()
{
Console.WriteLine(@"
YandereSpider.exe PageLink [-e PageCount] [-t MaxThreads] [-h]
PageLink URL of yande.re page which contains image links.
Absolute URL only, should start with 'https://yande.re'.
-e PageCount Count of pages you want to enumerate from input page.
0 means no enumeration, -1 means enumerating to end.
-t MaxThreads The maximum threads in HTTP access.
-h Show this help message.
");
}

/// <summary>
/// 提取页面中包含的图片链接。
/// </summary>
/// <param name="page"></param>
private static void ExtarctLinks(YanderePage page)
{
if (ConsoleWindow.WorkingThreadCount == -1)
{
ConsoleWindow.WorkingThreadCount = 0;
}

ConsoleWindow.WorkingThreadCount++;
var imageLinks = page.ImageLinks;
Console.WriteLine(string.Join(Environment.NewLine, imageLinks));
foreach (var imageLink in imageLinks)
{
ConsoleWindow.ImageLinks.Add(imageLink);
}
ConsoleWindow.WorkingThreadCount--;
}

/// <summary>
/// 提取页面及其子页面中包含的图片链接。
/// </summary>
/// <param name="page">页面链接提取对象。</param>
private static void ExtractPage(YanderePage page)
{
void callback(object state) => ConsoleWindow.ExtarctLinks(state as YanderePage);

if (YanderePage.IsPoolsPage(page))
{
foreach (var poolPage in page)
{
ThreadPool.QueueUserWorkItem(callback, poolPage);
}
}
else
{
ThreadPool.QueueUserWorkItem(callback, page);
}
}

/// <summary>
/// 遍历并提取各个页面的图片链接。
/// </summary>
/// <param name="page">页面链接提取对象。</param>
/// <param name="enumCount">指定遍历的页面数量;
/// 为 -1 则遍历至最后一页,为 0 则不进行遍历。默认为 0。</param>
private static void EnumeratePages(YanderePage page, int enumCount = -1)
{
enumCount = (enumCount < 0) ?
(page.Count - page.Index + 1) :
((enumCount == 0) ? 1 : enumCount);
for (int i = page.Index; i < page.Index + enumCount; i++)
{
ConsoleWindow.ExtractPage(page[i]);
}
}
}
}
114 changes: 114 additions & 0 deletions YandereSpider/Helpers/ConsoleManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#if !CORE

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security;

namespace XstarS
{
/// <summary>
/// 提供窗口应用程序的控制台窗口管理方法。
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal static class ConsoleManager
{
/// <summary>
/// 为当前应用程序分配控制台窗口。
/// </summary>
/// <returns>是否成功分配控制台窗口。</returns>
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();

/// <summary>
/// 释放当前已经分配的控制台窗口。
/// </summary>
/// <returns>是否成功释放控制台窗口。</returns>
[DllImport("kernel32.dll")]
private static extern bool FreeConsole();

/// <summary>
/// 获取当前已分配的控制台窗口的句柄。
/// </summary>
/// <returns>当前已分配的控制台窗口的句柄。
/// 若并未分配控制台窗口,则返回 <see cref="IntPtr.Zero"/>。</returns>
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();

/// <summary>
/// 指示当前是否已经分配了控制台窗口。
/// </summary>
public static bool HasConsole =>
ConsoleManager.GetConsoleWindow() != IntPtr.Zero;

/// <summary>
/// 为当前应用程序分配控制台窗口。
/// </summary>
public static void Show()
{
if (!ConsoleManager.HasConsole)
{
ConsoleManager.AllocConsole();
ConsoleManager.InvalidateStdOutError();
}
}

/// <summary>
/// 释放当前已经分配的控制台窗口。
/// </summary>
public static void Hide()
{
if (ConsoleManager.HasConsole)
{
ConsoleManager.SetStdOutErrorNull();
ConsoleManager.FreeConsole();
}
}

/// <summary>
/// 切换控制台窗口的显示状态。
/// </summary>
public static void Toggle()
{
if (ConsoleManager.HasConsole)
{
ConsoleManager.Hide();
}
else
{
ConsoleManager.Show();
}
}

/// <summary>
/// 释放 <see cref="Console"/> 的标准输出流和错误流,以触发其重新初始化。
/// </summary>
private static void InvalidateStdOutError()
{
var t_Console = typeof(Console);
var staticNoPublic = BindingFlags.Static | BindingFlags.NonPublic;

var sf__in = t_Console.GetField("_in", staticNoPublic);
var sf__out = t_Console.GetField("_out", staticNoPublic);
var sf__error = t_Console.GetField("_error", staticNoPublic);
var sm_InitializeStdOutError =
t_Console.GetMethod("InitializeStdOutError", staticNoPublic);

sf__out.SetValue(null, null);
sf__error.SetValue(null, null);
sm_InitializeStdOutError.Invoke(null, new object[] { true });
}

/// <summary>
/// 将 <see cref="Console"/> 的标准输出流和错误流重定向至 <see cref="TextWriter.Null"/>。
/// </summary>
private static void SetStdOutErrorNull()
{
Console.SetOut(TextWriter.Null);
Console.SetError(TextWriter.Null);
}
}
}

#endif
Loading

0 comments on commit 77cc06f

Please sign in to comment.