Terminology provider connection issue

Dear RWS,

I have created a custom terminology provider for Studio 2022, 2021 and 2019. It connects to a remote web service and use that as a source. When the studio loads the terminology provider, if the service is not available, user not available...etc the plugin display a Window, in which the user can try to login. In studio 2022 everything works fine, but in Studio 2019, it fails to load the terminology provider, because when the plugin tries to display the UI to login, it throws an error "calling thread must be STA". In Studio 2022 this error is also thrown, but after the first error, studio API tries once again and it successfully display the login UI. Thus I thought this is a fallback mechanism from studio side, if this error is cacthed then it retries to create the provider from the STA thread. I beleived in this, because it is quite a common scenario, if i remember well, if i add a server based MultiTerm termbase, and if the server is not available it also display a similar login window. However finding that 2019 studio works differently, it questions my beleif about that it is an on purpose fallback mechanism.

Can you advise about this?

Thanks in advance,

Balazs

emoji
Parents
  • Hi , I've been trying to gather some input from other teams to help in understanding what could be causing this issue with your plugin integration, but until now I have not found anything useful in your case and you can't simply decorate the method with STAthread and expect it to magically work, right.

    Is your solution somewhere public like github, so that I could review your integration?

    emoji
  • Hi

    Made a minimal repro solution, available on https://github.com/eurocombfeher/SampleProvider

    However i am no longer able to reproduce the desired output in Studio2022, even though I claimed it worked when I posted this thread. I don't know what happenned, maybe Studio has been updated.

    About desired outcome, in the custom termonology provider, there will be a TryLogin method, which simulates what happens in real life: If the service is not connected then try connect with a blocking call, if it fails or some config is missing it opens a configuration dialog, where you can login. Now in this example it just opens the config dialog, where you can click a button, which call an async login method (Task.Delay), then close the configuration window. 

    Reproduction

    1) open a sample project add the custom provider to its termbase, save it and close studio

    2) if you open studio, open the project settings (to ensure that the "login" succeded) then open any file from the project you will see the provider is working

    3) Close studio, reopen it. Now without opening project settings, just open a file from the project. The settings dialog come up and a warning that the provider is not loaded and it really does not work. (so if the async call/window popup happens, the provider will not be loaded)

    I wish to solve this 3) step to be able to "login" on demand when the provider is loaded on opening file (similarly how MT provider works)

    emoji
  • Hi ,

    Thank you for making available the source code, much appreciated. I allocated some time to review this project today, sorry for taking so long.

    I’ve been able to identify where you are having problems with this plugin and will try to explain as best I can. I’ve also created a PR against your repo from here: github.com/.../1

    We need to consider the separation of concerns with our plugin integration & I must confess that it isn’t explained clearly from our documentation, so let me try to shed some light...

    The provider implementation (i.e. AbstractTerminologyProvider) should not have any dependency on UI elements.  It is expected that Windows/Controls are managed from the ITerminologyProviderWinFormsUI implementation, where you have both Browse & Edit methods to present a UI to the user.

    Although the provider implementation (AbstractTerminologyProvider) seems like the right place to show a dialog/window that asks the user to provide some input (e.g. credentials, settings), it isn’t.  The provider should not have any dependency on the UI, as it can be executed in any automation context.

    The entry point for your provider is from the factory implementation (i.e. ITerminologyProviderFactory).  It is from there that you should evaluate if the provider has been added to the project and if the credentials are valid etc...  If they are valid, then proceed and initialize the provider, otherwise throw an exception; the exception will then be visible from the editor in your use case, but also visible as an exception from the project automation context.  The user would then typically need to go to the project setting and open the settings window of the provider, to update the information accordingly.

    emoji
  • Hi Patrick,

    Thank you for youre reply.

    You are right, I understand that  adding a logic like this into the constructor is not welcomed. I also understand the ITerminologyProviderWinFormsUI  part.

    However if you open a project where you have a groupshare termbase, but the provider cannot connect to the remote service, then on opening a file the following UI appears (see image below). Here you can try to reconnect to the provider before the initilization of the file continues (as you see in the background nothing is opened in the editor yet, the file opening is still in progress). I wanted to copy this behaviour in my provider.

    I do not understand how would help on my situation to move the TryLogin method from the contructor to the ITerminologyProviderFactory.CreateTerminologyProvider. I don't see the logic which calls this constructor/factory method, so if you can reassure that it can help my case, then I stop asking question, just implement it this way and hope for the best. Otherwise my question is that how can I imitate MT groupshare behaviour in my provider? I don't see how ITerminologyProviderWinFormsUI would help this case as well, maybe this is just the lack of API knowledge, but as I see that interface, the methods/actions are initiated from the user, not by studio.

    Trados Studio screenshot showing a 'Sign in' dialog box for GroupShare Authentication with fields for Server Address, Username, and Password. Editor in the background indicates file opening in progress with no open term base.

    emoji


    Generated Image Alt-Text
    [edited by: Trados AI at 10:14 AM (GMT 0) on 4 Mar 2024]
Reply
  • Hi Patrick,

    Thank you for youre reply.

    You are right, I understand that  adding a logic like this into the constructor is not welcomed. I also understand the ITerminologyProviderWinFormsUI  part.

    However if you open a project where you have a groupshare termbase, but the provider cannot connect to the remote service, then on opening a file the following UI appears (see image below). Here you can try to reconnect to the provider before the initilization of the file continues (as you see in the background nothing is opened in the editor yet, the file opening is still in progress). I wanted to copy this behaviour in my provider.

    I do not understand how would help on my situation to move the TryLogin method from the contructor to the ITerminologyProviderFactory.CreateTerminologyProvider. I don't see the logic which calls this constructor/factory method, so if you can reassure that it can help my case, then I stop asking question, just implement it this way and hope for the best. Otherwise my question is that how can I imitate MT groupshare behaviour in my provider? I don't see how ITerminologyProviderWinFormsUI would help this case as well, maybe this is just the lack of API knowledge, but as I see that interface, the methods/actions are initiated from the user, not by studio.

    Trados Studio screenshot showing a 'Sign in' dialog box for GroupShare Authentication with fields for Server Address, Username, and Password. Editor in the background indicates file opening in progress with no open term base.

    emoji


    Generated Image Alt-Text
    [edited by: Trados AI at 10:14 AM (GMT 0) on 4 Mar 2024]
Children
  • Hi  , the integrated groupshare provider solution is very different to what is exposed from the API for the third-party plugins implementations. 

    I do not understand how would help on my situation to move the TryLogin method from the contructor to the ITerminologyProviderFactory.CreateTerminologyProvider. I don't see the logic which calls this constructor/factory method, so if you can reassure that it can help my case, then I stop asking question, just implement it this way and hope for the best.

    The API enables you to implement a UI (ITerminologyProviderWinFormsUI) that is presented to the user when they add the provider to the project, or edit the provider settings...  A UI is not expected anywhere else other than the view (i.e. ITerminologyProviderViewerWinFormsUI).

    It is expected that the provider evalutates the settings/credentials from the factory implementation (i.e. ITerminologyProviderFactory), which is the entry point of your provider.  You can of course attempt to introduce a UI anywhere you see fit with your implementation, but you'll need to take into consideration how it will work with automation procedures outside of the Studio context, such as ProjectAutomation API etc...

    emoji
  • It is expected that the provider evalutates the settings/credentials from the factory implementation (i.e. ITerminologyProviderFactory), which is the entry point of your provider. 

    I can move the UI up there (already changed in the git source). I understand the logic behind separation, but I didn't see it would help in my case. It still won't be on the STA threads when a file is opened, thus on opening the file we cannot "reconnect" to the provider.

    Hi  , the integrated groupshare provider solution is very different to what is exposed from the API for the third-party plugins implementations. 

    Thus it is not possible to mimic MT behaviour? For MT it has been solved "in house" to work like that, but this is not possible for custom providers (at the moment).

    emoji
  • Hi  , so your concern is if you provide a project to a user with a reference to the terminology provider, then they would need to be presented with window asking them to provide credentials, correct?

    You can approach this in two ways.

    1. Instruct the user to edit the provider settings (from the projects settings) and provide valid credentials.  The credentials should then be persisted in the credential store, that is managed by the Trados Studio context.

    2. Attempt to load a form that enables the user to provide credentials when the provider is loaded into context (i.e. the factory implementation).  The following is a basic  example of how you might go about implementing this.

    public ITerminologyProvider CreateTerminologyProvider(Uri terminologyProviderUri, ITerminologyProviderCredentialStore credentials)
    {
    	// 1. Check criteria is valid from the terminology provider uri
    	// TODO: verify the parameters (if any) are valid
    
    	// 2. Check if credentials (if needed) are available from the credential store
    	var credential = credentials.GetCredential(terminologyProviderUri);
    
    	//	2.a If credential is null, then present a dialog to the user asking to provide credentials
    	//	var myForm = new Form1();
    	//	var result = myForm.ShowDialog();
    	//	if (result == DialogResult.Yes)
    	//	{
    	//		TODO update your settings
    	//	}
    						
    	//	2.b Save credentials to the credentialStore
    		credentials.AddOrUpdateCredential(terminologyProviderUri,new TerminologyProviderCredential("MyCredential", true));
    			
    	//	2.c Verify credential is valid
    	//	TODO: test credential against provider
    
    	//	2.d. If credential is not valid then throw an exception here (e.g. failed login etc...)
    	//	throw new UnauthorizedAccessException();
    
    
    	return new MyTerminologyProvider(terminologyProviderUri);
    }

    Note: you should not see an STA threading error with this basic implementation using winForms.  If you do, then I would try 'n create a separate thread + set the appartment state (example: thread.SetApartmentState(ApartmentState.STA))

    emoji
  • Using the crendetial store...etc was not the issue, but I have already tied to callback to the main thread, but it caused deadlock. I haven't thought about creating new STA thread and waiting for that to finish to solve this. It really helped.

    Thank you for your reply and patience!

    emoji