Skip to content
This repository was archived by the owner on Apr 19, 2018. It is now read-only.

Commit 089d6a6

Browse files
committed
Fix Search Incremental Loading Strategy
1 parent d4749ec commit 089d6a6

7 files changed

Lines changed: 118 additions & 52 deletions

File tree

SharedLibrary/ExtensionMethods.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public static IXmlNode Element(this IXmlNode node, string name)
112112
/// <param name="node">父节点</param>
113113
/// <param name="name">子节点名称</param>
114114
/// <returns></returns>
115-
public static IEnumerable<IXmlNode> Elements(this IXmlNode node,string name)
115+
public static IEnumerable<IXmlNode> Elements(this IXmlNode node, string name)
116116
{
117117
foreach (var item in node.ChildNodes)
118118
if (item.LocalName?.ToString() == name)
@@ -135,7 +135,7 @@ private static HtmlNode InnerDescendant(HtmlNode mnode, string name)
135135
/// <summary>
136136
/// 将字典对象转化成Html的Query格式字符串
137137
/// </summary>
138-
public static string ToQueryString(this Dictionary<string,string> dic, bool encode = true)
138+
public static string ToQueryString(this Dictionary<string, string> dic, bool encode = true)
139139
{
140140
bool isfirst = true;
141141
StringBuilder res = new StringBuilder();
@@ -151,5 +151,10 @@ public static string ToQueryString(this Dictionary<string,string> dic, bool enco
151151
}
152152
return res.ToString();
153153
}
154+
155+
public static IEnumerable<T> ToEnumerable<T>(this T target)
156+
{
157+
yield return target;
158+
}
154159
}
155160
}

SharedLibrary/Models/BaseModels/SongModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ public string TrackArtist
141141
/// </summary>
142142
public IList<SongModel> RelateHotSongs { get { return _RelateHotSongs; } set { Set(ref _RelateHotSongs, value); } }
143143

144+
/// <summary>
145+
/// 获取和设置歌曲与某一首歌是同一首
146+
/// </summary>
147+
public SongModel DuplicateOf { get; set; }
144148

145149
public override string ToString()
146150
{

SharedLibrary/Net/Api/WebApi.cs

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Runtime.Serialization;
8+
using System.Text;
89
using System.Text.RegularExpressions;
910
using System.Threading.Tasks;
1011
using System.Xml;
@@ -37,6 +38,7 @@ public static WebApi Instance
3738
}
3839
}
3940

41+
internal bool CheckNeedLogin(string html) => html.Contains("id=\"login\"");
4042
internal void ParsePlayListSong(SongModel song, HtmlNode tr)
4143
{
4244
var name = tr.SelectSingleNode("./td[@class='song_name']");
@@ -904,50 +906,69 @@ public IAsyncOperation<SearchResult> Search(string keyword)
904906
doc.LoadHtml(content);
905907
var root = doc.DocumentNode.SelectSingleNode("html/body/div[@id='page']");
906908
var results = root.SelectSingleNode(".//div[@class='search_result']").Elements("div").ToList();
909+
907910
var match = results[0];//最佳匹配内容
908-
if (match.GetAttributeValue("class", "") == "top_box")
911+
if (match.GetAttributeValue("class", "").Contains("top_box"))
909912
{
910913
results.Remove(match);
911914
// TODO: 处理最佳匹配
912915
}
916+
913917
var songs = results[0];
914-
res.Songs = new PageItemsCollection<SongModel>(songs.Descendant("tbody").Elements("tr").Select((node) => ParseSearchedSong(node)),
915-
async (page, ptoken) =>
916-
{
917-
var pgettask = HttpHelper.GetAsync($"http://www.xiami.com/search/song/page/{page}?key={keyword}");
918-
ptoken.Register(() => pgettask.Cancel());
919-
var pcontent = await pgettask;
920-
var pdoc = new HtmlDocument();
921-
pdoc.LoadHtml(pcontent);
922-
return pdoc.DocumentNode.SelectSingleNode(".//table[@class='track_list']")
923-
.Descendant("tbody").Elements("tr").Select((node) => ParseSearchedSong(node));// 会有多个tbody,TODO:处理tbody分组(取第一个?)
924-
});
918+
var songres = ParseSearchedSongTable(songs.Descendant("table"));
919+
//System.Diagnostics.Debugger.Break();
920+
if (songs.Element("span") != null)
921+
res.Songs = new PageItemsCollection<SongModel>(songres,
922+
async (page, ptoken) =>
923+
{
924+
var pgettask = HttpHelper.GetAsync($"http://www.xiami.com/search/song/page/{page}?key={keyword}");
925+
ptoken.Register(() => pgettask.Cancel());
926+
var pcontent = await pgettask;
927+
if (CheckNeedLogin(pcontent)) return null; // 需要登录才能看5页以后的内容
928+
var pdoc = new HtmlDocument();
929+
pdoc.LoadHtml(pcontent);
930+
return ParseSearchedSongTable(pdoc.DocumentNode.SelectSingleNode(".//table[@class='track_list']"));
931+
});
932+
else
933+
res.Songs = songres;
934+
925935
var albums = results[1];
936+
var albumres = albums.Descendant("ul").Elements("li").Select((node) => ParseSearchedAlbum(node));
926937
//System.Diagnostics.Debugger.Break();
927-
res.Albums = new PageItemsCollection<AlbumModel>(30, albums.Descendant("ul").Elements("li").Select((node) => ParseSearchedAlbum(node)),
928-
async (page, ptoken) =>
929-
{
930-
var pgettask = HttpHelper.GetAsync($"http://www.xiami.com/search/album/page/{page}?key={keyword}");
931-
ptoken.Register(() => pgettask.Cancel());
932-
var pcontent = await pgettask;
933-
var pdoc = new HtmlDocument();
934-
pdoc.LoadHtml(pcontent);
935-
return pdoc.DocumentNode.SelectSingleNode(".//ul[@class='clearfix']")
936-
.Elements("li").Select((node) => ParseSearchedAlbum(node));
937-
});
938+
if (albums.Element("span") != null)
939+
res.Albums = new PageItemsCollection<AlbumModel>(30, albumres,
940+
async (page, ptoken) =>
941+
{
942+
var pgettask = HttpHelper.GetAsync($"http://www.xiami.com/search/album/page/{page}?key={keyword}");
943+
ptoken.Register(() => pgettask.Cancel());
944+
var pcontent = await pgettask;
945+
if (CheckNeedLogin(pcontent)) return null;
946+
var pdoc = new HtmlDocument();
947+
pdoc.LoadHtml(pcontent);
948+
return pdoc.DocumentNode.SelectSingleNode(".//ul[@class='clearfix']")
949+
.Elements("li").Select((node) => ParseSearchedAlbum(node));
950+
});
951+
else
952+
res.Albums = albumres.ToList();
953+
938954
var artists = results[2];
955+
var artistres = artists.Descendant("ul").Elements("li").Select((node) => ParseSearchedArtist(node));
939956
//System.Diagnostics.Debugger.Break();
940-
res.Artists = new PageItemsCollection<ArtistModel>(30, artists.Descendant("ul").Elements("li").Select((node) => ParseSearchedArtist(node)),
941-
async (page, ptoken) =>
942-
{
943-
var pgettask = HttpHelper.GetAsync($"http://www.xiami.com/search/artist/page/{page}?key={keyword}");
944-
ptoken.Register(() => pgettask.Cancel());
945-
var pcontent = await pgettask;
946-
var pdoc = new HtmlDocument();
947-
pdoc.LoadHtml(pcontent);
948-
return pdoc.DocumentNode.SelectSingleNode(".//div[@class='artistBlock_list']").Element("ul")
949-
.Elements("li").Select((node) => ParseSearchedArtist(node));
950-
});
957+
if (artists.Element("span") != null)
958+
res.Artists = new PageItemsCollection<ArtistModel>(30, artistres,
959+
async (page, ptoken) =>
960+
{
961+
var pgettask = HttpHelper.GetAsync($"http://www.xiami.com/search/artist/page/{page}?key={keyword}");
962+
ptoken.Register(() => pgettask.Cancel());
963+
var pcontent = await pgettask;
964+
if (CheckNeedLogin(pcontent)) return null;
965+
var pdoc = new HtmlDocument();
966+
pdoc.LoadHtml(pcontent);
967+
return pdoc.DocumentNode.SelectSingleNode(".//div[@class='artistBlock_list ']").Element("ul")
968+
.Elements("li").Select((node) => ParseSearchedArtist(node));
969+
});
970+
else
971+
res.Artists = artistres.ToList();
951972
if (results.Count > 3)
952973
{
953974
var collects = results[3];
@@ -965,13 +986,43 @@ public IAsyncOperation<SearchResult> Search(string keyword)
965986
});
966987
}
967988

989+
internal List<SongModel> ParseSearchedSongTable(HtmlNode table)
990+
{
991+
string temp = table.InnerHtml;
992+
foreach(Match m in Regex.Matches(temp, @"(<tbody\s)[\s\S]+?(/tbody>)"))
993+
{
994+
StringBuilder sb = new StringBuilder();
995+
sb.Append("</tbody>");
996+
sb.Append(m.Value);
997+
sb.Append("<tbody>");
998+
temp = temp.Replace(m.Value, sb.ToString());
999+
}
1000+
table.InnerHtml = temp;
1001+
List<SongModel> res = new List<SongModel>();
1002+
foreach(var node in table.Elements("tbody"))
1003+
{
1004+
if (node.GetAttributeValue("class", "").Contains("same_song_group"))
1005+
res.AddRange(node.Elements("tr").Select((tr) =>
1006+
{
1007+
var song = ParseSearchedSong(tr);
1008+
song.DuplicateOf = res[res.Count - 1];
1009+
return song;
1010+
})); // 不全部返回的话会造成歌曲数目不够,PageItem加载时会产生Exception
1011+
else
1012+
res.AddRange(node.Elements("tr").Select((tr) => ParseSearchedSong(tr)));
1013+
};
1014+
return res;
1015+
}
9681016
internal SongModel ParseSearchedSong(HtmlNode tr)
9691017
{
1018+
//System.Diagnostics.Debugger.Break();
9701019
var tds = tr.Elements("td").ToList();
9711020
var checkbox = tds[0].Descendant("input");
9721021
SongModel song = SongModel.GetNew(uint.Parse(checkbox.GetAttributeValue("value", "0")));
9731022
song.Available = checkbox.GetAttributeValue("checked", "") == "checked";
974-
song.NameHtml = tds[1].Element("a").InnerHtml;
1023+
var links = tds[1].SelectNodes("./a[@target='_blank']");
1024+
song.NameHtml = links[0].InnerHtml;
1025+
if (links.Count > 1) song.MV = MVModel.GetNew(ParseXiamiIDString(links[1].GetAttributeValue("href", "/0")));
9751026
var anode = tds[3].Element("a");
9761027
var album = AlbumModel.GetNew(ParseXiamiID(anode.GetAttributeValue("href", "/0")));
9771028
album.NameHtml = anode.InnerHtml.Replace("《", "").Replace("》", "");
@@ -983,7 +1034,6 @@ internal SongModel ParseSearchedSong(HtmlNode tr)
9831034
}
9841035
internal AlbumModel ParseSearchedAlbum(HtmlNode li)
9851036
{
986-
LogService.DebugWrite(li.OuterHtml);
9871037
//System.Diagnostics.Debugger.Break();
9881038
var ips = li.SelectNodes("./div/p");
9891039
var albumlink = ips[1].Element("a");

SharedLibrary/PageItemsCollection.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ public class PageItemsCollection<T> : IncrementalLoadingBase<T>
2525
/// <summary>
2626
/// 根据已有的完整第一页内容获取一个<see cref="PageItemsCollection{T}"/>对象
2727
/// </summary>
28-
/// <param name="firstpage">第一页的内容</param>
28+
/// <param name="firstpage">完整的第一页的内容,请确保之后的数目与该页数目一致</param>
2929
/// <param name="fetchPage">获取某一页的方法</param>
3030
public PageItemsCollection(IEnumerable<T> firstpage, FetchPageDelegate fetchPage) : base(firstpage)
3131
{
32-
AddRange(firstpage);
32+
//AddRange(firstpage); //已在base(firstpage中实现)
3333
PageCapacity = (uint)Count;
3434
_fetchPage = fetchPage;
3535
}
@@ -50,19 +50,19 @@ public PageItemsCollection(uint capacity, FetchPageDelegate fetchPage) : base()
5050
/// <param name="capacity">每一页项目数量</param>
5151
/// <param name="firstpart">第一页的部分内容</param>
5252
/// <param name="fetchpage">获取某一页的方法</param>
53-
public PageItemsCollection(uint capacity, IEnumerable<T> firstpart, FetchPageDelegate fetchpage)
53+
public PageItemsCollection(uint capacity, IEnumerable<T> firstpart, FetchPageDelegate fetchpage):base(firstpart)
5454
{
55+
//AddRange(firstpart);
5556
PageCapacity = capacity;
5657
_fetchPage = fetchpage;
57-
AddRange(firstpart);
5858
}
5959
/// <summary>
6060
/// 获取固定大小的<see cref="PageItemsCollection{T}"/>
6161
/// </summary>
6262
/// <param name="items"></param>
63-
public PageItemsCollection(IEnumerable<T> items)
63+
public PageItemsCollection(IEnumerable<T> items):base(items)
6464
{
65-
AddRange(items);
65+
//AddRange(items);
6666
PageCapacity = (uint)Count;
6767
_hasMore = false;
6868
_fetchPage = (a, b) => null;
@@ -112,19 +112,24 @@ protected override async Task<IEnumerable<T>> LoadMoreItemsAsync(CancellationTok
112112
}
113113
uint pages = count / PageCapacity + _current;
114114
if (count % PageCapacity != 0) pages++;
115-
for (uint i = _current + 1; i <= pages; i++)
115+
for (uint i = _current + 1; i <= pages && _hasMore; i++)
116116
{
117117
var r = await _fetchPage(i, c);//为了保证顺序,不使用并发.TODO: 考虑并发后排序?
118-
returnlist.AddRange(r);
119-
if (i == pages)
120-
if (r == null)
121-
_hasMore = false;
122-
else if (r.Count() < PageCapacity)
118+
//if (i == pages)
119+
if (r == null)
120+
_hasMore = false;
121+
else
122+
{
123+
returnlist.AddRange(r);
124+
if (r.Count() < PageCapacity)
123125
_hasMore = false;
124126
else
127+
{
125128
_hasMore = true;
129+
_current = i;
130+
}
131+
}
126132
}
127-
_current = pages;
128133
return returnlist;
129134
}
130135
}

XiamiTing/Views/DiscoveryPage.xaml

280 Bytes
Binary file not shown.

XiamiTing/Views/SearchPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<RowDefinition Height="Auto"/>
5252
<RowDefinition/>
5353
</Grid.RowDefinitions>
54-
<t10c:PageHeader Text="搜索"/>
54+
<t10c:PageHeader x:Name="Header" Text="搜索"/>
5555
<AutoSuggestBox Grid.Row="1" Margin="16,8"
5656
PlaceholderText="请输入关键词" QueryIcon="Find"
5757
TextChanged="{x:Bind VM.AutoSuggestBox_TextChanged}"

XiamiTing/Views/SearchPage.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public sealed partial class SearchPage : Page
2525
public SearchPage()
2626
{
2727
this.InitializeComponent();
28+
if (!Net.LoginHelper.IsLoggedIn) Header.Text += "(未登录,搜索受限)";
2829
}
2930

3031
public void ResultList_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -34,6 +35,7 @@ public void ResultList_SelectionChanged(object sender, SelectionChangedEventArgs
3435

3536
private void VM_SearchingFinished(object sender, Models.SearchResult e)
3637
{
38+
//更新List
3739
SongResults.ItemsSource = e.Songs;
3840
AlbumResults.ItemsSource = e.Albums;
3941
ArtistResults.ItemsSource = e.Artists;

0 commit comments

Comments
 (0)