11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4- using HtmlAgilityPack ;
5- using Novell . Directory . Ldap ;
4+ using System . Net . Http ;
65using System . Threading . Tasks ;
76using IStudentsInfo ;
7+ using Newtonsoft . Json ;
8+ using Novell . Directory . Ldap ;
9+ using System . Threading ;
810
911namespace StudentsInfo
1012{
13+ public class InterruptibleLazy < T >
14+ {
15+ private Func < T > _valueFactory ;
16+ private readonly object _lockObj = new object ( ) ;
17+ private T _value ;
18+
19+ public InterruptibleLazy ( Func < T > valueFactory )
20+ {
21+ _valueFactory = valueFactory ;
22+ }
23+
24+ public T Value
25+ {
26+ get
27+ {
28+ if ( _valueFactory == null ) return _value ;
29+
30+ lock ( _lockObj )
31+ {
32+ if ( _valueFactory == null ) return _value ;
33+
34+ _value = _valueFactory ( ) ;
35+ Interlocked . MemoryBarrier ( ) ;
36+ _valueFactory = null ;
37+ }
38+
39+ return _value ;
40+ }
41+ }
42+ }
43+
1144 public class StudentsInformationProvider : IStudentsInformationProvider
1245 {
13- private readonly Lazy < Dictionary < string , List < string > > > _lazyProgramsGroups ;
46+ private readonly InterruptibleLazy < Task < Dictionary < string , List < string > > > > _programsGroups ;
1447 private readonly string _ldapHost = "ad.pu.ru" ;
1548 private readonly int _ldapPort = 389 ;
1649 private readonly string _searchBase = "DC=ad,DC=pu,DC=ru" ;
50+ private readonly HttpClient _httpClient ;
1751
1852 private string _username ;
1953 private string _password ;
20-
54+
2155 public async Task < List < GroupModel > > GetGroups ( string programName )
2256 {
23- return await Task . Run ( ( ) =>
24- {
25- return _lazyProgramsGroups . Value . ContainsKey ( programName )
26- ? _lazyProgramsGroups . Value [ programName ]
27- . Aggregate ( ( current , next ) => current + "," + next )
28- . Split ( ',' )
29- . Select ( group => new GroupModel { GroupName = group . Trim ( ) } )
30- . ToList ( )
31- : new List < GroupModel > ( ) ;
32- } ) ;
57+ var programsGroups = await _programsGroups . Value ;
58+ if ( ! programsGroups . TryGetValue ( programName , out var groups ) )
59+ return new List < GroupModel > ( ) ;
60+
61+ return groups . Select ( group => new GroupModel { GroupName = group } ) . ToList ( ) ;
3362 }
34-
63+
3564 public List < StudentModel > GetStudentInformation ( string groupName )
3665 {
37- var searchFilter = $ "(&(objectClass=person)(memberOf=CN=АкадемГруппа_{ groupName } ,OU=АкадемГруппа,OU=Группы,DC=ad,DC=pu,DC=ru))";
66+ var searchFilter =
67+ $ "(&(objectClass=person)(memberOf=CN=АкадемГруппа_{ groupName } ,OU=АкадемГруппа,OU=Группы,DC=ad,DC=pu,DC=ru))";
3868 var studentsList = new List < StudentModel > ( ) ;
3969 LdapConnection connection = null ;
40-
70+
4171 try
4272 {
4373 connection = new LdapConnection ( ) ;
@@ -48,7 +78,7 @@ public List<StudentModel> GetStudentInformation(string groupName)
4878 {
4979 return studentsList ;
5080 }
51-
81+
5282 var results = connection . Search (
5383 _searchBase ,
5484 LdapConnection . SCOPE_SUB ,
@@ -62,7 +92,7 @@ public List<StudentModel> GetStudentInformation(string groupName)
6292 var entry = results . next ( ) ;
6393 var cn = entry . getAttribute ( "cn" ) ? . StringValue ;
6494 var displayName = entry . getAttribute ( "displayName" ) ? . StringValue ;
65-
95+
6696 if ( cn != null && displayName != null )
6797 {
6898 string [ ] splitNames = displayName . Split ( ' ' ) ;
@@ -81,11 +111,11 @@ public List<StudentModel> GetStudentInformation(string groupName)
81111 {
82112 return studentsList ;
83113 }
84- catch ( LdapException ldapEx )
114+ catch ( LdapException )
85115 {
86116 return studentsList ;
87117 }
88- catch ( Exception ex )
118+ catch ( Exception )
89119 {
90120 return studentsList ;
91121 }
@@ -95,12 +125,11 @@ public List<StudentModel> GetStudentInformation(string groupName)
95125 {
96126 if ( connection != null && connection . Connected )
97127 {
98- SafeDisconnect ( connection ) ;
128+ SafeDisconnect ( connection ) ;
99129 }
100130 }
101- catch ( Exception ex )
131+ catch ( Exception )
102132 {
103- Console . WriteLine ( $ "Error during disconnect: { ex . Message } ") ;
104133 }
105134 }
106135
@@ -116,71 +145,69 @@ private void SafeDisconnect(LdapConnection connection)
116145 catch ( PlatformNotSupportedException )
117146 {
118147 }
119- catch ( Exception ex )
148+ catch ( Exception )
120149 {
121- Console . WriteLine ( $ "SafeDisconnect error: { ex . Message } ") ;
122150 }
123151 }
124-
125- public List < ProgramModel > GetProgramNames ( )
152+
153+ public async Task < List < ProgramModel > > GetProgramNames ( )
126154 {
127- return _lazyProgramsGroups . Value . Keys
155+ var programGroups = await _programsGroups . Value ;
156+ return programGroups . Keys
128157 . Select ( key => new ProgramModel { ProgramName = key } )
129158 . ToList ( ) ;
130159 }
131160
132- public StudentsInformationProvider ( string username , string password , string ldapHost , int ldapPort , string searchBase )
161+ public StudentsInformationProvider ( string username , string password , string ldapHost , int ldapPort ,
162+ string searchBase )
133163 {
134- this . _username = username ;
135- this . _password = password ;
136- this . _ldapHost = ldapHost ;
137- this . _ldapPort = ldapPort ;
138- this . _searchBase = searchBase ;
164+ _username = username ;
165+ _password = password ;
166+ _ldapHost = ldapHost ;
167+ _ldapPort = ldapPort ;
168+ _searchBase = searchBase ;
169+ _httpClient = new HttpClient ( ) ;
139170
140- _lazyProgramsGroups = new Lazy < Dictionary < string , List < string > > > ( ( ) =>
171+ _programsGroups = new InterruptibleLazy < Task < Dictionary < string , List < string > > > > ( async ( ) =>
141172 {
142173 var programsGroups = new Dictionary < string , List < string > > ( ) ;
143-
174+
144175 try
145176 {
146- const string url = "https://timetable.spbu.ru/MATH?lang=ru" ;
147- var web = new HtmlWeb ( ) ;
148-
149- web . PreRequest = request =>
177+ var programsResponse =
178+ await _httpClient . GetAsync (
179+ "https://timetable.spbu.ru/api/v1/study/divisions/MATH/programs/levels" ) ;
180+ if ( programsResponse . IsSuccessStatusCode )
150181 {
151- request . Headers . Add ( "Accept-Language" , "ru" ) ;
152- return true ;
153- } ;
182+ var content = await programsResponse . Content . ReadAsStringAsync ( ) ;
183+ var studyLevels = JsonConvert . DeserializeObject < List < StudyLevel > > ( content ) ;
154184
155- var doc = web . Load ( url ) ;
156- var programNodes = doc . DocumentNode . SelectNodes ( "//li[contains(@class, 'common-list-item row')]" ) ;
157-
158- foreach ( var programNode in programNodes )
159- {
160- var programNameNode = programNode . SelectSingleNode ( ".//div[contains(@class, 'col-sm-5')]" ) ;
161- var programName = programNameNode ? . InnerText . Trim ( ) ;
162-
163- var titleNodes = programNode . SelectNodes ( ".//div[contains(@class, 'col-sm-1')]" ) ;
164-
165- if ( titleNodes != null && programName != null )
185+ foreach ( var level in studyLevels )
166186 {
167- var titles = new List < string > ( ) ;
168- foreach ( var titleNode in titleNodes )
187+ foreach ( var programCombination in level . StudyProgramCombinations )
169188 {
170- var title = titleNode . SelectSingleNode ( ".//a" ) ? . Attributes [ "title" ] ? . Value ;
171- if ( title != null )
189+ foreach ( var admissionYear in programCombination . AdmissionYears )
172190 {
173- titles . Add ( title ) ;
174- }
175- }
191+ var groupsResponse = await _httpClient . GetAsync (
192+ $ "https://timetable.spbu.ru/api/v1/programs/{ admissionYear . StudyProgramId } /groups") ;
193+ if ( groupsResponse . IsSuccessStatusCode )
194+ {
195+ var groupsContent = await groupsResponse . Content . ReadAsStringAsync ( ) ;
196+ var programGroups = JsonConvert . DeserializeObject < ProgramGroups > ( groupsContent ) ;
176197
177- if ( programsGroups . ContainsKey ( programName ) )
178- {
179- programsGroups [ programName ] . AddRange ( titles ) ;
180- }
181- else
182- {
183- programsGroups [ programName ] = titles ;
198+ var programName = programCombination . Name ;
199+ var groups = programGroups . Groups . Select ( g => g . StudentGroupName ) . ToList ( ) ;
200+
201+ if ( programsGroups . ContainsKey ( programName ) )
202+ {
203+ programsGroups [ programName ] . AddRange ( groups ) ;
204+ }
205+ else
206+ {
207+ programsGroups [ programName ] = groups ;
208+ }
209+ }
210+ }
184211 }
185212 }
186213 }
@@ -193,5 +220,32 @@ public StudentsInformationProvider(string username, string password, string ldap
193220 return programsGroups ;
194221 } ) ;
195222 }
223+
224+ private class StudyLevel
225+ {
226+ public string StudyLevelName { get ; set ; }
227+ public List < StudyProgramCombination > StudyProgramCombinations { get ; set ; }
228+ }
229+
230+ private class StudyProgramCombination
231+ {
232+ public string Name { get ; set ; }
233+ public List < AdmissionYear > AdmissionYears { get ; set ; }
234+ }
235+
236+ private class AdmissionYear
237+ {
238+ public int StudyProgramId { get ; set ; }
239+ }
240+
241+ private class ProgramGroups
242+ {
243+ public List < GroupInfo > Groups { get ; set ; }
244+ }
245+
246+ private class GroupInfo
247+ {
248+ public string StudentGroupName { get ; set ; }
249+ }
196250 }
197- }
251+ }
0 commit comments