20.01.2021, 16:44 | #41 |
Участник
|
Спасибо что поделились. Еще как вариант можно наверное сделать отдельную табличку(Статус, Результирующий файл), запускать эту операцию в пакете и приатачивать файл к этой табличке по завершению. Пользователь соответсвенно будет скачивать файл когда он сформируется и может вообще закрыть браузер
|
|
20.01.2021, 17:30 | #42 |
Участник
|
Цитата:
Сообщение от trud
Спасибо что поделились. Еще как вариант можно наверное сделать отдельную табличку(Статус, Результирующий файл), запускать эту операцию в пакете и приатачивать файл к этой табличке по завершению. Пользователь соответсвенно будет скачивать файл когда он сформируется и может вообще закрыть браузер
Для моих целей этого вполне достаточно, я всего лишь хотел восстановить возможность взаимодействия с пользователем после выполнения операции в отдельной сессии. Доработал пример для корректной работы в пакетном режиме X++: // This is a framework class. Customizing this class may cause problems with future upgrades to the software. class Test_RunbaseBatch extends RunBaseBatch { // Packed variables str csvFileContent; Email email2Send; #define.CurrentVersion(1) #localmacro.CurrentList csvFileContent, email2Send #endmacro public boolean getFromDialog() { boolean ret; ret = super(); email2Send = SysUserInfo::getUserEmail(curUserId()); return ret; } public container pack() { return [#CurrentVersion,#CurrentList]; } public void run() { info("run"); if (! this.validate()) throw error(""); commaStreamIo iO = commaStreamIo::constructForWrite(); container header = ["Num"]; iO.writeExp(header); int i; for (i=1; i<=660; i++) { iO.write(i); //sleep(1000); //Over 10 min. sleep(100); } System.IO.Stream stream = iO.getStream(); stream.Position = 0; System.IO.StreamReader reader = new System.IO.StreamReader(stream); csvFileContent = reader.ReadToEnd(); if (this.isInBatch()) { this.runAfterOperation(); } } public void runAfterOperation() { info("runAfterOperation"); Filename filename = "file.csv"; System.Byte[] byteArray = System.Text.Encoding::Unicode.GetBytes(csvFileContent); System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray); stream.Position = 0; if (this.isInBatch()) { if (SysEmailDistributor::validateEmail(email2Send)) { SysMailerMessageBuilder messageBuilder = new SysMailerMessageBuilder(); messageBuilder.setFrom(email2Send,"@SYS115063") .addTo(email2Send) .setPriority(1) .setSubject(this.caption()) .setBody(this.caption()) .addAttachment(stream, fileName); SysMailerFactory::sendNonInteractive(messageBuilder.getMessage()); info(strFmt("CSV file %1 Sent to user on e-mail %2",filename,email2Send)); } } else { File::SendFileToUser(stream, fileName); info(strFmt("CSV file %1 Sent to user",filename)); } } public boolean runsImpersonated() { return true; } public boolean unpack(container packedClass) { Version version = RunBase::getVersion(packedClass); ; switch (version) { case #CurrentVersion: [version,#CurrentList] = packedClass; break; default: return false; } return true; } public boolean validate(Object _calledFrom = null) { if (false) return checkFailed(""); return true; } static ClassDescription description() { return "Test RunBase"; } static Test_RunbaseBatch construct() { return new Test_RunbaseBatch(); } static void main(Args args) { Test_RunbaseBatch runBase; runBase = Test_RunbaseBatch::construct(); if (runBase.prompt()) { runBase.runOperation(); if (!runBase.batchInfo().parmBatchExecute()) { runBase.runAfterOperation(); } } } protected boolean canRunInNewSession() { return true; } } |
|
22.01.2021, 15:33 | #43 |
Участник
|
|
|
22.01.2021, 15:46 | #44 |
Участник
|
Круто выглядит. А как вы этого добились? я что-то думал что прогресс бар убрали
|
|
22.01.2021, 16:54 | #45 |
Участник
|
Работает только для SysOperation, в Asynchronous и ReliableAsynchronous режимах.
Важно параметризировать parmShowProgressForm(true) в контроллере, и в сервисе получить RunbaseProgress. Вот код: Controller X++: class Test_SysOperationController extends SysOperationServiceController { protected void loadFromSysLastValue() { if (!dataContractsInitialized) { // This is a bug in the SysOperationController class // never load from syslastvalue table when executing in batch // it is never a valid scenario if (!this.isInBatch()) { super(); } dataContractsInitialized = true; } } public void new() { super(); // defaulting parameters common to all scenarios // If using reliable async mechanism do not wait for the batch to // complete. This is better done at the application level since // the batch completion state transition is not predictable //this.parmRegisterCallbackForReliableAsyncCall(false); // default for controllers in these samples is synchronous execution // batch execution will be explicity specified. The default for // SysOperationServiceController is ReliableAsynchronous execution //this.parmExecutionMode(SysOperationExecutionMode::ReliableAsynchronous); this.parmExecutionMode(SysOperationExecutionMode::Asynchronous); //this.parmExecutionMode(SysOperationExecutionMode::Synchronous); // ProgressBar doesn't work. this.parmShowProgressForm(true); this.parmClassName(classStr(Test_SysOperationService)); this.parmMethodName(methodStr(Test_SysOperationService, runOperation)); //this.parmMethodName(methodStr(Test_SysOperationService, runOperationResult)); // Doesn't work ??? } public ClassDescription caption() { return 'Test SysOperation !'; } protected boolean canRunInNewSession() { return true; } public void asyncCallbackVoid(AsyncTaskResult _asyncResult) { info("Async Callback Void"); super(_asyncResult); /* SysOperationDataContractInfo contractInfo = this.getDataContractInfoObjects().lookup("dataContract"); Test_SysOperationDataContract dataContract = contractInfo.dataContractObject(); */ //Box::info("Async Callback Void Box"); //this.sendFile("Async Callback Void"); } public void asyncCallbackReturnValue(AsyncTaskResult _asyncResult, anytype _returnValue) { // Doesn't work ??? info("Async Callback Result"); //super(_asyncResult,_returnValue); //Box::info("Async Callback Result Box"); } public void sendFile(str _fileContent) { Filename filename = "file.csv"; System.Byte[] byteArray = System.Text.Encoding::Unicode.GetBytes(_fileContent); System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray); stream.Position = 0; File::SendFileToUser(stream, fileName); info(strFmt("File %1 Sent to user",filename)); } public static void main(Args args) { Test_SysOperationController operation; operation = new Test_SysOperationController(); operation.startOperation(); } } X++: [DataContractAttribute, SysOperationContractProcessingAttribute(classStr(Test_SysOperationUIBuilder))] class Test_SysOperationDataContract extends SysOperationDataContractBase { int duration; [DataMemberAttribute, SysOperationLabelAttribute('Duration (sec.)'), SysOperationHelpTextAttribute('Type some number >= 0'), SysOperationDisplayOrderAttribute('1')] public int parmDuration(int _duration = duration) { duration = _duration; return duration; } } X++: class Test_SysOperationUIBuilder extends SysOperationAutomaticUIBuilder { DialogField durationField; public void postBuild() { super(); // get references to dialog controls after creation durationField = this.bindInfo().getDialogField(this.dataContractObject(), methodStr(Test_SysOperationDataContract, parmDuration)); } public void postRun() { super(); // register overrides for form control events durationField.registerOverrideMethod(methodstr(FormIntControl, validate), methodstr(Test_SysOperationUIBuilder, durationFieldValidate), this); } public boolean durationFieldValidate(FormIntControl _control) { if (_control.value() < 0) { error('Please type a number >= 0'); return false; } return true; } } X++: class Test_SysOperationService extends SysOperationServiceBase { public void runOperation(Test_SysOperationDataContract dataContract) { RunbaseProgress progress = this.getProgressController(dataContract); int total = dataContract.parmDuration(); progress.setTotal(total); int i; for (i = 1; i <= total; i++ ) { progress.incCount(); progress.setText(strFmt("%1 / %2",i,total)); progress.update(true); sleep(1000); } info('Done!'); } public container runOperationResult(Test_SysOperationDataContract dataContract) { this.runOperation(dataContract); return [dataContract.parmDuration()]; } } Может кто то подскажет, почему не работает asyncCallbackReturnValue(..) для parmMethodName(methodStr(Test_SysOperationService, runOperationResult)) ? Выбрасывается ошибка что нет такого метода но вот же он... ??? |
|
|
За это сообщение автора поблагодарили: Logger (5). |
26.01.2021, 18:06 | #46 |
Участник
|
C asyncCallbackReturnValue(..) разобрался, это баг в стандартном методе SysOperationServiceController.asyncCallbackReturnValue(..). Колбэк возвращаяет одну переменную, значит второй параметр нужно сделать необязательным.
Вот так все хорошо, и _returnValue имеет правильное значение, ровно то что вернул метод сервиса. X++: ... public void asyncCallbackReturnValue(AsyncTaskResult _asyncResult, anytype _returnValue = _asyncResult.getResult()) { info(strFmt("Async Callback Result: %1",con2Str(_returnValue))); //super(_asyncResult,_returnValue); //Box::info("Async Callback Result Box"); } ... Последний раз редактировалось kair84; 26.01.2021 в 18:14. |
|
|
За это сообщение автора поблагодарили: madm (1). |
27.01.2021, 12:52 | #47 |
Участник
|
Вижу что тема Вам не сильно интересна, хотя казалось что даже очень.
Чтоб закрыть её, вот как можно сделать прогресс для обычного RunBase : X++: class Test_RunBaseBatch extends RunBaseBatch { DialogField dfDuration; // Packed variables int duration; str csvFileContent; Email email2Send; guid callId; #define.CurrentVersion(1) #localmacro.CurrentList callId, duration, csvFileContent, email2Send #endmacro SysOperationProgressWait progressWait; //SysProgress sysProgress; public object dialog() { DialogRunbase dialog; dialog = super(); dfDuration = dialog.addFieldValue(identifierStr(Integer),duration,"Duration (Sec.)"); return dialog; } public boolean getFromDialog() { boolean ret; ret = super(); duration = dfDuration.value(); email2Send = SysUserInfo::getUserEmail(curUserId()); return ret; } public container pack() { return [#CurrentVersion,#CurrentList]; } public void runAsync() { callId = newGuid(); progressWait = SysOperationProgressWait::construct(); progressWait.parmCallId(callId); progressWait.parmCaption(this.caption()); progressWait.parmBatchTaskId(0); xGlobal::runAsyncWithObjectCallback( classNum(Test_RunBaseBatch), staticMethodStr(Test_RunBaseBatch, doRunAsync), this.pack(), this, methodStr(Test_RunBaseBatch, runAsyncCallback)); Message::Add(MessageSeverity::Informational ,"Start Operation Async"); progressWait.beginWaiting(); } private void runAsyncCallback(AsyncTaskResult _result) { progressWait.endWaiting(); Message::Add(MessageSeverity::Informational ,"End Operation Async"); container parms = _result.getResult(); this.unpack(parms); this.runAfterOperation(); } private static container doRunAsync(container _parms, System.Threading.CancellationToken cancellationToken) { Test_RunBaseBatch runbase = new Test_RunBaseBatch(); runbase.unpack(_parms); runbase.run(); return runbase.pack(); } public void run() { Message::Add(MessageSeverity::Informational ,"run"); if (! this.validate()) throw error(""); SysOperationProgressServer progressBar = new SysOperationProgressServer(1,false,callId); progressBar.setCaption("SysOperationProgressServer !!!"); progressBar.setTotal(duration); commaStreamIo iO = commaStreamIo::constructForWrite(); container header = ["Num"]; iO.writeExp(header); int i; for (i=1; i<=duration; i++) { iO.write(i); progressBar.incCount(); progressBar.setText(strFmt("%1 / %2",i,duration)); sleep(1000); } System.IO.Stream stream = iO.getStream(); stream.Position = 0; System.IO.StreamReader reader = new System.IO.StreamReader(stream); csvFileContent = reader.ReadToEnd(); if (this.isInBatch()) { this.runAfterOperation(); } Message::Add(MessageSeverity::Informational ,"Done!"); } public void runAfterOperation() { info("runAfterOperation"); Filename filename = "file.csv"; System.Byte[] byteArray = System.Text.Encoding::Unicode.GetBytes(csvFileContent); System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray); stream.Position = 0; if (this.isInBatch()) { if (SysEmailDistributor::validateEmail(email2Send)) { SysMailerMessageBuilder messageBuilder = new SysMailerMessageBuilder(); messageBuilder.setFrom(email2Send,"@SYS115063") .addTo(email2Send) .setPriority(1) .setSubject(this.caption()) .setBody(this.caption()) .addAttachment(stream, fileName); SysMailerFactory::sendNonInteractive(messageBuilder.getMessage()); info(strFmt("CSV file %1 Sent to user on e-mail %2",filename,email2Send)); } } else { File::SendFileToUser(stream, fileName); info(strFmt("CSV file %1 Sent to user",filename)); } } public boolean unpack(container packedClass) { Version version = RunBase::getVersion(packedClass); ; switch (version) { case #CurrentVersion: [version,#CurrentList] = packedClass; break; default: return false; } return true; } public boolean validate(Object _calledFrom = null) { if (false) return checkFailed(""); return true; } static ClassDescription description() { return "Test RunBase Descr."; } static void main(Args args) { Test_RunBaseBatch runBase; runBase = new Test_RunBaseBatch(); runBase.init(); if (runBase.prompt()) { runBase.runAsync(); } } protected boolean canRunInNewSession() { return false; } } Очевидно, что для того, чтобы во время выполнения кода на сервере была возможность взаимодействовать с пользователем, необходимо выполнять операцию асинхронно, а для того, чтобы связать серверный процесс с интерфейсом стали использовать таблицу SysProgress, в нее пишется вся необходимая информация о прогрессе (один или несколько прогресс-баров, заголовок, текст, и т.д) и обновляется в время выполнения. Надеюсь проделанная мной работа не напрасна и пригодится кому то еще кроме меня. Возможно даже удостоится переноса в Блог. |
|
|
За это сообщение автора поблагодарили: trud (10). |
27.01.2021, 13:22 | #48 |
Участник
|
Тема полезная, спасибо что поделились
А зачем вызов "if (this.isInBatch()) " в методе run? еще вопрос - а если вернуть в canRunInNewSession() = true, не получится ли упросить код? |
|
27.01.2021, 14:22 | #49 |
Участник
|
Из батча выполнится только run(), и тогда из него нужно позвать runAfterOperation(), а для обычного запуска ( без батча) run() запустится асинхронно, а runAfterOperation() уже после, в синхронном режиме, для того чтобы проинтерактировать с юзером например. Это опционально можно и не делать вовсе.
Цитата:
Варианты всегда есть, ну или почти всегда. Когда уже становится ясно как именно работает этот прогресбар в 7, то и варианты могут различные найтись. Может быть кто то покопается еще и найдет способ оживить старый прогресбар. Последний раз редактировалось kair84; 27.01.2021 в 14:27. |
|