вівторок, 26 травня 2020 р.

QRCode\BarCode in your C#\Xamarin application

Below is really working C#\Xamarin code, tested. You'll be need to install ZXing.Mobile packet. I realised it by adding DependencyService to my project.

OK. Thanks to the posts above, I managed to get this working so I thought I'd post code for anyone having issues in the future. Remember, this is solely around the display of a barcode NOT the scanning!

I added ZXing.Net.Mobile to my Droid and iOS projects. I didn't need to add it to my core Forms project.

In my core Forms project I had an image in XAML:

<Image Grid.Row="13" Grid.ColumnSpan="2" Source="{Binding BarcodeImageSource}" HorizontalOptions="Center" WidthRequest="300" HeightRequest="130"/>

On my view model I had:

public ImageSource BarcodeImageSource { get;set;}
....
// this will set the image source for the BarcodeImageSource property which the Image in XAML will pick up 
var stream = this.barcodeService.ConvertImageStream(result.CardId);
this.BarcodeImageSource =  ImageSource.FromStream(() => {return stream; });

Ok that bit of code 'this.barcodeService.ConvertImageStream(result.CardId);' had to use dependency injection to have Droid and iOS specific code. The ZXing code is the same in both cases. It's actually the code for writing the bitmap to a stream that is platform specific.

So I have my interface:

using System;
using System.IO;

namespace MyApp.Services
{
  public interface IBarcodeService 
  {   
      Stream ConvertImageStream(string text, int width = 300, int height=130);    
  }
}

Then Android implementation:

using Android.Graphics;
using MyApp.Services
using System;
using System.IO;
using ZXing.Mobile;

namespace MyApp.Android
{
  public class BarcodeService : IBarcodeService
  {
      public Stream ConvertImageStream(string text,int width = 300, int height=130)
      {
          var barcodeWriter = new ZXing.Mobile.BarcodeWriter {
                      Format = ZXing.BarcodeFormat.CODE_39,
                      Options = new ZXing.Common.EncodingOptions {
                          Width = width,
                          Height = height,
                                              Margin = 10
                      }
                  };

          barcodeWriter.Renderer = new ZXing.Mobile.BitmapRenderer();
          var bitmap = barcodeWriter.Write(text);
          var stream = new MemoryStream();
          bitmap.Compress(Bitmap.CompressFormat.Png,100,stream);  // this is the diff between iOS and Android
          stream.Position = 0;
          return stream;
      }
  }
}

And the iOS implementation.

using System;
using System.Drawing;
using System.IO;
using Xamarin.Forms;
using MyApp.Services;
using UIKit;
using CoreGraphics;
using ZXing.Mobile;

namespace MyApp.iOS
{
  public class BarcodeService : IBarcodeService
  {
      public Stream ConvertImageStream(string text,int width = 300, int height=130)
      {
          var barcodeWriter = new ZXing.Mobile.BarcodeWriter {
                      Format = ZXing.BarcodeFormat.CODE_39,
                      Options = new ZXing.Common.EncodingOptions {
                          Width = width,
                          Height = height,
                                              Margin = 10
                      }
                  };
          barcodeWriter.Renderer = new ZXing.Mobile.BitmapRenderer();
          var bitmap = barcodeWriter.Write(text);
          var stream = bitmap.AsPNG().AsStream(); // this is the difference 
          stream.Position = 0;

          return stream;
      }
  }
}

понеділок, 4 травня 2020 р.

How to replace text in batch file

First part. What if you want to replace some text in your source code based on different settings (f.e., when CI uses)? I had a situation, when host and database were hardcoded by another person as consts and they have stay there. You may move consts into ini file (what I did some time later), but you also are able to parse your source file. Below I'll show how to do it using bat file.

So code below searches consts with the names SQL_SERVER_NAME and DATABASE_NAME and replaces them with the replace_SQL_SERVER_NAME and replace_DATABASE_NAME lines. Now you know, that this code has a limitation: it replaces whole lines. But it works, that it.

REM database.php update START
@echo off &setlocal
setlocal enableDelayedExpansion
:: Lines below has been searched
set "const=const"
set "SQL_SERVER_NAME=SQL_SERVER_NAME"
set "DATABASE_NAME=DATABASE_NAME"
:: Lines below has been replaced with the "search_xxx"
:: !!!-------------------- UPDATE VARIABLES BELOW --------------------!!!
set "replace_SQL_SERVER_NAME= const SQL_SERVER_NAME = "SERVER\NEW_SERVER_NAME";"
set "replace_DATABASE_NAME= const DATABASE_NAME = "NEW_DATABASE_NAME";"
:: !!!-------------------- UPDATE VARIABLES ABOVE --------------------!!!
:: File to be processed
set "textfile=database.php"
:: Temporary file
set "newfile=database_tmp.php"
:: Define LF to contain a linefeed character
set "signExclamation=^!"
:: Define LF to contain a linefeed character
set ^"LF=^

^"
:: The empty line above is critical
::(for /f "delims=" %%i in (%textfile%) do (
(for /f "USEBACKQ tokens=1,2 delims=^!^" %%i in (`type %textfile% ^| find /V /N ""`) do (
    set "part1=%%i"
 set "part2=%%j"
 :: Save ! sign
 if "!part2!"=="" (
  set "line=!part1!!part2!"
 ) else (
  set "line=!part1!!signExclamation!!part2!"
 )

 :: Save an empty lines
 set "line=!line:*]=!"

 set "str1="
 set "str2="
 :: Left trim the line to compare with the "search"
 for /f "tokens=1,2 delims=  " %%a in ("!line!") do (
  set "str1=%%a"
  set "str2=%%b"
 )

 :: Replace consts
 set "str=!line!"
 if "!str1!"=="!const!" (
  if "!str2!"=="!SQL_SERVER_NAME!" (
   set "str=!replace_SQL_SERVER_NAME!"
  ) else (
   if "!str2!"=="!DATABASE_NAME!" (
    set "str=!replace_DATABASE_NAME!"
   )
  )
 )

    echo(!str!)
)>"%newfile%"
del %textfile%
rename %newfile%  %textfile%
endlocal
@echo on
REM database.php update END

You are welcome to test code above. Just create database.php file, add there 2 lines:
 const SQL_SERVER_NAME = "OLD_SERVER_NAME";
 const DATABASE_NAME = "OLD_DATABASE_NAME";
Create *.bat file in the same folder and run it.

Second part. What if you want to insert some code into your f.e. web.config file in production?

REM web.config update START
@echo off &setlocal
setlocal enableDelayedExpansion
:: Line below has been searched
set "search=</system.webServer>"
:: Line below has been added before the "search"
set "replace=        <urlCompression doStaticCompression="false" doDynamicCompression="false" />"
:: File to be processed
set "textfile=web.config"
:: Temporary file
set "newfile=web_tmp.config"
:: Define LF to contain a linefeed character
set ^"LF=^

^"
:: The empty line above is critical
(for /f "delims=" %%i in (%textfile%) do (
    set "line=%%i"
 :: Left trim the line to compare with the "search"
 for /f "tokens=* delims= " %%a in ("!line!") do set "str=%%a"
 :: Add line before the "search"
 if "!str!"=="!search!" (
  set "str=!replace!!LF!"
 ) else (
  set "str="
 )
    echo(!str!!line!))>"%newfile%"
del %textfile%
rename %newfile%  %textfile%
endlocal
@echo on
REM web.config update END

Well, it just finds search in in your web.config file and inserts replace before it. You know how to test.

вівторок, 18 лютого 2020 р.

Delphi - Close inactive sessions in DataSnap

There is something wrong with DataSnap at least in Delphi 10 or I did something wrong. Anyway, when client application becomes to be frozen (f.e. long time running SQL query or becomes to be frozen somehow in another way or client application did not ask anything from the DataSnap for awhile), DatSnap stopped to create new instance for other client, after DataSnap ran TDSSessionManager.Instance.CloseSession(ASession.SessionName);.

Controls used in DataSnap server side application: DSServer and DSHTTPService. Client application communicates via http(s).

In Server side DataModuleCreate set how long session will live in an inactive state:

  TDSSessionManager.Instance.AddSessionEvent(
    procedure(Sender: TObject;
              const EventType: TDSSessionEventType;
              const Session: TDSSession)
    begin
      case EventType of
        {The provided Session was just created.}
        SessionCreate: Session.LifeDuration := 10000000; // less than 3 hours
        {The provided Session has just been closed, either intentionally or it has expired.}
        SessionClose: ;
      end;
    end);

Define how to mark that session is active:
function <>.DSServerTrace(TraceInfo: TDBXTraceInfo): CBRType;
begin
  if TraceInfo.TraceFlag in [TDBXTraceFlags.Execute, TDBXTraceFlags.Command, TDBXTraceFlags.Transact, TDBXTraceFlags.Reader, TDBXTraceFlags.Driver, TDBXTraceFlags.Custom] then // mark session as an active
    TDSSessionManager.Instance.GetThreadSession.MarkActivity;

  Result := cbrUSEDEF;
end;

In DSServerConnect(DSConnectEventObject: TDSConnectEventObject); run procedure below:
procedure clearInactiveSessions();
  var
    sl: TStringList;
    _Session: TDSSession;
    i: Integer;
  begin
    sl := TStringList.Create;
    try
      TDSSessionManager.Instance.ForEachSession(
        procedure(const ASession: TDSSession)
        begin
            // different session
            if (ASession.SessionName <> TDSSessionManager.Instance.GetThreadSession.SessionName)
              and(
                (not ASession.IsValid)
                or
                (ASession.ElapsedSinceLastActvity > UINT(ASession.LifeDuration))
              )
            then
            begin
              //TDSSessionManager.Instance.CloseSession(ASession.SessionName); // ATTENTION: Causes a freezing of application
              TDBXScheduler.Instance.CancelEvent(ASession.Id);
              // add removed session from the SessionManager to the list
              sl.AddObject(ASession.SessionName, TDSSessionManager.Instance.RemoveSession(ASession.SessionName));
            end;
        end
        );

        for i := 0 to sl.Count - 1 do
        begin
          _Session := TDSSession(sl.Objects[i]);
          if Assigned(_Session) then
            FreeAndNil(_Session);
        end;
    finally
      FreeAndNil(sl);
    end;
  end;

So above procedure shall be ran, when new client trying to connect to the DataSnap.

Normally, TDSSessionManager.Instance.CloseSession(ASession.SessionName); should work fine, but for some reason did not.

As result, frozen or inactive client application did not get any respond that its connection was closed, but session becomes properly closed. Client can kill its process and DataSnap still continue working.

Probably in Delphi 10.3.x it is fixed, will check somewhen.

середа, 12 лютого 2020 р.

Docker

1. Show docker settings.

docker info

- Play with Dicker

1. Put "C:\ProgramData\Docker" on a different drive.

To save free space on your system hard drive, just create/modify the C:\ProgramData\Docker\config\daemon.json file as referenced in the getting started guide here.

{
"graph": "F:\\ProgramData\\Docker"
}

This will put all your images, layers and containers in a different location but leave the config in its original location.
Source

2. Install Microsoft SQL Server.

3. Run SQL Server.

docker run -d -p <HostEnvironmentIP>:<InContainerIP> -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=<YourPassword>" --name sql1 microsoft/mssql-server-windows-express