static NormalResult Set(PatronItem patron, Record record, DateTime lastWriteTime) { XmlDocument dom = new XmlDocument(); try { dom.LoadXml(record.RecordBody.Xml); } catch (Exception ex) { return(new NormalResult { Value = -1, ErrorInfo = $"读者记录装载进入 XMLDOM 时出错:{ex.Message}", ErrorCode = "loadXmlError" }); } // TODO: 如果 XML 记录尺寸太大,可以考虑删除一些无关紧要的元素以后进入 patron.Xml,避免溢出 SQLite 一条记录可以存储的最大尺寸 string pii = DomUtil.GetElementText(dom.DocumentElement, "barcode"); if (string.IsNullOrEmpty(pii)) { pii = "@refID:" + DomUtil.GetElementText(dom.DocumentElement, "refID"); } string cardNumber = DomUtil.GetElementText(dom.DocumentElement, "cardNumber"); cardNumber = cardNumber.ToUpper(); if (string.IsNullOrEmpty(cardNumber) == false) { cardNumber = "," + cardNumber + ","; } patron.PII = pii; patron.RecPath = record.Path; patron.Bindings = cardNumber; patron.Xml = record.RecordBody.Xml; patron.Timestamp = record.RecordBody.Timestamp; patron.LastWriteTime = lastWriteTime; return(new NormalResult()); }
// 第一阶段:获得全部读者库记录,进入本地数据库 // result.Value // -1 出错 // >=0 实际获得的读者记录条数 public static async Task <ReplicationPlan> DownloadAllPatronRecordAsync(CancellationToken token) { LibraryChannel channel = App.CurrentApp.GetChannel(); var old_timeout = channel.Timeout; channel.Timeout = TimeSpan.FromMinutes(5); // 设置 5 分钟。因为读者记录检索需要一定时间 try { ReplicationPlan plan = GetReplicationPlan(channel); if (plan.Value == -1) { return(plan); } int nRedoCount = 0; REDO: if (token.IsCancellationRequested) { return new ReplicationPlan { Value = -1, ErrorInfo = "用户中断" } } ; // 检索全部读者库记录 long lRet = channel.SearchReader(null, // stop, "<all>", "", -1, "__id", "left", "zh", null, // strResultSetName "", // strOutputStyle out string strError); if (lRet == -1) { // 一次重试机会 if (lRet == -1 && (channel.ErrorCode == ErrorCode.RequestCanceled || channel.ErrorCode == ErrorCode.RequestError) && nRedoCount < 2) { nRedoCount++; goto REDO; } return(new ReplicationPlan { Value = -1, ErrorInfo = strError, ErrorCode = channel.ErrorCode.ToString() }); } long hitcount = lRet; // 把超时时间改短一点 channel.Timeout = TimeSpan.FromSeconds(20); DateTime search_time = DateTime.Now; // 获取和存储记录 ResultSetLoader loader = new ResultSetLoader(channel, null, null, $"id,xml,timestamp", "zh"); using (BiblioCacheContext context = new BiblioCacheContext()) { context.Database.EnsureCreated(); // 删除 Patrons 里面的已有记录 context.Patrons.RemoveRange(context.Patrons.ToList()); await context.SaveChangesAsync(token); // loader.Prompt += this.Loader_Prompt; if (hitcount > 0) { int i = 0; foreach (DigitalPlatform.LibraryClient.localhost.Record record in loader) { if (token.IsCancellationRequested) { return new ReplicationPlan { Value = -1, ErrorInfo = "用户中断" } } ; PatronItem item = new PatronItem(); var result = Set(item, record, search_time); if (result.Value == -1) { // TODO: 是否汇总报错信息? continue; } context.Patrons.Add(item); if ((i % 10) == 0) { await context.SaveChangesAsync(token); } i++; } await context.SaveChangesAsync(token); } } return(new ReplicationPlan { Value = (int)hitcount, StartDate = plan.StartDate }); } catch (Exception ex) { return(new ReplicationPlan { Value = -1, ErrorInfo = $"DownloadAllPatronRecord() 出现异常:{ex.Message}" }); } finally { channel.Timeout = old_timeout; App.CurrentApp.ReturnChannel(channel); } }
static NormalResult Set(PatronItem patron, Record record, DateTime lastWriteTime) { XmlDocument dom = new XmlDocument(); try { dom.LoadXml(record.RecordBody.Xml); } catch (Exception ex) { return(new NormalResult { Value = -1, ErrorInfo = $"读者记录装载进入 XMLDOM 时出错:{ex.Message}", ErrorCode = "loadXmlError" }); } // TODO: 如果 XML 记录尺寸太大,可以考虑删除一些无关紧要的元素以后进入 patron.Xml,避免溢出 SQLite 一条记录可以存储的最大尺寸 string pii = DomUtil.GetElementText(dom.DocumentElement, "barcode"); if (string.IsNullOrEmpty(pii)) { pii = "@refID:" + DomUtil.GetElementText(dom.DocumentElement, "refID"); } string cardNumber = DomUtil.GetElementText(dom.DocumentElement, "cardNumber"); cardNumber = cardNumber.ToUpper(); if (string.IsNullOrEmpty(cardNumber) == false) { cardNumber = "," + cardNumber + ","; } // 2020/7/17 if (pii.StartsWith("@") == false) { string libraryCode = DomUtil.GetElementText(dom.DocumentElement, "libraryCode"); var ret = ShelfData.GetOwnerInstitution(libraryCode + "/", out string isil, out string alternative); if (ret == true) { // 应该是 xxx.xxx 形态 if (string.IsNullOrEmpty(isil) == false) { pii = isil + "." + pii; } else if (string.IsNullOrEmpty(alternative) == false) { pii = alternative + "." + pii; } } } patron.PII = pii; patron.RecPath = record.Path; patron.Bindings = cardNumber; patron.Xml = record.RecordBody.Xml; patron.Timestamp = record.RecordBody.Timestamp; patron.LastWriteTime = lastWriteTime; return(new NormalResult()); }
// 第一阶段:获得全部读者库记录,进入本地数据库 // result.Value // -1 出错 // >=0 实际获得的读者记录条数 public static async Task <ReplicationPlan> DownloadAllPatronRecordAsync( Delegate_writeLog writeLog, CancellationToken token) { _inDownloadingPatron++; // 2020/9/26 if (_inDownloadingPatron > 1) { _inDownloadingPatron--; return(new ReplicationPlan { Value = -1, ErrorCode = "running", ErrorInfo = "前一次的“下载全部读者记录到本地缓存”过程还在进行中,本次触发被放弃" }); } writeLog?.Invoke($"开始下载全部读者记录到本地缓存"); LibraryChannel channel = App.CurrentApp.GetChannel(); var old_timeout = channel.Timeout; channel.Timeout = TimeSpan.FromMinutes(5); // 设置 5 分钟。因为读者记录检索需要一定时间 try { ReplicationPlan plan = GetReplicationPlan(channel); writeLog?.Invoke($"GetReplicationPlan() return {plan.ToString()}"); if (plan.Value == -1) { return(plan); } int nRedoCount = 0; REDO: if (token.IsCancellationRequested) { return new ReplicationPlan { Value = -1, ErrorInfo = "用户中断" } } ; // 检索全部读者库记录 long lRet = channel.SearchReader(null, // stop, "<all>", "", -1, "__id", "left", "zh", null, // strResultSetName "", // strOutputStyle out string strError); if (lRet == -1) { writeLog?.Invoke($"SearchReader() 出错, strError={strError}, channel.ErrorCode={channel.ErrorCode}"); // 一次重试机会 if (lRet == -1 && (channel.ErrorCode == ErrorCode.RequestCanceled || channel.ErrorCode == ErrorCode.RequestError) && nRedoCount < 2) { nRedoCount++; goto REDO; } return(new ReplicationPlan { Value = -1, ErrorInfo = strError, ErrorCode = channel.ErrorCode.ToString() }); } long hitcount = lRet; writeLog?.Invoke($"共检索命中读者记录 {hitcount} 条"); // 把超时时间改短一点 channel.Timeout = TimeSpan.FromSeconds(20); DateTime search_time = DateTime.Now; Hashtable pii_table = new Hashtable(); int skip_count = 0; int error_count = 0; // 获取和存储记录 ResultSetLoader loader = new ResultSetLoader(channel, null, null, $"id,xml,timestamp", "zh"); using (BiblioCacheContext context = new BiblioCacheContext()) { context.Database.EnsureCreated(); // 删除 Patrons 里面的已有记录 context.Patrons.RemoveRange(context.Patrons.ToList()); await context.SaveChangesAsync(token); // loader.Prompt += this.Loader_Prompt; if (hitcount > 0) { int i = 0; foreach (DigitalPlatform.LibraryClient.localhost.Record record in loader) { if (token.IsCancellationRequested) { return new ReplicationPlan { Value = -1, ErrorInfo = "用户中断" } } ; PatronItem item = new PatronItem(); // result.Value: // -1 出错 // 0 需要跳过这条读者记录 // 1 成功 var result = Set(item, record, search_time); if (result.Value == -1 || result.Value == 0) { // TODO: 是否汇总报错信息? if (result.Value == -1) { writeLog?.Invoke($"Set() ({item.RecPath}) 出错: {result.ErrorInfo}"); error_count++; } if (result.Value == 0) { skip_count++; } continue; } // if (pii_table.ContainsKey(result.PII)) { string recpath = (string)pii_table[result.PII]; writeLog?.Invoke($"发现读者记录 {item.RecPath} 的 PII '{result.PII}' 和 {recpath} 的 PII 重复了。跳过它"); continue; } pii_table[result.PII] = item.RecPath; // TODO: PII 应该是包含 OI 的严格形态 context.Patrons.Add(item); if ((i % 10) == 0) { await context.SaveChangesAsync(token); } i++; } await context.SaveChangesAsync(token); } } writeLog?.Invoke($"plan.StartDate='{plan.StartDate}'。skip_count={skip_count}, error_count={error_count}。返回"); return(new ReplicationPlan { Value = (int)hitcount, StartDate = plan.StartDate }); } catch (Exception ex) { // 2020/9/26 writeLog?.Invoke($"DownloadAllPatronRecord() 出现异常:{ExceptionUtil.GetDebugText(ex)}"); return(new ReplicationPlan { Value = -1, ErrorInfo = $"DownloadAllPatronRecord() 出现异常:{ex.Message}" }); } finally { channel.Timeout = old_timeout; App.CurrentApp.ReturnChannel(channel); writeLog?.Invoke($"结束下载全部读者记录到本地缓存"); _inDownloadingPatron--; } }