File: syncgui.pas

package info (click to toggle)
tomboy-ng 0.42-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,872 kB
  • sloc: pascal: 56,089; sh: 1,571; makefile: 63; xml: 23
file content (538 lines) | stat: -rw-r--r-- 21,496 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
unit SyncGUI;
{   Copyright (C) 2017-2024 David Bannon

    License:
    This code is licensed under MIT License, see the file License.txt
    or https://spdx.org/licenses/MIT.html  SPDX short identifier: MIT

    ------------------

}

{	History
	2017/12/06	Marked FileSync debug mode off to quieten console output a little
	2017/12/30  Changed above DebugMode to VerboseMode
	2017/12/30  We now call IndexNotes() after a sync. Potentially slow.
	2017/12/30  Added a seperate procedure to do manual Sync, its called
				by a timer to ensure we can see dialog before it starts.
	2018/01/01  Added ID in sync report to make it easier to track errors.
	2018/01/01  Set goThumbTracking true so contents of scroll box glide past as
    			you move the "Thumb Slide".
	2018/01/01  Changed ModalResult for cancel button to mrCancel
	2018/01/08  Tidied up message box text displayed when a sync conflict happens.
	2018/01/25  Changes to support Notebooks
    2018/01/04  Forced a screen update before manual sync so user knows whats happening.
    2018/04/12  Added ability to call MarkNoteReadOnly() to cover case where user has unchanged
                note open while sync process downloads or deletes that note from disk.
    2018/04/13  Taught MarkNoteReadOnly() to also delete ref in NoteLister to a sync deleted note
    2018/05/12  Extensive changes - MainUnit is now just that. Only change here relates
                to naming of MainUnit and SearchUnit.
    2018/05/21  Show any sync errors as hints in the StringGrid.
    2018/06/02  Honor a cli --debug-sync
    2018/06/14  Update labels when transitioning from Testing Sync to Manual Sync
    2018/08/14  Added SDiff to replace clumbsy dialog when sync clash happens.
    2018/08/18  Improved test/reporting of file access during sync
    2018/10/25  New sync model. Much testing, support for Tomdroid.
    2018/10/28  Much tweaking and bug fixing.
    2018/10/29  Tell TB_Sdiff about note title before showing it.
    2018/10/30  Don't show SyNothing in sync report
    2018/11/04  Added support to update in memory NoteList after a sync.
    2019/05/19  Display strings all (?) moved to resourcestrings
    2020/02/20  Added capability to sync without showing GUI.
    2020/06/18  Only show good sync notification for 3 seconds
    2020/08/07  Changed the stringGrid to a ListView 'cos it handles dark themes better.
    2020/08/10  ListView becomes type=vsReport
    2020/04/26  Set Save button to disabled immediatly when pressed.
    2021/09/08  Added progress indicator
    2022/04/15  Improve man sync close button communication with user.
}

{$mode objfpc}{$H+}




interface

uses
		Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
		StdCtrls, {Grids,} ComCtrls, Syncutils;

type

		{ TFormSync }

  TFormSync = class(TForm)
				ButtonSave: TButton;
				ButtonCancel: TButton;
				ButtonClose: TButton;
				Label1: TLabel;
				Label2: TLabel;
                LabelProgress: TLabel;
                ListViewReport: TListView;      // Viewstyle=vsReport, make columns in Object Inspector
				Memo1: TMemo;
				Panel1: TPanel;
				Panel2: TPanel;
				Panel3: TPanel;
				Splitter3: TSplitter;
                                        { Runs a sync without showing form. Ret False if error or its not setup.
                                          Caller must ensure that Sync is config and that the Sync dir is available.
                                          If clash, user will see dialog. }
                procedure FormCreate(Sender: TObject);
//                function RunSyncHidden() : boolean;
				procedure ButtonCancelClick(Sender: TObject);
				procedure ButtonCloseClick(Sender: TObject);
    			procedure ButtonSaveClick(Sender: TObject);
				procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
                procedure FormHide(Sender: TObject);
                { At Show, depending on SetUpSync, we'll either go ahead and do it, any
                  error is fatal or, if True, walk user through process. }
				procedure FormShow(Sender: TObject);
		private
                FormShown : boolean;
                LocalTimer : TTimer;

                procedure AddLVItem(Act, Title, ID: string);
                procedure AdjustNoteList();
                procedure AfterShown(Sender : TObject);
                    // Display a summary of sync actions to user.
                function DisplaySync(): string;
                    { Called when user wants to join a (possibly uninitialised) Repo,
                      will handle some problems with user's help. }
                procedure JoinSync;
                    { Called to do a sync assuming its all setup. Any problem is fatal }
                function ManualSync: boolean;
                    { Populates the string grid with details of notes to be actioned }
                procedure ShowReport;

                                    { We will pass address of this method to lower level units so
                                    they can report on progress. Short one to three words ?  }
                procedure SyncProgress(const St: string);

		public
                Busy : boolean; // indicates that there is some sort of sync in process now.
                AnotherSync : boolean;  // After this (manual) sync, we have another one (ie github)
                Transport : TSyncTransPort;
                UserName, Password : string;    // For those thatnsports that need such things.
                LocalConfig, NoteDirectory : ANSIString;
                    { Indicates we are doing a setup User has already agreed to abandon any
                      existing Repo but we don't know if indicated spot already contains a
                      repo or, maybe we want to make one. }
              	SetupSync : boolean;
                    { we will pass address of this function to Sync }
                function Proceed(const ClashRec : TClashRecord) : TSyncAction;
		end;

var
		FormSync: TFormSync;

implementation

{ In SetupFileSync mode, does a superficial, non writing, test OnShow() user
  can then click 'OK' and we'd do a real sync, exit and settings saved in calling
  process.
}

uses LazLogger, SearchUnit, TB_SDiff, Sync,  LCLType, {SyncError,} ResourceStr,
        {notifier,} Settings, MainUnit { tb_utils};

{$R *.lfm}

var
        ASync : TSync;
{ TFormSync }


function TFormSync.Proceed(const ClashRec : TClashRecord) : TSyncAction;
var
    SDiff : TFormSDiff;
begin
    SDiff := TFormSDiff.Create(self);
    SDiff.RemoteFilename := ClashRec.ServerFileName;
    SDiff.LocalFilename := ClashRec.LocalFileName;
    SDiff.NoteTitle := ClashRec.Title;
    case SDiff.ShowModal of
            mrYes      : Result := SyDownLoad;
            mrNo       : Result := SyUpLoadEdit;
            mrNoToAll  : Result := SyAllLocal;
            mrYesToAll : Result := SyAllRemote;
            mrAll      : Result := SyAllNewest;
            mrClose    : Result := SyAllOldest;
    otherwise
            Result := SyUnSet;      // Thats an ERROR !  What are you doing about it ?
    end;
    SDiff.Free;
    Application.ProcessMessages;    // so dialog goes away while remainder are being processed.
    // Use Remote, Yellow is mrYes, File1
    // Use Local, Aqua is mrNo, File2
end;

procedure TFormSync.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
	FreeandNil(ASync);
    Busy := False;
end;

procedure TFormSync.FormHide(Sender: TObject);
begin
    if LocalTimer = Nil then exit();
    LocalTimer.Free;
    LocalTimer := nil;
end;

procedure TFormSync.SyncProgress(const St: string);
begin
        LabelProgress.Caption := St;
        Application.ProcessMessages;
end;

// Following resourcestrings defined in syncUtils.pas

function TFormSync.DisplaySync(): string;
var
    UpNew, UpEdit, Down, DelLoc, DelRem, Clash, DoNothing, Errors : integer;
    procedure Publish(Msg : string; Numb : integer);
    begin
        while length(Msg) < 18 do Msg := Msg + ' ';
        Memo1.Append(Msg + ' ' + inttostr(Numb));
    end;

begin
    Memo1.Font.Name := Sett.FixedFont;
    ASync.ReportMetaData(UpNew, UpEdit, Down, DelLoc, DelRem, Clash, DoNothing, Errors);
    Publish(rsNewUploads, UpNew);
    Publish(rsEditUploads, UpEdit);
    Publish(rsDownloads, Down);
    Publish(rsLocalDeletes, DelLoc);
    Publish(rsRemoteDeletes, DelRem);
    Publish(rsClashes, Clash);
    Publish(rsDoNothing, DoNothing);
//    Memo1.Append(rsNewUploads + inttostr(UpNew));
//    Memo1.Append(rsEditUploads + inttostr(UpEdit));
//    Memo1.Append(rsDownloads + inttostr(Down));
//    Memo1.Append(rsLocalDeletes + inttostr(DelLoc));
//    Memo1.Append(rsRemoteDeletes + inttostr(DelRem));
//    Memo1.Append(rsClashes + inttostr(Clash));
//    Memo1.Append(rsDoNothing + inttostr(DoNothing));
    if Errors > 0 then
        Publish(rsSyncERRORS, Errors);
//        Memo1.Append(rsSyncERRORS + inttostr(Errors));
    result := 'Uploads=' + inttostr(UpNew+UpEdit) + ' downloads=' + inttostr(Down) + ' deletes=' + inttostr(DelLoc + DelRem);
    // debugln('Display Sync called, DoNothings is ' + inttostr(DoNothing));
end;

    // User is only allowed to press Cancel or Save when this is finished.
procedure TFormSync.JoinSync;
var
    SyncAvail : TSyncAvailable;
begin
    freeandnil(ASync);
    ASync := TSync.Create;
    Label1.Caption   :=  SyncTransportName(Transport) + ' ' + rsTestingRepo;
    Application.ProcessMessages;
    ASync.ProceedFunction:= @Proceed;
    ASync.DebugMode  := Application.HasOption('s', 'debug-sync');
    ASync.NotesDir   := NoteDirectory;
    ASync.ConfigDir  := LocalConfig;
    ASync.ProgressProcedure := @SyncProgress;
    ASync.Password   := Sett.LabelToken.Caption;              // better find a better way to do this Davo
    Async.UserName   := Sett.EditUserName.text;
    ASync.RepoAction := RepoJoin;

    if (Async.SetTransport(TransPort) = SyncNetworkError) then begin
        showmessage(rsUnableToProceed + ' ' + rsNetworkNotAvailable);
        ModalResult := mrCancel;
    end;
    SyncAvail        := ASync.TestConnection();
    if SyncAvail = SyncNoRemoteRepo then
        if mrYes = QuestionDlg('Advice', rsCreateNewRepo, mtConfirmation, [mrYes, mrNo], 0) then begin
            ASync.RepoAction:=RepoNew;
            SyncAvail := ASync.TestConnection();
        end;
    if SyncAvail <> SyncReady then begin
        showmessage(rsUnableToProceed + ' ' + ASync.ErrorString);
        ModalResult := mrCancel;
    end;
    Label1.Caption :=  SyncTransportName(Transport) + ' ' + rsLookingatNotes;
    Application.ProcessMessages;
    ASync.TestRun := True;
    ASync.GetSyncData();                    // does not return anything interesting here. Does in Auto mode however.
//    if  ASync.UseSyncData() then begin  // ToDo : am I dealing with TestRun correctly ?
        DisplaySync();
        ShowReport();
        Label1.Caption :=  SyncTransportName(Transport) + ' ' + rsLookingatNotes;
        Label2.Caption := rsSaveAndSync;
        ButtonSave.Enabled := True;
//    end  else
//        Showmessage(rsSyncError + ' ' + ASync.ErrorString);
    ButtonCancel.Enabled := True;
end;

procedure TFormSync.AfterShown(Sender : TObject);
begin
    LocalTimer.Enabled := False;             // Don't want to hear from you again
    if SetUpSync then begin
        JoinSync();
    end else
        ManualSync();
end;

//RESOURCESTRING
//  rsPleaseWait = 'Please wait a minute or two ...';

procedure TFormSync.FormShow(Sender: TObject);
begin
    if Application.HasOption('debug-sync') then
        debugln(#10#10' ============= TFormSync.FormShow =============');
    Busy := True;
    LabelProgress.Caption := '';
    Left := 55 + random(55);
    Top := 55 + random(55);
    FormShown := False;
    Label2.Caption := rsNextBitSlow;
    Memo1.Clear;
    ListViewReport.Clear;
    ButtonSave.Enabled := False;
    ButtonClose.Enabled := False;
    ButtonCancel.Enabled := False;
    {$ifdef windows}  // linux apps know how to do this themselves
    if Sett.DarkTheme then begin
         // Sett.BackGndColour;  Sett.TextColour;
         ListViewReport.Color :=       clnavy;
         ListViewReport.Font.Color :=  Sett.HiColour;
         splitter3.Color:= clnavy;
         Panel1.color := Sett.BackGndColour;
         Panel2.color := Sett.BackGndColour;
         Panel3.color := Sett.BackGndColour;
         Label1.Font.Color:= Sett.TextColour;
         Label2.Font.Color := Sett.TextColour;
         Memo1.Color:= Sett.BackGndColour;
         Memo1.Font.Color := Sett.TextColour;
         ButtonCancel.Color := Sett.HiColour;
         ButtonClose.Color := Sett.HiColour;
         ButtonSave.Color := Sett.HiColour;
    end;
    {$endif}
    // We call a timer to get out of OnShow so ProcessMessages works as expected
    LocalTimer := TTimer.Create(Nil);
    LocalTimer.OnTimer:= @AfterShown;
    LocalTimer.Interval:=500;
    LocalTimer.Enabled := True;
end;

(* function TFormSync.RunSyncHidden(): boolean;          // ToDo : this is now done in Settings, remove ???
begin
    //debugln('In RunSyncHidden');
    if SetUpSync then exit(False);      // should never call this in setup mode but to be sure ...
    busy := true;
    ListViewReport.Clear;
    // Result := ManualSync();
    ASync := TSync.Create;
    try
        ASync.AutoSetUp(Transport);             // ToDo : must determine what mode of Sync we are running in.
        ASync.GetSyncData();
        ASync.UseSyncData();
    finally
        ASync.Free;
        Busy := False;
    end;
end; *)

procedure TFormSync.FormCreate(Sender: TObject);
begin
    UserName := '';
    Password := '';
end;

        // User is only allowed to press Close when this is finished.
function TFormSync.ManualSync : boolean;
var
    //SyncState : TSyncAvailable = SyncNotYet;
    //Notifier : TNotifier;
    SyncSummary : string;
    SyncAvail : TSyncAvailable;
begin
    Label1.Caption :=  SyncTransportName(Transport) + ' ' + rsTestingSync;
    Application.ProcessMessages;
	ASync := TSync.Create;
    try
        //Screen.Cursor := crHourGlass;
        //sleep(1000);
        ASync.ProceedFunction := @Proceed;
        ASync.ProgressProcedure := @SyncProgress;
        ASync.DebugMode := Application.HasOption('s', 'debug-sync');
	    ASync.NotesDir:= NoteDirectory;
	    ASync.ConfigDir := LocalConfig;
        ASync.RepoAction:= RepoUse;
        ASync.Password := Sett.LabelToken.Caption;              // better find a better way to do this Davo
        Async.UserName := Sett.EditUserName.text;

        if Async.SetTransport(TransPort) = SyncNetworkError then begin
            if not Visible then begin
                SearchForm.UpdateStatusBar(1, rsAutoSyncNotPossible);
                if Sett.CheckNotifications.checked then begin
                    MainForm.ShowNotification(rsAutoSyncNotPossible);
                end;
                exit(false);
            end else begin                                                      // busy unset in finally clause
                showmessage('Unable to sync because ' + ASync.ErrorString);
                FormSync.ModalResult := mrAbort;
                exit(false);                                                    // busy unset in finally clause
            end;
        end;


//debugln({$I %FILE%}, ', ', {$I %CURRENTROUTINE%}, '(), line:', {$I %LINE%}, ' : Testing Connection.');
        SyncAvail := ASync.TestConnection();
        if SyncAvail <> SyncReady then begin
            debugln({$I %FILE%}, ', ', {$I %CURRENTROUTINE%}, '(), line:', {$I %LINE%}, ' : '
                    , 'Test Transport Failed, ' + SyncAvailableString(SyncAvail));
            if ASync.DebugMode then debugln('Failed testConnection');
            // in autosync mode, form is not visible, we just send a notify that cannot sync right now.and return false
            if not Visible then begin
                SearchForm.UpdateStatusBar(1, rsAutoSyncNotPossible);
                if Sett.CheckNotifications.checked then begin
                    MainForm.ShowNotification(rsAutoSyncNotPossible);
                end;
                exit(false);
            end else begin                                                      // busy unset in finally clause
                //Screen.Cursor := crDefault;
                showmessage('Unable to sync because ' + ASync.ErrorString);
                //Screen.Cursor := crHourGlass;
//                if AnotherSync then
                    FormSync.ModalResult := mrAbort;
{                else begin
                    Label2.Caption := rsPressClose;
                    ButtonClose.Enabled := True;
                end;  }
                exit(false);                                                    // busy unset in finally clause
            end;
            exit(false);        //redundant ?
        end;
//debugln({$I %FILE%}, ', ', {$I %CURRENTROUTINE%}, '(), line:', {$I %LINE%}, ' : ', 'Transport good, about to run.');
        Label1.Caption :=  SyncTransportName(Transport) + ' ' + rsRunningSync;
        Application.ProcessMessages;
        ASync.TestRun := False;
        if ASync.GetSyncData() and ASync.UseSyncData() then;   // ToDo : should I evaluate separetly ?

        SyncSummary :=  DisplaySync();
        SearchForm.UpdateStatusBar(1, rsLastSync + ' ' + FormatDateTime('YYYY-MM-DD hh:mm', now)  + ' ' + SyncSummary);
        if (not Visible) and Sett.CheckNotifications.Checked then begin
            MainForm.ShowNotification(rsLastSync  + ' ' + SyncSummary, 2000);
        end;
        ShowReport();
        AdjustNoteList();                              // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Label1.Caption :=  SyncTransportName(Transport) + ' ' + rsAllDone;
        Label2.Caption := rsPressClose;
        ButtonClose.Enabled := True;
        if AnotherSync then begin
            ButtonClose.Hint := 'proceed to next sync';
            ButtonClose.ShowHint := True;
        end else
            ButtonClose.ShowHint := False;
        Result := True;
    finally
        FreeandNil(ASync);
        Busy := False;
        //Screen.Cursor := crDefault;
    end;
end;

procedure TFormSync.AdjustNoteList();
var
    DeletedList, DownList : TStringList;
    Index : integer;
begin
    DeletedList := TStringList.Create;
    DownList := TStringList.Create;
 	with ASync.RemoteMetaData do begin
		for Index := 0 to Count -1 do begin
            if Items[Index]^.Action = SyDeleteLocal then
                DeletedList.Add(Items[Index]^.ID);
            if Items[Index]^.Action = SyDownload then
                DownList.Add(Items[Index]^.ID);
        end;
    end;
    if (DeletedList.Count > 0) or (DownList.Count > 0) then
        SearchForm.ProcessSyncUpdates(DeletedList, DownList);
    FreeandNil(DeletedList);
    FreeandNil(DownList);
end;

procedure TFormSync.AddLVItem(Act, Title, ID : string);
var
    TheItem : TListItem;
begin
   TheItem := ListViewReport.Items.Add;
   TheItem.Caption := Act;
   TheItem.SubItems.Add(copy(Title, 1, 25)+' ');
   TheItem.SubItems.Add(ID);
end;

procedure TFormSync.ShowReport;
var
    Index : integer;
    Rows : integer = 0;
begin
    with ASync.RemoteMetaData do begin
	    for Index := 0 to Count -1 do begin
	        if Items[Index]^.Action <> SyNothing then begin
	                AddLVItem(
	                    ASync.RemoteMetaData.ActionName(Items[Index]^.Action)
	                    , Items[Index]^.Title
	                    , Items[Index]^.ID);
	                inc(Rows);
	        end;
	    end
	end;
	if  Rows = 0 then
	    Memo1.Append(rsNoNotesNeededSync)
	else Memo1.Append(inttostr(ASync.RemoteMetaData.Count) + rsNotesWereDealt);
    if ASync.TransMode = SyncGitHub then begin
        Memo1.Append('Token expires : ' + ASync.TokenExpire);
        Sett.LabelToken.Hint := 'Expires ' + ASync.TokenExpire;
    end;
    {$IFDEF DARWIN}     // Apparently ListView.columns[n].autosize does not work in Mac, this is rough but better then nothing.
    ListViewReport.Columns[0].Width := listviewReport.Canvas.Font.GetTextWidth('upload edit ');
    ListViewReport.Columns[1].Width := ListViewReport.Columns[0].Width *2;
    {$ENDIF}
end;


procedure TFormSync.ButtonCancelClick(Sender: TObject);
begin
    ModalResult := mrCancel;
end;

procedure TFormSync.ButtonCloseClick(Sender: TObject);
begin
	ModalResult := mrOK;
end;

    // This only ever happens during a Join, RepoAction will still be 'join'.
procedure TFormSync.ButtonSaveClick(Sender: TObject);
begin
    Label2.Caption:=rsNextBitSlow;
    Label1.Caption :=  SyncTransportName(Transport) + ' ' + 'First Time Sync';
    Memo1.Clear;
    ButtonCancel.Enabled := False;
    ButtonSave.Enabled := False;
    Application.ProcessMessages;
    ASync.TestRun := False;
    if ASync.GetSyncData() and ASync.UseSyncData() then begin  // ToDo : should I evaluate separetly ?
        SearchForm.UpdateStatusBar(1, rsLastSync + ' ' + FormatDateTime('YYYY-MM-DD hh:mm', now)  + ' ' + DisplaySync());
        ShowReport();
        AdjustNoteList();
        Label1.Caption :=  SyncTransportName(Transport) + ' ' + rsAllDone;
        Label2.Caption := rsPressClose;
        if ASync.TransMode = SyncGithub then begin
            Sett.LabelSyncRepo.Caption := ASync.GetTransRemoteAddress;   // this relies on Sett being in Github mode, during a Join, should be ....
            Sett.LabelToken.Hint := 'Expires ' + ASync.TokenExpire;
        end;
    end  else
        Showmessage(rsSyncError + ASync.ErrorString);
    ButtonClose.Enabled := True;
end;

end.