// 执行API中的"move"操作 // 1) 操作成功后, NewRecord中有实际保存的新记录,NewTimeStamp为新的时间戳 // 2) 如果返回TimeStampMismatch错,则OldRecord中有库中发生变化后的“原记录”,OldTimeStamp是其时间戳 // return: // -1 出错 // 0 成功 int DoOperMove( SessionInfo sessioninfo, // string strUserID, RmsChannel channel, EntityInfo info, ref XmlDocument domOperLog, ref List<EntityInfo> ErrorInfos) { EntityInfo error = null; bool bExist = true; // info.RecPath所指的记录是否存在? int nRet = 0; long lRet = 0; string strError = ""; // 检查路径 if (info.OldRecPath == info.NewRecPath) { strError = "当action为\"move\"时,info.NewRecordPath路径 '" + info.NewRecPath + "' 和info.OldRecPath '" + info.OldRecPath + "' 必须不相同"; goto ERROR1; } // 检查即将覆盖的目标位置是不是有记录,如果有,则不允许进行move操作。 // 如果要进行带覆盖目标位置记录功能的move操作,前端可以先执行一个delete操作,然后再执行move操作。 // 这样规定,是为了避免过于复杂的判断逻辑,也便于前端操作者清楚操作的后果。 // 因为如果允许move带有覆盖目标记录功能,则被覆盖的记录的预删除操作,等于进行了一次事项注销,但这个效用不明显,对前端操作人员准确判断事态并对后果负责(而且可能这种注销需要额外的操作权限),不利 bool bAppendStyle = false; // 目标路径是否为追加形态? string strTargetRecId = ResPath.GetRecordId(info.NewRecPath); if (strTargetRecId == "?" || String.IsNullOrEmpty(strTargetRecId) == true) bAppendStyle = true; string strOutputPath = ""; string strMetaData = ""; if (bAppendStyle == false) { string strExistTargetXml = ""; byte[] exist_target_timestamp = null; // 获取覆盖目标位置的现有记录 lRet = channel.GetRes(info.NewRecPath, out strExistTargetXml, out strMetaData, out exist_target_timestamp, out strOutputPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { // 如果记录不存在, 说明不会造成覆盖态势 /* strExistSourceXml = "<root />"; exist_source_timestamp = null; strOutputPath = info.NewRecPath; * */ } else { error = new EntityInfo(info); error.ErrorInfo = "move操作发生错误, 发生在读入即将覆盖的目标位置 '" + info.NewRecPath + "' 原有记录阶段:" + strError; error.ErrorCode = channel.OriginErrorCode; ErrorInfos.Add(error); return -1; } } else { // 如果记录存在,则目前不允许这样的操作 strError = "移动(move)操作被拒绝。因为在即将覆盖的目标位置 '" + info.NewRecPath + "' 已经存在" + this.ItemName + "记录。请先删除(delete)这条记录,再进行移动(move)操作"; goto ERROR1; } } string strExistSourceXml = ""; byte[] exist_source_timestamp = null; // 先读出数据库中源位置的已有记录 // REDOLOAD: lRet = channel.GetRes(info.OldRecPath, out strExistSourceXml, out strMetaData, out exist_source_timestamp, out strOutputPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { /* // 如果记录不存在, 则构造一条空的记录 bExist = false; strExistSourceXml = "<root />"; exist_source_timestamp = null; strOutputPath = info.NewRecPath; * */ // 这种情况如果放宽,会有严重的副作用,所以不让放宽 strError = "move操作的源记录 '" + info.OldRecPath + "' 在数据库中不存在,所以无法进行移动操作。"; goto ERROR1; } else { error = new EntityInfo(info); error.ErrorInfo = "移动操作发生错误, 在读入库中原有源记录(路径在info.OldRecPath) '" + info.OldRecPath + "' 阶段:" + strError; error.ErrorCode = channel.OriginErrorCode; ErrorInfos.Add(error); return -1; } } // 把两个记录装入DOM XmlDocument domSourceExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { domSourceExist.LoadXml(strExistSourceXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(info.NewRecord); } catch (Exception ex) { strError = "info.NewRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 观察时间戳是否发生变化 nRet = ByteArray.Compare(info.OldTimestamp, exist_source_timestamp); if (nRet != 0) { // 时间戳不相等了 // 需要把info.OldRecord和strExistXml进行比较,看看和事项有关的元素(要害元素)值是否发生了变化。 // 如果这些要害元素并未发生变化,就继续进行合并、覆盖保存操作 XmlDocument domOld = new XmlDocument(); try { domOld.LoadXml(info.OldRecord); } catch (Exception ex) { strError = "info.OldRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 比较两个记录, 看看和事项有关的要害字段是否发生了变化 // return: // 0 没有变化 // 1 有变化 nRet = IsItemInfoChanged(domOld, domSourceExist); if (nRet == 1) { error = new EntityInfo(info); // 错误信息中, 返回了修改过的原记录和新时间戳 error.OldRecord = strExistSourceXml; error.OldTimestamp = exist_source_timestamp; if (bExist == false) error.ErrorInfo = "移动操作发生错误: 数据库中的原记录 (路径为'" + info.OldRecPath + "') 已被删除。"; else error.ErrorInfo = "移动操作发生错误: 数据库中的原记录 (路径为'" + info.OldRecPath + "') 已发生过修改"; error.ErrorCode = ErrorCodeValue.TimestampMismatch; ErrorInfos.Add(error); return -1; } // exist_source_timestamp此时已经反映了库中被修改后的记录的时间戳 } // 2011/2/11 nRet = CanChange( sessioninfo, "move", domSourceExist, domNew, out strError); if (nRet == -1) goto ERROR1; if (nRet == 0) { error = new EntityInfo(info); error.ErrorInfo = strError; error.ErrorCode = ErrorCodeValue.AccessDenied; ErrorInfos.Add(error); return -1; } // 2010/4/8 // nRet = this.App.SetOperation( ref domNew, "moved", sessioninfo.UserID, // strUserID, "", out strError); if (nRet == -1) goto ERROR1; string strWarning = ""; // 合并新旧记录 // return: // -1 出错 // 0 正确 // 1 有部分修改没有兑现。说明在strError中 string strNewXml = ""; nRet = MergeTwoItemXml( sessioninfo, domSourceExist, domNew, out strNewXml, out strError); if (nRet == -1) goto ERROR1; if (nRet == 1) strWarning = strError; // 移动记录 byte[] output_timestamp = null; // TODO: Copy后还要写一次?因为Copy并不写入新记录。 // 其实Copy的意义在于带走资源。否则还不如用Save+Delete lRet = channel.DoCopyRecord(info.OldRecPath, info.NewRecPath, true, // bDeleteSourceRecord out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "DoCopyRecord() error :" + strError; goto ERROR1; } // Debug.Assert(strOutputPath == info.NewRecPath); string strTargetPath = strOutputPath; lRet = channel.DoSaveTextRes(strTargetPath, strNewXml, false, // include preamble? "content", output_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "移动操作中," + this.ItemName + "记录 '" + info.OldRecPath + "' 已经被成功移动到 '" + strTargetPath + "' ,但在写入新内容时发生错误: " + strError; if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { // 不进行反复处理。 // 因为源已经移动,情况很复杂 } // 仅仅写入错误日志即可。没有Undo this.App.WriteErrorLog(strError); error = new EntityInfo(info); error.ErrorInfo = "移动操作发生错误:" + strError; error.ErrorCode = channel.OriginErrorCode; ErrorInfos.Add(error); return -1; } else // 成功 { info.NewRecPath = strOutputPath; // 兑现保存的位置,因为可能有追加形式的路径 DomUtil.SetElementText(domOperLog.DocumentElement, "action", "move"); // 新记录 XmlNode node = DomUtil.SetElementText(domOperLog.DocumentElement, "record", strNewXml); DomUtil.SetAttr(node, "recPath", info.NewRecPath); // 旧记录 node = DomUtil.SetElementText(domOperLog.DocumentElement, "oldRecord", strExistSourceXml); DomUtil.SetAttr(node, "recPath", info.OldRecPath); // 保存成功,需要返回信息元素。因为需要返回新的时间戳 error = new EntityInfo(info); error.NewTimestamp = output_timestamp; error.NewRecord = strNewXml; error.ErrorInfo = "移动操作成功。NewRecPath中返回了实际保存的路径, NewTimeStamp中返回了新的时间戳,NewRecord中返回了实际保存的新记录(可能和提交的源记录稍有差异)。"; if (string.IsNullOrEmpty(strWarning) == false) { error.ErrorInfo = "移动操作成功。但" + strWarning; error.ErrorCode = ErrorCodeValue.PartialDenied; } else error.ErrorCode = ErrorCodeValue.NoError; ErrorInfos.Add(error); } return 0; ERROR1: error = new EntityInfo(info); error.ErrorInfo = strError; error.ErrorCode = ErrorCodeValue.CommonError; ErrorInfos.Add(error); return -1; }
// 保存记录 // parameters: // strRecordPath 记录路径。如果==null,表示直接用textBox_recPath中当前的内容作为路径 public void SaveRecord(string strRecordPath) { if (strRecordPath != null) textBox_recPath.Text = strRecordPath; if (textBox_recPath.Text == "") { MessageBox.Show(this, "路径不能为空"); return; } ResPath respath = new ResPath(textBox_recPath.Text); Uri uri = null; try { uri = new Uri(respath.Url); } catch (Exception ex) { MessageBox.Show(this, "路径错误: " + ex.Message); return; } // 保存到文件 if (uri.IsFile) { MessageBox.Show(this, "暂时不支持保存到文件"); return; } string strError; string strXml = ""; bool bHasUploadedFile = false; int nRet = GetXmlRecord(out strXml, out bHasUploadedFile, out strError); if (nRet == -1) { MessageBox.Show(this, strError); return; } byte [] baOutputTimeStamp = null; string strOutputPath = ""; long lRet = 0; int nUploadCount = 0; // 使用Channel RmsChannel channelSave = channel; channel = Channels.GetChannel(respath.Url); Debug.Assert(channel != null, "Channels.GetChannel 异常"); try { stop.OnStop += new StopEventHandler(this.DoStop); stop.Initial("正在保存记录 " + respath.FullPath); stop.BeginLoop(); EnableControlsInLoading(true); //string strTemp = ByteArray.GetHexTimeStampString(this.TimeStamp); if (String.IsNullOrEmpty(this.strDatabaseOriginPath) == false && bHasUploadedFile == true && respath.FullPath != this.strDatabaseOriginPath) { ResPath respath_old = new ResPath(this.strDatabaseOriginPath); if (respath_old.Url != respath.Url) { MessageBox.Show(this, "目前暂不支持跨服务器情况下的资源复制。本记录中原有的已上载资源,在另存到目标库的时丢失(为空),请注意保存完后手动上载。"); goto SKIPCOPYRECORD; } // 复制记录 // return: // -1 出错。错误信息在strError中 // 0或者其他 成功 nRet = channel.DoCopyRecord(respath_old.Path, respath.Path, false, // bool bDeleteOriginRecord, out baOutputTimeStamp, out strOutputPath, out strError); if (nRet == -1) { MessageBox.Show(this, "复制资源时发生错误: " + strError); } else { // 为继续保存最新XML记录作准备 respath.Path = strOutputPath; // ?形式路径其实已经确定 this.TimeStamp = baOutputTimeStamp; } } SKIPCOPYRECORD: lRet = channel.DoSaveTextRes(respath.Path, strXml, false, // bInlucdePreamble "", // style this.TimeStamp, out baOutputTimeStamp, out strOutputPath, out strError); EnableControlsInLoading(false); stop.EndLoop(); stop.OnStop -= new StopEventHandler(this.DoStop); stop.Initial(""); if (lRet == -1) { MessageBox.Show(this, "保存记录失败,原因: "+strError); return; } // this.TimeStamp = baOutputTimeStamp; respath.Path = strOutputPath; textBox_recPath.Text = respath.FullPath; //// this.strDatabaseOriginPath = respath.Url + "?" + strOutputPath; // 保存从数据库中来的原始path stop.OnStop += new StopEventHandler(this.DoStop); stop.Initial("正在保存资源 " + respath.FullPath); stop.BeginLoop(); EnableControlsInLoading(true); Debug.Assert(channel != null, ""); // 保存对象资源,循环对象列表就可以了 nUploadCount = this.listView_resFiles.DoUpload( respath.Path, channel, stop, out strError); EnableControlsInLoading(false); stop.EndLoop(); stop.OnStop -= new StopEventHandler(this.DoStop); stop.Initial(""); } finally { channel = channelSave; } if (nUploadCount == -1) { MessageBox.Show(this, "XML记录保存成功, 但保存资源失败,原因: "+strError); return; } if (nUploadCount > 0) { // 使用Channel channelSave = channel; channel = Channels.GetChannel(respath.Url); Debug.Assert(channel != null, "Channels.GetChannel 异常"); // 需要重新获得时间戳 string strStyle = "timestamp,metadata"; // withresmetadata string strMetaData = ""; string strContent = ""; try { lRet = channel.GetRes(respath.Path, strStyle, out strContent, out strMetaData, out baOutputTimeStamp, out strOutputPath, out strError); if (lRet == -1) { MessageBox.Show(this, "重新获得时间戳 '" + respath.FullPath + "' 失败。原因 : " + strError); return; } } finally { channel = channelSave; } this.TimeStamp = baOutputTimeStamp; // 设置时间戳很重要。即便xml不合法,也应设置好时间戳,否则窗口无法进行正常删除。 this.m_strMetaData = strMetaData; // 记忆XML记录的元数据 } this.Changed = false; MessageBox.Show(this, "保存记录 '" + respath.FullPath + "' 成功。"); }
// 复制属于同一书目记录的全部实体记录 // parameters: // strAction copy / move // return: // -2 目标实体库不存在,无法进行复制或者删除 // -1 error // >=0 实际复制或者移动的实体记录数 public int CopyBiblioChildItems(RmsChannel channel, string strAction, List<DeleteEntityInfo> entityinfos, string strTargetBiblioRecPath, XmlDocument domOperLog, out string strError) { strError = ""; if (entityinfos == null || entityinfos.Count == 0) return 0; int nOperCount = 0; XmlNode root = null; if (domOperLog != null) { root = domOperLog.CreateElement(strAction == "copy" ? "copy" + this.ItemNameInternal + "Records" : "move" + this.ItemNameInternal + "Records"); domOperLog.DocumentElement.AppendChild(root); } // 获得目标书目库下属的实体库名 string strTargetItemDbName = ""; string strTargetBiblioDbName = ResPath.GetDbName(strTargetBiblioRecPath); // return: // -1 出错 // 0 没有找到 // 1 找到 int nRet = this.GetItemDbName(strTargetBiblioDbName, out strTargetItemDbName, out strError); if (nRet == 0 || string.IsNullOrEmpty(strTargetItemDbName) == true) { return -2; // 目标实体库不存在 } string strParentID = ResPath.GetRecordId(strTargetBiblioRecPath); if (string.IsNullOrEmpty(strParentID) == true) { strError = "目标书目记录路径 '" + strTargetBiblioRecPath + "' 不正确,无法获得记录号"; return -1; } List<string> newrecordpaths = new List<string>(); List<string> oldrecordpaths = new List<string>(); for (int i = 0; i < entityinfos.Count; i++) { DeleteEntityInfo info = entityinfos[i]; byte[] output_timestamp = null; string strOutputRecPath = ""; // this.EntityLocks.LockForWrite(info.ItemBarcode); try { XmlDocument dom = new XmlDocument(); try { dom.LoadXml(info.OldRecord); } catch (Exception ex) { strError = "记录 '" + info.RecPath + "' 装入XMLDOM发生错误: " + ex.Message; goto ERROR1; } DomUtil.SetElementText(dom.DocumentElement, "parent", strParentID); // 复制的情况 if (strAction == "copy") { // 避免refID重复 DomUtil.SetElementText(dom.DocumentElement, "refID", null); } long lRet = channel.DoCopyRecord(info.RecPath, strTargetItemDbName + "/?", strAction == "move" ? true : false, // bDeleteSourceRecord out output_timestamp, out strOutputRecPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; strError = "复制" + this.ItemName + "记录 '" + info.RecPath + "' 时发生错误: " + strError; goto ERROR1; } // 2011/5/24 // 修改xml记录。<parent>元素发生了变化 byte[] baOutputTimestamp = null; string strOutputRecPath1 = ""; lRet = channel.DoSaveTextRes(strOutputRecPath, dom.OuterXml, false, "content", // ,ignorechecktimestamp output_timestamp, out baOutputTimestamp, out strOutputRecPath1, out strError); if (lRet == -1) goto ERROR1; oldrecordpaths.Add(info.RecPath); newrecordpaths.Add(strOutputRecPath); } finally { // this.EntityLocks.UnlockForWrite(info.ItemBarcode); } // 增补到日志DOM中 if (domOperLog != null) { Debug.Assert(root != null, ""); XmlNode node = domOperLog.CreateElement("record"); root.AppendChild(node); DomUtil.SetAttr(node, "recPath", info.RecPath); DomUtil.SetAttr(node, "targetRecPath", strOutputRecPath); } nOperCount++; } return nOperCount; ERROR1: // Undo已经进行过的操作 if (strAction == "copy") { string strWarning = ""; foreach (string strRecPath in newrecordpaths) { string strTempError = ""; byte[] timestamp = null; byte[] output_timestamp = null; REDO_DELETE: long lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strTempError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (timestamp == null) { timestamp = output_timestamp; goto REDO_DELETE; } } strWarning += strTempError + ";"; } } if (string.IsNullOrEmpty(strWarning) == false) strError = strError + "。在Undo过程中,又遇到出错: " + strWarning; } else if (strAction == "move") { string strWarning = ""; for (int i = 0; i < newrecordpaths.Count; i++) { byte[] output_timestamp = null; string strOutputRecPath = ""; string strTempError = ""; long lRet = channel.DoCopyRecord(newrecordpaths[i], oldrecordpaths[i], true, // bDeleteSourceRecord out output_timestamp, out strOutputRecPath, out strTempError); if (lRet == -1) { strWarning += strTempError + ";"; } } if (string.IsNullOrEmpty(strWarning) == false) strError = strError + "。在Undo过程中,又遇到出错: " + strWarning; } return -1; }
// 移动或者复制书目记录 // strExistingXml和请求中传来的old xml的时间戳比较,在本函数外、调用前进行 // parameters: // strAction 动作。为"onlycopybiblio" "onlymovebiblio"之一。增加 copy / move // strNewBiblio 需要在目标记录中更新的内容。如果 == null,表示不特意更新 // strMergeStyle 如何合并两条记录的元数据部分? reserve_source / reserve_target。 空表示 reserve_source int DoBiblioOperMove( string strAction, SessionInfo sessioninfo, RmsChannel channel, string strOldRecPath, string strExistingSourceXml, // byte[] baExistingSourceTimestamp, // 请求中提交过来的时间戳 string strNewRecPath, string strNewBiblio, // 已经经过Merge预处理的新记录XML string strMergeStyle, out string strOutputTargetXml, out byte[] baOutputTimestamp, out string strOutputRecPath, out string strError) { strError = ""; long lRet = 0; baOutputTimestamp = null; strOutputRecPath = ""; strOutputTargetXml = ""; // 最后保存成功的记录 // 检查路径 if (strOldRecPath == strNewRecPath) { strError = "当action为\"" + strAction + "\"时,strNewRecordPath路径 '" + strNewRecPath + "' 和strOldRecPath '" + strOldRecPath + "' 必须不相同"; goto ERROR1; } if (String.IsNullOrEmpty(strNewRecPath) == true) { strError = "DoBiblioOperMove() strNewRecPath参数值不能为空"; goto ERROR1; } // 检查即将覆盖的目标位置是不是有记录,如果有,则不允许进行move操作。 bool bAppendStyle = false; // 目标路径是否为追加形态? string strTargetRecId = ResPath.GetRecordId(strNewRecPath); string strExistTargetXml = ""; if (strTargetRecId == "?" || String.IsNullOrEmpty(strTargetRecId) == true) { // 2009/11/1 if (String.IsNullOrEmpty(strTargetRecId) == true) strNewRecPath += "/?"; bAppendStyle = true; } string strOutputPath = ""; string strMetaData = ""; if (bAppendStyle == false) { byte[] exist_target_timestamp = null; // 获取覆盖目标位置的现有记录 lRet = channel.GetRes(strNewRecPath, out strExistTargetXml, out strMetaData, out exist_target_timestamp, out strOutputPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { // 如果记录不存在, 说明不会造成覆盖态势 /* strExistSourceXml = "<root />"; exist_source_timestamp = null; strOutputPath = info.NewRecPath; * */ } else { strError = "移动操作发生错误, 在读入即将覆盖的目标位置 '" + strNewRecPath + "' 原有记录阶段:" + strError; goto ERROR1; } } else { #if NO // 如果记录存在,则目前不允许这样的操作 strError = "移动(move)操作被拒绝。因为在即将覆盖的目标位置 '" + strNewRecPath + "' 已经存在书目记录。请先删除(delete)这条记录,再进行移动(move)操作"; goto ERROR1; #endif } } /* // 把两个记录装入DOM XmlDocument domSourceExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { domSourceExist.LoadXml(strExistingSourceXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(strNewBiblio); } catch (Exception ex) { strError = "strNewBiblio装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } * */ // 只有order权限的情况 if (StringUtil.IsInList("setbiblioinfo", sessioninfo.RightsOrigin) == false && StringUtil.IsInList("order", sessioninfo.RightsOrigin) == true) { if (strAction == "onlymovebiblio" || strAction == "move") { string strSourceDbName = ResPath.GetDbName(strOldRecPath); // 源头书目库为 非工作库 情况 if (IsOrderWorkBiblioDb(strSourceDbName) == false) { // 非工作库不能删除记录 if (IsOrderWorkBiblioDb(strSourceDbName) == false) { // 非工作库。要求原来记录不存在 strError = "当前帐户只有order权限而没有setbiblioinfo权限,不能用" + strAction + "功能删除源书目记录 '" + strOldRecPath + "'"; goto ERROR1; } } } } // 移动记录 byte[] output_timestamp = null; string strIdChangeList = ""; // TODO: Copy后还要写一次?因为Copy并不写入新记录。 // 其实Copy的意义在于带走资源。否则还不如用Save+Delete lRet = channel.DoCopyRecord(strOldRecPath, strNewRecPath, strAction == "onlymovebiblio" || strAction == "move" ? true : false, // bDeleteSourceRecord strMergeStyle, out strIdChangeList, out output_timestamp, out strOutputRecPath, out strError); if (lRet == -1) { strError = "DoCopyRecord() error :" + strError; goto ERROR1; } // TODO: 兑现对 856 字段的合并,和来自源的 856 字段的 $u 修改 if (String.IsNullOrEmpty(strNewBiblio) == false) { this.BiblioLocks.LockForWrite(strOutputRecPath); try { // TODO: 如果新的、已存在的xml没有不同,或者新的xml为空,则这步保存可以省略 string strOutputBiblioRecPath = ""; lRet = channel.DoSaveTextRes(strOutputRecPath, strNewBiblio, false, "content", // ,ignorechecktimestamp output_timestamp, out baOutputTimestamp, out strOutputBiblioRecPath, out strError); if (lRet == -1) goto ERROR1; } finally { this.BiblioLocks.UnlockForWrite(strOutputRecPath); } } { // TODO: 是否和前面一起锁定? byte[] exist_target_timestamp = null; // 获取最后的记录 lRet = channel.GetRes(strOutputRecPath, out strOutputTargetXml, out strMetaData, out exist_target_timestamp, out strOutputPath, out strError); } return 0; ERROR1: return -1; }
// 复制属于同一书目记录的全部实体记录 // parameters: // strAction copy / move // return: // -1 error // >=0 实际复制或者移动的实体记录数 public int CopyBiblioChildRecords(RmsChannel channel, string strAction, List<DeleteEntityInfo> entityinfos, List<string> target_recpaths, string strTargetBiblioRecPath, List<string> newbarcodes, out string strError) { strError = ""; if (entityinfos == null || entityinfos.Count == 0) return 0; if (entityinfos.Count != target_recpaths.Count) { strError = "entityinfos.Count (" + entityinfos.Count.ToString() + ") != target_recpaths.Count (" + target_recpaths.Count .ToString()+ ")"; return -1; } int nOperCount = 0; string strParentID = ResPath.GetRecordId(strTargetBiblioRecPath); if (string.IsNullOrEmpty(strParentID) == true) { strError = "目标书目记录路径 '" + strTargetBiblioRecPath + "' 不正确,无法获得记录号"; return -1; } List<string> newrecordpaths = new List<string>(); List<string> oldrecordpaths = new List<string>(); List<string> parentids = new List<string>(); List<string> oldrecords = new List<string>(); for (int i = 0; i < entityinfos.Count; i++) { DeleteEntityInfo info = entityinfos[i]; string strTargetRecPath = target_recpaths[i]; string strNewBarcode = newbarcodes[i]; byte[] output_timestamp = null; string strOutputRecPath = ""; this.EntityLocks.LockForWrite(info.ItemBarcode); try { XmlDocument dom = new XmlDocument(); try { dom.LoadXml(info.OldRecord); } catch (Exception ex) { strError = "记录 '" + info.RecPath + "' 装入XMLDOM发生错误: " + ex.Message; goto ERROR1; } DomUtil.SetElementText(dom.DocumentElement, "parent", strParentID); // 复制的情况,要避免出现操作后的条码号重复现象 if (strAction == "copy") { // 修改册条码号,避免发生条码号重复 string strOldItemBarcode = DomUtil.GetElementText(dom.DocumentElement, "barcode"); if (String.IsNullOrEmpty(strOldItemBarcode) == false) { // 2014/1/5 if (string.IsNullOrEmpty(strNewBarcode) == true) strNewBarcode = "temp_" + strOldItemBarcode; DomUtil.SetElementText(dom.DocumentElement, "barcode", strNewBarcode); } // 2014/1/5 DomUtil.SetElementText(dom.DocumentElement, "refID", null); // 把借者清除 // (源实体记录中如果有借阅信息,在普通界面上是无法删除此记录的。只能用出纳窗正规进行归还,然后才能删除) { DomUtil.SetElementText(dom.DocumentElement, "borrower", null); DomUtil.SetElementText(dom.DocumentElement, "borrowPeriod", null); DomUtil.SetElementText(dom.DocumentElement, "borrowDate", null); } } // TODO: 可以顺便确认有没有对象资源。如果没有,就省略CopyRecord操作 long lRet = channel.DoCopyRecord(info.RecPath, strTargetRecPath, strAction == "move" ? true : false, // bDeleteSourceRecord out output_timestamp, out strOutputRecPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; strError = "复制实体记录 '" + info.RecPath + "' 时发生错误: " + strError; goto ERROR1; } // 修改xml记录。<parent>元素发生了变化 byte[] baOutputTimestamp = null; string strOutputRecPath1 = ""; lRet = channel.DoSaveTextRes(strOutputRecPath, dom.OuterXml, false, "content", // ,ignorechecktimestamp output_timestamp, out baOutputTimestamp, out strOutputRecPath1, out strError); if (lRet == -1) goto ERROR1; oldrecordpaths.Add(info.RecPath); newrecordpaths.Add(strOutputRecPath); parentids.Add(strParentID); if (strAction == "move") oldrecords.Add(info.OldRecord); } finally { this.EntityLocks.UnlockForWrite(info.ItemBarcode); } nOperCount++; } return nOperCount; ERROR1: // 不要Undo return -1; }
// 复制属于同一书目记录的全部实体记录 // parameters: // strAction copy / move // return: // -2 目标实体库不存在,无法进行复制或者删除 // -1 error // >=0 实际复制或者移动的实体记录数 public int CopyBiblioChildEntities(RmsChannel channel, string strAction, List<DeleteEntityInfo> entityinfos, string strTargetBiblioRecPath, XmlDocument domOperLog, out string strError) { strError = ""; if (entityinfos == null || entityinfos.Count == 0) return 0; int nOperCount = 0; XmlNode root = null; if (domOperLog != null) { root = domOperLog.CreateElement(strAction == "copy" ? "copyEntityRecords" : "moveEntityRecords"); domOperLog.DocumentElement.AppendChild(root); } // 获得目标书目库下属的实体库名 string strTargetItemDbName = ""; string strTargetBiblioDbName = ResPath.GetDbName(strTargetBiblioRecPath); // return: // -1 出错 // 0 没有找到 // 1 找到 int nRet = this.GetItemDbName(strTargetBiblioDbName, out strTargetItemDbName, out strError); if (nRet == 0 || string.IsNullOrEmpty(strTargetItemDbName) == true) { return -2; // 目标实体库不存在 } string strParentID = ResPath.GetRecordId(strTargetBiblioRecPath); if (string.IsNullOrEmpty(strParentID) == true) { strError = "目标书目记录路径 '"+strTargetBiblioRecPath+"' 不正确,无法获得记录号"; return -1; } List<string> newrecordpaths = new List<string>(); List<string> oldrecordpaths = new List<string>(); List<string> parentids = new List<string>(); List<string> oldrecords = new List<string>(); for (int i = 0; i < entityinfos.Count; i++) { DeleteEntityInfo info = entityinfos[i]; byte[] output_timestamp = null; string strOutputRecPath = ""; string strNewBarcode = ""; // 复制中修改后的册条码号 this.EntityLocks.LockForWrite(info.ItemBarcode); try { XmlDocument dom = new XmlDocument(); try { dom.LoadXml(info.OldRecord); } catch (Exception ex) { strError = "记录 '" + info.RecPath + "' 装入XMLDOM发生错误: " + ex.Message; goto ERROR1; } DomUtil.SetElementText(dom.DocumentElement, "parent", strParentID); // 复制的情况,要避免出现操作后的条码号重复现象 if (strAction == "copy") { // 修改册条码号,避免发生条码号重复 string strOldItemBarcode = DomUtil.GetElementText(dom.DocumentElement, "barcode"); if (String.IsNullOrEmpty(strOldItemBarcode) == false) { strNewBarcode = strOldItemBarcode + "_" + Guid.NewGuid().ToString(); DomUtil.SetElementText(dom.DocumentElement, "barcode", strNewBarcode); } // *** 后面这几个清除动作要作为规则出现 // 清除 refid DomUtil.SetElementText(dom.DocumentElement, "refID", null); // 把借者清除 // (源实体记录中如果有借阅信息,在普通界面上是无法删除此记录的。只能用出纳窗正规进行归还,然后才能删除) { DomUtil.SetElementText(dom.DocumentElement, "borrower", null); DomUtil.SetElementText(dom.DocumentElement, "borrowPeriod", null); DomUtil.SetElementText(dom.DocumentElement, "borrowDate", null); } } // TODO: 可以顺便确认有没有对象资源。如果没有,就省略CopyRecord操作 long lRet = channel.DoCopyRecord(info.RecPath, strTargetItemDbName + "/?", strAction == "move" ? true : false, // bDeleteSourceRecord out output_timestamp, out strOutputRecPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; strError = "复制实体记录 '" + info.RecPath + "' 时发生错误: " + strError; goto ERROR1; } // 修改xml记录。<parent>元素发生了变化 byte[] baOutputTimestamp = null; string strOutputRecPath1 = ""; lRet = channel.DoSaveTextRes(strOutputRecPath, dom.OuterXml, false, "content", // ,ignorechecktimestamp output_timestamp, out baOutputTimestamp, out strOutputRecPath1, out strError); if (lRet == -1) goto ERROR1; oldrecordpaths.Add(info.RecPath); newrecordpaths.Add(strOutputRecPath); parentids.Add(strParentID); if (strAction == "move") oldrecords.Add(info.OldRecord); } finally { this.EntityLocks.UnlockForWrite(info.ItemBarcode); } // 增补到日志DOM中 if (domOperLog != null) { Debug.Assert(root != null, ""); XmlNode node = domOperLog.CreateElement("record"); root.AppendChild(node); DomUtil.SetAttr(node, "recPath", info.RecPath); DomUtil.SetAttr(node, "targetRecPath", strOutputRecPath); // 2014/1/5 if (string.IsNullOrEmpty(strNewBarcode) == false) DomUtil.SetAttr(node, "newBarcode", strNewBarcode); } nOperCount++; } return nOperCount; ERROR1: // Undo已经进行过的操作 if (strAction == "copy") { string strWarning = ""; foreach (string strRecPath in newrecordpaths) { string strTempError = ""; byte[] timestamp = null; byte[] output_timestamp = null; REDO_DELETE: long lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strTempError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (timestamp == null) { timestamp = output_timestamp; goto REDO_DELETE; } } strWarning += strTempError + ";"; } } if (string.IsNullOrEmpty(strWarning) == false) strError = strError + "。在Undo过程中,又遇到出错: " + strWarning; } else if (strAction == "move") { string strWarning = ""; for (int i = 0; i < newrecordpaths.Count; i++) { byte[] output_timestamp = null; string strOutputRecPath = ""; string strTempError = ""; // TODO: 如果确认没有对象,就可以省略这一步 long lRet = channel.DoCopyRecord(newrecordpaths[i], oldrecordpaths[i], true, // bDeleteSourceRecord out output_timestamp, out strOutputRecPath, out strTempError); if (lRet == -1) { strWarning += strTempError + ";"; } // 修改xml记录。<parent>元素发生了变化 byte[] baOutputTimestamp = null; string strOutputRecPath1 = ""; lRet = channel.DoSaveTextRes(oldrecordpaths[i], oldrecords[i], false, "content", // ,ignorechecktimestamp output_timestamp, out baOutputTimestamp, out strOutputRecPath1, out strTempError); if (lRet == -1) { strWarning += strTempError + ";"; } } if (string.IsNullOrEmpty(strWarning) == false) strError = strError + "。在Undo过程中,又遇到出错: " + strWarning; } return -1; }
// 执行SetEntities API中的"move"操作 // 1) 操作成功后, NewRecord中有实际保存的新记录,NewTimeStamp为新的时间戳 // 2) 如果返回TimeStampMismatch错,则OldRecord中有库中发生变化后的“原记录”,OldTimeStamp是其时间戳 // return: // -1 出错 // 0 成功 int DoEntityOperMove( string strStyle, SessionInfo sessioninfo, RmsChannel channel, EntityInfo info, ref XmlDocument domOperLog, ref List<EntityInfo> ErrorInfos) { // int nRedoCount = 0; EntityInfo error = null; bool bExist = true; // info.RecPath所指的记录是否存在? int nRet = 0; long lRet = 0; string strError = ""; // 检查路径 if (info.OldRecPath == info.NewRecPath) { strError = "当action为\"move\"时,info.NewRecordPath路径 '" + info.NewRecPath + "' 和info.OldRecPath '" + info.OldRecPath + "' 必须不相同"; goto ERROR1; } // 检查即将覆盖的目标位置是不是有记录,如果有,则不允许进行move操作。 // 如果要进行带覆盖目标位置记录功能的move操作,前端可以先执行一个delete操作,然后再执行move操作。 // 这样规定,是为了避免过于复杂的判断逻辑,也便于前端操作者清楚操作的后果。 // 因为如果允许move带有覆盖目标记录功能,则被覆盖的记录的预删除操作,等于进行了一次注销,但这个效用不明显,对前端操作人员准确判断事态并对后果负责(而且可能这种注销需要额外的操作权限),不利 bool bAppendStyle = false; // 目标路径是否为追加形态? string strTargetRecId = ResPath.GetRecordId(info.NewRecPath); if (strTargetRecId == "?" || String.IsNullOrEmpty(strTargetRecId) == true) bAppendStyle = true; string strOutputPath = ""; string strMetaData = ""; if (bAppendStyle == false) { string strExistTargetXml = ""; byte[] exist_target_timestamp = null; // 获取覆盖目标位置的现有记录 lRet = channel.GetRes(info.NewRecPath, out strExistTargetXml, out strMetaData, out exist_target_timestamp, out strOutputPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { // 如果记录不存在, 说明不会造成覆盖态势 /* strExistSourceXml = "<root />"; exist_source_timestamp = null; strOutputPath = info.NewRecPath; * */ } else { error = new EntityInfo(info); error.ErrorInfo = "移动操作发生错误, 在读入即将覆盖的目标位置 '" + info.NewRecPath + "' 原有记录阶段:" + strError; error.ErrorCode = channel.OriginErrorCode; ErrorInfos.Add(error); return -1; } } else { // 如果记录存在,则目前不允许这样的操作 strError = "移动(move)操作被拒绝。因为在即将覆盖的目标位置 '" + info.NewRecPath + "' 已经存在册记录。请先删除(delete)这条记录,再进行移动(move)操作"; goto ERROR1; } } string strExistSourceXml = ""; byte[] exist_source_timestamp = null; // 先读出数据库中源位置的已有记录 // REDOLOAD: lRet = channel.GetRes(info.OldRecPath, out strExistSourceXml, out strMetaData, out exist_source_timestamp, out strOutputPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { /* // 如果记录不存在, 则构造一条空的记录 bExist = false; strExistSourceXml = "<root />"; exist_source_timestamp = null; strOutputPath = info.NewRecPath; * */ // 这种情况如果放宽,会有严重的副作用,所以不让放宽 strError = "移动(move)操作的源记录 '" + info.OldRecPath + "' 在数据库中不存在,所以无法进行移动操作。"; goto ERROR1; } else { error = new EntityInfo(info); error.ErrorInfo = "移动(move)操作发生错误, 在读入库中原有源记录(路径在info.OldRecPath) '" + info.OldRecPath + "' 阶段:" + strError; error.ErrorCode = channel.OriginErrorCode; ErrorInfos.Add(error); return -1; } } // 把两个记录装入DOM XmlDocument domSourceExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { domSourceExist.LoadXml(strExistSourceXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(info.NewRecord); } catch (Exception ex) { strError = "info.NewRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 观察时间戳是否发生变化 nRet = ByteArray.Compare(info.OldTimestamp, exist_source_timestamp); if (nRet != 0) { // 时间戳不相等了 // 需要把info.OldRecord和strExistXml进行比较,看看和册登录有关的元素(要害元素)值是否发生了变化。 // 如果这些要害元素并未发生变化,就继续进行合并、覆盖保存操作 XmlDocument domOld = new XmlDocument(); try { domOld.LoadXml(info.OldRecord); } catch (Exception ex) { strError = "info.OldRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 比较两个记录, 看看和册登录有关的字段是否发生了变化 // return: // 0 没有变化 // 1 有变化 nRet = IsRegisterInfoChanged(domOld, domSourceExist); if (nRet == 1) { error = new EntityInfo(info); // 错误信息中, 返回了修改过的原记录和新时间戳 error.OldRecord = strExistSourceXml; error.OldTimestamp = exist_source_timestamp; if (bExist == false) error.ErrorInfo = "移动操作发生错误: 数据库中的原记录 (路径为'" + info.OldRecPath + "') 已被删除。"; else error.ErrorInfo = "移动操作发生错误: 数据库中的原记录 (路径为'" + info.OldRecPath + "') 已发生过修改"; error.ErrorCode = ErrorCodeValue.TimestampMismatch; ErrorInfos.Add(error); return -1; } // exist_source_timestamp此时已经反映了库中被修改后的记录的时间戳 } string strSourceLibraryCode = ""; // 检查一个册记录的馆藏地点是否符合馆代码列表要求 // return: // -1 检查过程出错 // 0 符合要求 // 1 不符合要求 nRet = CheckItemLibraryCode(domSourceExist, sessioninfo, // sessioninfo.LibraryCodeList, out strSourceLibraryCode, out strError); if (nRet == -1) goto ERROR1; // 检查旧记录是否属于管辖范围 if (sessioninfo.GlobalUser == false || sessioninfo.UserType == "reader") { if (nRet != 0) { strError = "即将被移动的册记录其馆藏地点不符合要求: " + strError; goto ERROR1; } } bool bNoOperations = false; // 是否为不要覆盖<operations>内容 if (StringUtil.IsInList("nooperations", strStyle) == true) { bNoOperations = true; } if (bNoOperations == false) { // 2010/4/8 // nRet = SetOperation( ref domNew, "moved", sessioninfo.UserID, "", out strError); if (nRet == -1) goto ERROR1; } // 合并新旧记录 string strNewXml = ""; nRet = MergeTwoEntityXml(domSourceExist, domNew, out strNewXml, out strError); if (nRet == -1) goto ERROR1; // 只有order权限的情况 if (StringUtil.IsInList("setiteminfo", sessioninfo.RightsOrigin) == false && StringUtil.IsInList("setentities", sessioninfo.RightsOrigin) == false && StringUtil.IsInList("order", sessioninfo.RightsOrigin) == true) { // 2009/11/26 changed string strEntityDbName = ResPath.GetDbName(info.OldRecPath); if (String.IsNullOrEmpty(strEntityDbName) == true) { strError = "从路径 '" + info.OldRecPath + "' 中获得数据库名时失败"; goto ERROR1; } string strBiblioDbName = ""; // 根据实体库名, 找到对应的书目库名 // 注意,返回1的时候,strBiblioDbName也有可能为空 // return: // -1 出错 // 0 没有找到 // 1 找到 nRet = GetBiblioDbNameByItemDbName(strEntityDbName, out strBiblioDbName, out strError); if (nRet == 0 || nRet == -1) { strError = "根据实体库名 '" + strEntityDbName + "' 中获得书目库名时失败"; goto ERROR1; } // 非工作库 if (IsOrderWorkBiblioDb(strBiblioDbName) == false) { // 非工作库。要求<state>包含“加工中” string strState = DomUtil.GetElementText(domSourceExist.DocumentElement, "state"); if (IncludeStateProcessing(strState) == false) { strError = "当前帐户只有order权限而没有setiteminfo(或setentities)权限,不能用move功能删除从属于非工作库的、状态不包含“加工中”的实体记录 '" + info.OldRecPath + "'"; goto ERROR1; } } // TODO: 如果原样移动,目标记录并不被修改,似乎也该允许? } // 移动记录 byte[] output_timestamp = null; // TODO: Copy后还要写一次?因为Copy并不写入新记录。(注:Copy/Move有时候会跨库,这样记录中<parent>需要改变) // 其实Copy的意义在于带走资源。否则还不如用Save+Delete lRet = channel.DoCopyRecord(info.OldRecPath, info.NewRecPath, true, // bDeleteSourceRecord out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "DoCopyRecord() error :" + strError; goto ERROR1; } string strTargetLibraryCode = ""; // 检查一个册记录的馆藏地点是否符合馆代码列表要求 // return: // -1 检查过程出错 // 0 符合要求 // 1 不符合要求 nRet = CheckItemLibraryCode(strNewXml, sessioninfo, // sessioninfo.LibraryCodeList, out strTargetLibraryCode, out strError); if (nRet == -1) goto ERROR1; // 2014/7/3 if (this.VerifyBookType == true) { string strEntityDbName = ResPath.GetDbName(info.NewRecPath); if (String.IsNullOrEmpty(strEntityDbName) == true) { strError = "从路径 '" + info.NewRecPath + "' 中获得数据库名时失败"; goto ERROR1; } XmlDocument domTemp = new XmlDocument(); domTemp.LoadXml(strNewXml); // 检查一个册记录的读者类型是否符合值列表要求 // parameters: // return: // -1 检查过程出错 // 0 符合要求 // 1 不符合要求 nRet = CheckItemBookType(domTemp, strEntityDbName, out strError); if (nRet == -1 || nRet == 1) goto ERROR1; } // 检查新记录是否属于管辖范围 if (sessioninfo.GlobalUser == false || sessioninfo.UserType == "reader") { if (nRet != 0) { strError = "册记录新内容中的馆藏地点不符合要求: " + strError; goto ERROR1; } } // Debug.Assert(strOutputPath == info.NewRecPath); string strTargetPath = strOutputPath; lRet = channel.DoSaveTextRes(strTargetPath, strNewXml, false, // include preamble? "content", output_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "WriteEntities()API move操作中,实体记录 '" + info.OldRecPath + "' 已经被成功移动到 '" + strTargetPath + "' ,但在写入新内容时发生错误: " + strError; if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { // 不进行反复处理。 // 因为源已经移动,情况很复杂 } // 仅仅写入错误日志即可。没有Undo this.WriteErrorLog(strError); /* if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount > 10) { strError = "反复保存(DoCopyRecord())均遇到时间戳冲突, 超过10次重试仍然失败"; goto ERROR1; } // 发现时间戳不匹配 // 重复进行提取已存在记录\比较的过程 nRedoCount++; goto REDOLOAD; }*/ error = new EntityInfo(info); error.ErrorInfo = "移动操作发生错误:" + strError; error.ErrorCode = channel.OriginErrorCode; ErrorInfos.Add(error); return -1; } else // 成功 { info.NewRecPath = strOutputPath; // 兑现保存的位置,因为可能有追加形式的路径 DomUtil.SetElementText(domOperLog.DocumentElement, "libraryCode", strSourceLibraryCode + "," + strTargetLibraryCode); // 册所在的馆代码 DomUtil.SetElementText(domOperLog.DocumentElement, "action", "move"); if (String.IsNullOrEmpty(strStyle) == false) DomUtil.SetElementText(domOperLog.DocumentElement, "style", strStyle); // 新记录 XmlNode node = DomUtil.SetElementText(domOperLog.DocumentElement, "record", strNewXml); DomUtil.SetAttr(node, "recPath", info.NewRecPath); // 旧记录 node = DomUtil.SetElementText(domOperLog.DocumentElement, "oldRecord", strExistSourceXml); DomUtil.SetAttr(node, "recPath", info.OldRecPath); // 保存成功,需要返回信息元素。因为需要返回新的时间戳 error = new EntityInfo(info); error.NewTimestamp = output_timestamp; error.NewRecord = strNewXml; error.ErrorInfo = "移动操作成功。NewRecPath中返回了实际保存的路径, NewTimeStamp中返回了新的时间戳,NewRecord中返回了实际保存的新记录(可能和提交的源记录稍有差异)。"; error.ErrorCode = ErrorCodeValue.NoError; ErrorInfos.Add(error); } return 0; ERROR1: error = new EntityInfo(info); error.ErrorInfo = strError; error.ErrorCode = ErrorCodeValue.CommonError; ErrorInfos.Add(error); return -1; }