-
Content Count
3001 -
Joined
-
Last visited
-
Days Won
135
Everything posted by Remy Lebeau
-
TValueListEditor how to catch exception when key exist?
Remy Lebeau replied to softtouch's topic in VCL
Like Peter said, try the OnSetEditText or OnValidate event. OnSetEditText is called when a new grid cell value is being saved. It is called before the cell is updated, and before the new value is validated. OnValidate is called if the InplaceEditor is active and modified when 1) before a new cell is selected, 2) when the ValueListEditor is losing input focus, 3) when the mouse is being pressed down on the ValueListEditor. Either way, you can raise an exception if the user input is not acceptable to you. For instance, you can raise a custom exception that a TApplication.OnException handler can look for, or you can call SysUtils.Abort() to raise an EAbort exception, which the VCL will catch and discard. You could handle the ValueListEditor's default keyUnique exception in the TApplication.OnException event. However, TValueListEditor does not use a specialized descendant class for that particular exception, it just uses the base SysUtils.Exception class directly. So, to differentiate that exception from others, you would have to look at its Message text, which is the localizable resource string "SKeyConflict" from the Vcl.Consts unit: SKeyConflict = 'A key with the name of "%s" already exists'; The exception will not terminate the program. It is being raised in a UI handler in the context of the main message loop, which will catch any uncaught exception, and either pass it to the TApplication.OnException event if assigned or just display it to the user. Either way, the exception is then discarded. -
TValueListEditor how to catch exception when key exist?
Remy Lebeau replied to softtouch's topic in VCL
TValueListEditor has a KeyIsValid() method, which has a RaiseError property. This is the same method that TValueListEditor calls internally, with RaiseError=True, but you can also call it yourself, with RaiseError=False, eg: procedure TForm1.Button1Click(Sender: TObject); var if TValueListStrings(ValueListEditor1.Strings).KeyIsValid('key1', False) then ValueListEditor1.InsertRow('key1', 'hello', true); end; -
Alternatively, the OnMouse(Down|Up) events have a Shift parameter which defines several flags, including ssCtrl, which is present when the Ctrl key is down when the event is triggered.
-
In small pieces, sure. Not whole projects. That is not what this site is for. Then I suggest you ask on one of the many freelancing sites available, such as freelancer.com or similar.
-
Then you are going to have to show the actual error message, and the code it refers to. Every code I've shown here is correct and should be working fine. None of my examples have unused values. On the other hand, some of your examples do.
-
Do note that setting TEdit.NumbersOnly=true merely tells the TEdit to accept only digit characters and no others (so, no negatives, no decimals, etc) , but it will not validate that the text as a whole will convert to a valid integer. So you still have to handle that conversion yourself, if you don't use a UI control that does it for you.
-
On the other hand, the StrToxxxDef() functions don't let you differentiate whether valid vs invalid input was entered unless you use a sentinel value that can't appear in any valid input. The TryStrToxxx() functions let you differentiate very easily. Besides, what is the point of converting a string to an integer/floating type if you are not going to use the converted value anyway? var v: integer; begin // don't ignore the return value! // TryStrToInt('...', v); if TryStrToInt('...', v) then begin // use v as needed ... end else begin // error converting ... end; // don't ignore the return value! // StrToIntDef('...', 0); v := StrToIntDef('...', 0); if v <> 0 then begin // use v as needed ... end else begin // was the input actually '0', or did an error occur ??? end; end;
-
And alternatively, the TryStrToxxx() functions.
-
I'm guessing that code is simply old and can now be removed.
-
Do you understand what StrToInt() actually does? What do you think StrToInt('x') should do, and why do you think it should NOT throw an error (hint: it SHOULD)? The string 'x' is not representative of a valid integer value, so of course it can't be converted into an actual integer. You said the user is entering a value in an Edit box, but this code is not accessing that Edit box at all. You need something more like this instead: procedure TFormSimpleVariable.ButtonShowValueClick(Sender: TObject); var x: Integer; begin x := StrToInt(EditValue.Text); LabelResult.Caption := 'The value of x is ' + IntToStr(x); end; Alternatively, for numeric input, consider using a more appropriate UI control, like TSpinEdit or TNumberBox, which provide the input as actual integers rather than as strings.
-
Your InviaRiceviFile() function is using the wrong Add method of TIdMultipartFormDataStream. You should be using either AddFile() or the TStream overload of AddFormField(). You are using the String overload of AddFormField() instead, which is meant for text fields, not binary data fields. So, you are setting the content of the 'file' field to be just the file path itself, rather than the content of the file that is located at the path. On a side note, don't use the TIdHTTP.Request.CustomHeaders property to specify a 'Content-Type' header, use the TIdHTTP.Request.ContentType property instead. Note that the TStrings overload of the TIdHTTP.Post() method already sets the ContentType to 'application/x-www-form-urlencoded' for you. Also, when using the TIdHTTP.Request.CustomHeaders property to send your own 'Authorization' header, the TIdHTTP.Request.BasicAuthentication property should be set to False, not to True. With that said, try this: function Login: string; const tokenUri = <https del portale che riceve credenziali e genera il token>; tokenToAskForAToken = <token string>; var Params: TStringList; Token: string; begin Result := ''; idhttp2.Request.BasicAuthentication := False; IdHttp1.Request.CustomHeaders.AddValue('Authorization', 'Basic ' + tokenToAskForAToken); IdHttp1.Request.ContentType := 'application/x-www-form-urlencoded'; try Params := TStringList.Create; try Params.Add('grant_type=client_credentials'); Token := IdHTTP1.Post(tokenUri, Params); Memo1.Lines.Add(Token); Result := 'bearer ' + Token; finally Params.Free; end; except on E: Exception do Memo2.Lines.Add(E.Classname + ': ' + E.Message); end; end; procedure InviaRiceviFile; const functionUri = <https del portale che riceve ed invia i file>; bdapFilePath = 'c:\temp\BDAP_P_2022.zip'; //file di input bdapZipFilePath = 'c:\temp\XBRL_BDAP_P_2022.txt'; //file di output var Params: TIdMultiPartFormDataStream; Response: TMemoryStream; begin idhttp2.Request.BasicAuthentication := False; IdHttp2.Request.CustomHeaders.Values['Authorization'] := Login; try Params := TIdMultiPartFormDataStream.Create; try Response := TMemoryStream.Create; try Params.AddFile('file', bdapFilePath, 'application/octet-stream'); IdHttp2.Post(functionUri, Params, Response); Response.SaveToFile(bdapZipFilePath); finally Response.Free; end; finally Params.Free; end; except on E: Exception do Memo2.Lines.Add(E.Classname + ': ' + E.Message); end; end;
-
TIdIpMcastClient - Group join on second interface issue
Remy Lebeau replied to FredM's topic in Indy
That post's code is a mix of Indy and manual coding. In any case, that post is saying that a multicast receiving socket needs to be bound to 0.0.0.0 or ::0 and then join each desired interface to the multicast group. Searching around, I see many other posts with similar advice. Although TIdIPMCastClient can bind its listening sockets to 0.0.0.0/::0 (just set Binding.IP = ''), it then joins their bound IP to the group, so it would be joining 0.0.0.0/::0 to the group, which is clearly not going to work. I may have to add a new event to TIdIPMCastClient that is called after binding to let users decide which interfaces to join, and then update TIdSocketHandle.AddMulticastMembership() to let a caller specify the interface to join (TIdStack.AddMulticastMembership() already has an ALocalIP parameter for that purpose, which TIdSocketHandle sets it its currently bound IP). According to this, Linux can't receive multicast packets if the listening socket is not bound to 0.0.0.0/::0. On the other hand, I've seen several posts, such as this one, which say a receiving socket should be bound to the multicast group IP instead of a local interface, and then join the desired interfaces to the group. Must be a Linux/Posix thing, because that advice is in direct conflict with Microsoft's documentation for receiving multicast on Windows: So, I'm not sure what to think at this point. It seems that everyone agrees that binding a receiver socket to 0.0.0.0/::0 and then having it join an interface to the group will "work", but I've seen several posts say that doing so may cause the socket to receive more UDP packets than the listener wants. Which I guess makes sense. Whereas many people say to bind the socket to the multicast group IP, but Microsoft says not to do that. <sigh> -
Not a good idea to send JSON in the query parameters. But, I suppose it is not forbidden either, so I should probably update TIdHTTPServer to handle that more cleanly, like sending a "414 URI Too Long " response instead of raising an exception. TIdHTTPServer already has a MaximumHeaderLineCount property (which raises an exception instead of sending a "431 Request Header Fields Too Large" response). I could add new MaximumUriLength and MaximumHeaderLineLength properties. I've opened a new ticket for these features: #474: Update TIdHTTPServer to handle long URIs and long request headers more cleanly OK
-
That would work for TIdHTTP, but for TIdHTTPServer you would need access to its OnConnect event in order to update each connected client's IOHandler.MaxLineLength, and AFAIK WebBroker does not expose access to that event. But, I don't have any experience with WebBroker, so who knows...
-
I suppose that is a possibility, but it is less common for a client to send a chunked request since that requires the client to have foreknowledge that the server actually supports HTTP 1.1 and chunked transfers before making the request.
-
TIdIpMcastClient - Group join on second interface issue
Remy Lebeau replied to FredM's topic in Indy
Makes me wonder if this is related to the following TIdIPMCastServer issue: #203: TIdIPMCastServer is missing a required call to setSockOpt for IPv6 In a nutshell, TIdIPMCastServer does not use the IPV6_MULTICAST_IF socket option to bind its outgoing sockets to any interface indexes. This seems to be required for sending, but is it also required for receiving? Indy does not currently support binding a socket to an interface index, only to an interface IP address. So, if binding to an index is needed in this case, you would have to manually perform SetSockOpt(IPV6_MULTICAST_IF) yourself, or maybe SetSockOpt(SO_BINDTODEVICE) instead. According to this post, it's also possible to bind a socket to an interface index by specifying the index as the Scope ID of the interface's IPv6 address, however Indy does not currently support scope IDs, either. -
How is your client sending the JSON request, exactly? What does the raw request look like? Indy's TIdHTTPServer (which TIdHTTPWebBrokerBridge uses internally) does not read body content line-by-line, only the request line and headers are read as lines, so you should not be getting a MaxLineLength error under normal conditions if the JSON is in the request body. Is the client sending the JSON in the URL query parameters instead, perhaps?
-
You are not supposed to use your secret keys as-is for the login token. You are supposed to use the keys to request an access token dynamically, and then use that token instead for login. NEVER give out your secret keys! Did you read the documentation I linked to earlier? Authenticate an IMAP, POP or SMTP connection using OAuth In particular, make sure you read the section titled "Get an access token". You are responsible for handling Steps 1 (which presumably you have already done) and Step 2 (the piece that is currently missing), and TIdIMAP4/TIdSASLXOuth2 will handle step 3 for you.
-
Official blog announcement: https://blogs.embarcadero.com/delphi-11-and-cbuilder-11-community-editions-released/
-
I do not know the exact GitHub commit that Embarcadero shipped, but it is in the 10.6.2 line. The initial Delphi 11.0 release was in September 2021, and for instance did not include a fix for this issue which was fixed in November 2021. I do not know whether or not Embarcadero has shipped newer Indy versions in Delphi 11.1-11.3. But you will definitely have an Indy version from the past couple of years at least.
-
No, you do not call the Authenticate methods directly. Yes, you need to assign a TIdUserPassProvider object to TIdSASLXOAuth2.UserProvider, and then assign the IMAP username to TIdUserPassProvider.Username, and either 1) assign the OAuth access token to TIdUserPassProvider.Password, or 2) set the TIdUserPassProvider.Password to a blank string and instead assign a handler to the TIdSASLXOAuth2.OnGetAccessToken event to return the access token. Then, simply set TIdIMAP4.AuthType to iatSASL, and call TIdIMAP4.Connect() with AAutoLogin=True (which is the default behavior), or call Connect() with AAutoLogin=False and then call TIdIMAP4.Login() when needed. You don't call them yourself at all. No to all of that. TIdIMAP4 and TIdSASLXOAuth2 will handle all of that detail internally for you. All you have to provide is the access token, as given to you by the OAuth provider. Just the OAuth access token. But it needs to be provided by TIdUserPassProvider.Password or TIdSASLXOAuth2.OnGetAccessToken, not by TIdIMAP4.Password. The TIdIMAP4.Username and TIdIMAP4.Password properties are used only when TIdIMAP4.AuthType=iatUserPass. Yes.
-
Out parameters can't be optional. You will have to overload the function instead, eg: function DoValidateEmail(const EmailAddress: string; ErrorMessage: PString): Boolean; begin ... if (not Result) and Assigned(ErrorMessage) then ErrorMessage^ := ...; ... end; function ValidEmail(const EmailAddress: string; out ErrorMessage: string): Boolean; overload; begin Result := DoValidateEmail(EmailAddress, @ErrorMessage); end; function ValidEmail(const EmailAddress: string): Boolean; overload; begin Result := DoValidateEmail(EmailAddress, nil); end; Alternatively: function ValidEmail(const EmailAddress: string; out ErrorMessage: string): Boolean; overload; begin ... if not Result then ErrorMessage := ...; ... end; function ValidEmail(const EmailAddress: string): Boolean; overload; var LDiscarded: string; begin Result := ValidEmail(EmailAddress, LDiscarded); end;
-
Can you be more specific about what "stuck in-limbo" actually means? Recreating the TIdHTTP and TIdSSLIOHandlerSocketOpenSSL objects would work fine. How does it "not help" exactly? If you don't want to recreate the objects, you should be able to just Disconnect() the TIdHTTP and Clear() its IOHandler.InputBuffer if it has any unread data in it before the timeout occurred. That won't make any difference. Besides, TIdHTTP should already be disconnecting the socket if a read error occurs. So really, you might only need to clear the InputBuffer. You should not use the Method and SSLVersions properties together. They are mutually exclusive, setting one updates the other. In fact, don't even use the Method property at all. You don't need to do that manually at all. TIdSSLIOHandlerSocketOpenSSL calls SSL_set_tlsext_host_name() internally for you, and has done so since 2016. 'utf-8' is not a valid value for the 'Content-Encoding' request header. You need to use the 'charset' attribute of the 'Content-Type' header instead (ie, the Request.Charset property). Also, you are not telling the TStringStream to use UTF-8, so it will use the OS default instead, which is ANSI on Windows and UTF-8 on Posix, so you will mismatch your JSON data if it ever contains non-ASCII characters. Use this instead: vJsonToSend := TStringStream.Create(vJsonToPost, TEncoding.UTF8); ... FHTTP.Request.ContentType := 'application/json'; FHTTP.Request.Charset := 'utf-8'; The correct way to detect a read timeout is to check if the caught exception is an instance of the EIdReadTimeout class. Don't rely on parsing the exception's Message: if E is EIdReadTimeout then For that matter, why would you want to recreate the objects only on a read timeout, and not on other errors?
-
If you want the arrays contiguous, then why not just use a single array? const itemSize = 65; numItems = 3; var items: array[0..(itemSize*numItems)-1] of AnsiChar; function item1: PAnsiChar; begin Result := @items[0]; end; function item2: PAnsiChar; begin Result := @items[itemSize*1]; end; function item3: PAnsiChar; begin Result := @items[itemSize*2]; end; Or: const itemSize = 65; numItems = 3; type itemType = array[0..itemSize-1] of AnsiChar; var items: array[0..numItems-1] of itemType; function item1: PAnsiChar; begin Result := @items[0][0]; end; function item2: PAnsiChar; begin Result := @items[1][0]; end; function item3: PAnsiChar; begin Result := @items[2][0]; end;
-
Outlook365 requires OAuth authentication: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth At this time, Indy does not have any components for retrieving an access token from an OAuth provider, so you will have to handle that yourself manually per the above documentation, such as with TIdHTTP. However, there is currently a sasl-oauth branch in Indy's GitHub repo (it hasn't been merged into the main code yet) which adds a few new TIdSASL components for sending OAuth access tokens for authentication in POP3, SMTP, IMAP, and DICT protocols. So, in this case, you could add the TIdSASLXOAuth2 component to the TIdIMAP4.SASLMechanisms property, and then use the OAuth access token as the password.