File: cli.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 (336 lines) | stat: -rw-r--r-- 14,273 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
unit cli;

{$mode objfpc}{$H+}

{   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

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

    This unit is active before the GUI section and may decide GUI is not needed.
    Handles command line switches, imports and comms with an existing instance.

    History
    2020/06/18  Remove unnecessary debug line.
    2021/12/28  Tidy LongOpts model.
    2022/01/13  When importing file, don't check for its existance, importer will do that
    2022/01/13  When importing note, check if FFileName starts with '~'
    2022/04/07  Tidy up options.
    2022/05/03  Add unix username to IPC pipe.
    2022/10/18  Short switch for import MD is -m
    2022/10/20  Do Import from this instance with direct call to IndexNewNote().
    2024/06/01  Changed cli switch --kde-leftclick to allow-leftclick as useful in gnome too.
}


{   Note that for Qt app, the command line can also include a option meant for Qt.
    See https://doc.qt.io/qt-5/qguiapplication.html#supported-command-line-options
    It appears that Qt sees such an option before this unit does AND it removes it
    and also removes anything after it ! But does act on it. Seems both - and -- acceptable -
        --platformtheme qt5ct
        -platformtheme qt5ct
    And a typo in the option value is ignored.
    Use a space as seperator, not '=' !
}

interface

uses
    Classes, SysUtils, Dialogs;

function ContinueToGUI() : boolean ;
                            { Will take offered file name, report some error else checks its a  .note,
                            saves it to normal repo and requests a running instance (if there is one) to
                            reindex as necessary. Note that in CLI mode, we do not check for a Title Clash
                            (because NoteLister may not be running. In GUI mode, existing Title is checked.}
function Import_Note(FFName : string = '') : string;
                            { Will take offered file name, report error else converts that file to .note,
                            saves it to normal repo and requests a running instance (if there is one) to
                            reindex as necessary. Note that in CLI mode, we do not check for a Title Clash
                            (because NoteLister may not be running. In GUI mode, existing Title is checked.}
function Import_Text_MD_File(MD : boolean; FFName : string = '') : string;

var
    SingleNoteName : string = '';    // other unit will want to know.....
    OpenNewNotePlease : boolean = false;


const Version_string  = {$I %TOMBOY_NG_VER};

implementation



uses Forms, LCLProc, LazFileUtils, FileUtil, ResourceStr, simpleipc, IniFiles,
    import_notes, tb_utils;

type
  ENoNotesRepoException = class(Exception);

var
    LongOpts : TStringArray;          // See initialization section

const
    ShortOpts = 'hgo:l:vt:m:n:c';  // help gnome3 open lang ver import-[txt md note]

                { If something on commandline means don't proceed, ret True }
function CommandLineError(inCommingError : string = '') : boolean;
var
    ErrorMsg : string;
begin
    ErrorMsg := InCommingError;
    Result := false;
    if ErrorMsg = '' then begin
        ErrorMsg := Application.CheckOptions(ShortOpts, LongOpts);
        if Application.HasOption('h', 'help') then
            ErrorMsg := 'Usage -';
    end;
    if ErrorMsg <> '' then begin
        DebugLn(ErrorMsg);
       {$ifdef DARWIN}
       debugln(rsMachelp1);
       debugln(rsMacHelp2);
       {$endif}
       debugln('   --dark-theme                  ' + 'Does not work for GTK2');
       debugln('   -l --lang=CCode               ' + rsHelpLang);    // syntax depends on bugfix https://bugs.freepascal.org/view.php?id=35432
       debugln('   -h --help                     ' + rsHelpHelp);
       debugln('   -c                            ' + rsHelpCreateNew);
       debugln('   --version                     ' + rsHelpVersion);
       debugln('   --no-splash                   ' + rsHelpNoSplash);
       debugln('   --debug-sync                  ' + rsHelpDebugSync);
       debugln('   --debug-index                 ' + rsHelpDebugIndex);
       debugln('   --debug-spell                 ' + rsHelpDebugSpell);
       debugln('   --config-dir=PATH_to_DIR      ' + rsHelpConfig);
       debugln('   --open-note=PATH_to_NOTE      ' + rsHelpSingleNote);
       debugln('   --debug-log=SOME.LOG          ' + rsHelpDebug);
       // debugln('   --save-exit                ' + rsHelpSaveExit);    // legacy but still allowed.
       debugln('   --import-txt=PATH_to_FILE     ' + rsHelpImportFile + '  also -t');
       debugln('   --import-md=PATH_to_FILE      ' + rsHelpImportFile + '  also -m');
       debugln('   --import-note=PATH_to_NOTE    ' + rsHelpImportFile + '  also -n');
       debugln('   --title-fname                 ' + rsHelpTitleISFName);
       {$ifdef LCLgtk2}
       debugln('   --useappind=yes|no            ' + rsParticularSysTray);
       {$endif}
       {$ifdef LINUX}
       debugln('   --allow-leftclick             ' + rsAllowLeftClick);
       {$endif}
       {$if defined(LCLQT5) or defined(LCLQt6)}
       debugln('   --strict-theme                ' + rsStrictThemeColors);
       debugln('   -platform xcb                 ' + rsBypassWayland);
       debugln('   -platformtheme gnome|gtk2|qt5ct|qt6ct  ' + rsSelectColors);
       {$endif}
       result := true;
    end;
end;

                        { Ret T if we have ONE or more command line Paramaters, not to be confused with
                          a Option, a parameter has no '-'.  Because the only parameter we expect is SingleNoteFileName,
                          we also honour -o --open-note.  More than one such parameter is an error, report to console,
                          ret true but set SingleFileName to ''. }
function HaveCMDParam() : boolean;
var
    Params : TStringArray;
begin
    Result := False;
    if Application.HasOption('o', 'open-note') then begin
       SingleNoteName := Application.GetOptionValue('o', 'open-note');
       exit(True);
    end;
    Params := Application.GetNonOptions(ShortOpts, LongOpts);
    if length(Params) = 1 then begin
        if Params[0] <> '%f' then begin   // MX Linux passes the %f from desktop file during autostart
                SingleNoteName := Params[0];
                exit(True);
        end;
    end;
    if length(Params) > 1 then begin
        CommandLineError('Unrecognised parameters on command line');
        SingleNoteName := '';
        exit(True);
    end;
end;

// Looks for server, if present, sends indicated message and returns true, else false.
function CanSendMessage(Msg : string) : boolean;
var
    CommsClient : TSimpleIPCClient;
begin
    Result := False;
//    if TheReindexProc = nil then begin
        try
            CommsClient  := TSimpleIPCClient.Create(Nil);
            CommsClient.ServerID:='tomboy-ng' {$ifdef UNIX} + '-' + GetEnvironmentVariable('USER'){$endif}; // on multiuser system, unique
            if CommsClient.ServerRunning then begin
                CommsClient.Active := true;
                CommsClient.SendStringMessage(Msg);
                CommsClient.Active := false;
                Result := True;
            end;
        finally
            freeandnil(CommsClient);
        end;
//    end else TheReindexProc();
end;

                        { Returns the absolute notes dir, raises ENoNotesRepoException if it cannot find it }
function GetNotesDir() : string;
    var
       ConfigFile : TIniFile;
begin
     // TiniFile does not care it it does not find the config file, just returns default values.
     ConfigFile :=  TINIFile.Create(TB_GetDefaultConfigDir + 'tomboy-ng.cfg');
     try
        result := ConfigFile.readstring('BasicSettings', 'NotesPath', '');      // MUST be mentioned in config file
     finally
         ConfigFile.Free;
     end;
     if Result = '' then
         Raise ENoNotesRepoException.create('tomboy-ng does not appear to be configured');
end;

function Import_Note(FFName : string = '') : string;
var
    FFileName, Fname : string;
    GUID : TGUID;
begin
     Result := '';
     if FFName <> '' then
        FFileName := FFName
     else FFileName := Application.GetOptionValue('n', 'import-note');
     {$ifdef UNIX}
    if FFileName[1] = '~' then
        FFileName := GetEnvironmentVariable('HOME') + FFileName.Remove(0,1);
    {$endif}
     if not FileExists(FFileName) then begin
         Result := 'Error, request to import nonexisting file : ' + FFileName;
         debugln(Result);
         exit;
     end;
     if GetTitleFromFFN(FFileName, false) = '' then begin
         Result := 'Error, request to import invalid Note file : ' + FFileName;
         debugln(Result);
         exit;
     end;
     if IDLooksOK(ExtractFileNameOnly(FFileName)) then
        FName := ExtractFileName(FFileName)
     else begin
        CreateGUID(GUID);
        FName := copy(GUIDToString(GUID), 2, 36) + '.note';
     end;
     // debugln('About to copy ' + FFileName + ' to ' + FName);
     if CopyFile(FFileName, GetNotesDir() + FName) then begin
        if TheReindexProc = nil then        // Defined in tb_utils, set in SearchUnit.
            CanSendMessage('REINDEX:' + FName)
        else TheReindexProc(FName, True);
     end
        else begin
            Result := 'ERROR - failed to copy ' + FFileName + ' to ' + GetNotesDir() + FName;
            debugln(Result);
        end;
end;



function Import_Text_MD_File(MD : boolean; FFName : string = '') : string;
    // May generate an error but reports it to commad line so, OK ?
    // but is also called from SearchUnit, so who sees that error then ?
    //
var
    FFileName : string;
    Importer : TImportNotes;
begin
    result := '';                   // Anything here is an error message
    if FFName <> '' then
        FFileName := FFName
    else
        if MD then
            FFileName := Application.GetOptionValue('m', 'import-md')
        else FFileName := Application.GetOptionValue('t', 'import-txt');
    Importer := TImportNotes.Create;
    try
        try
            Importer.DestinationDir := GetNotesDir();       // might raise ENoNotesRepoException
            Importer.Mode := 'plaintext';
            if MD then Importer.Mode := 'markdown';
            Importer.FirstLineIsTitle := true;
            if Application.HasOption('f', 'title-fname') then
                Importer.FirstLineIsTitle := false;
            Importer.ImportName := FFileName;
            if Importer.Execute() = 0 then
                debugln(Importer.ErrorMsg)
            else                                                        // At this stage, we have a valid note in repo but not Indexed.
                if TheReindexProc = nil then begin                      // Defined in tb_utils, set in SearchUnit.
                    CanSendMessage('REINDEX:'+ Importer.NewFileName)    // eg REINDEX:48480CC5-EC3E-4AA0-8C83-62886DB291FD.note
                end
                else TheReindexProc(Importer.NewFileName, True);        // If TheReindexProc then we have a GUI, show message there.
        except on
            //E: ENoNotesRepoException do begin
            E: Exception do begin
                            Result := E.Message;                        // SearchUnit will look at that.
                            debugln(E.Message);
                            exit;
                        end;
        end;
    finally
        Importer.Free;
    end;

end;

    // Looks to see if user just wants some command line activity, returns
    // false if GUI is not needed.

function ContinueToGUI() : boolean ;
begin
    if CommandLineError() then exit(False);
    if Application.HasOption('v', 'version') then begin
        debugln('tomboy-ng version ' + Version_String);
        exit(False);
     end;
    if Application.HasOption('t', 'import-txt') then begin
        Import_Text_MD_File(false);
        exit(False);
    end;
    if Application.HasOption('m', 'import-md') then begin
        Import_Text_MD_File(true);
        exit(False);
    end;
    if Application.HasOption('n', 'import-note') then begin
        Import_Note();
        exit(False);
    end;
    if Application.HasOption('c', 'create-note') then begin
       if CanSendMessage('CREATENOTE') then
            exit(false)                 // no GUI needed, just tell existing one to open a new note
       else
           OpenNewNotePlease := True;   // mainunit FormShow() will look there to see if it got a new note request at startup
    end;                                // applies only when user uses -c and -ng is not already running

    // Note that the useappind option is processed in the LPR file.

    if HaveCMDParam() then              // Only command line parameter is filename of a Single Note
         if SingleNoteName = '' then
            exit(False)                 // thats an error, more than one parameter
         else exit(True);               // proceed in SNM, we set its File Name further up.
    // Looks like a normal startup
    if CanSendMessage('SHOWSEARCH') then exit(False);     // will fail if -ng is not already running.
    Result := true;
end;

initialization
    LongOpts := TStringArray.create(                   // a type, not an object, don't free.
        'dark-theme', 'lang:', 'debug-log:',
        'help', 'version', 'no-splash',
        'debug-sync', 'debug-index', 'debug-spell',
        'config-dir:', 'open-note:', 'save-exit',      // -o for open also legal. save-exit is legecy
        'import-txt:', 'import-md:', 'import-note:',   // -t, -m -n respectivly
        'title-fname', 'gnome3', 'useappind:',         // -g and gnome3 is legal but legacy, ignored.
        'strict-theme',                                // Strict-theme applies to only Qt versions
        'allow-leftclick',                             // overrule wayland decision to use only right click
        'create-note',                                 // create a new note (-ng open or not), added post 0.41
        'disableaccurateframe');                       // possible read by LCL to stop an annoying little window in RasPiOS
end.