Page 1 of 3

OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 2:14 am
by ts-soft

Code: Select all

;======================================================================
; Module:          OnlyOne.pbi
;
; Author:          Thomas (ts-soft) Schulz
; Date:            Feb 16, 2014
; Version:         1.2
; Target Compiler: PureBasic 5.2+
; Target OS:       All
; License:         Free, unrestricted, no warranty whatsoever
;                  Use at your own risk
;======================================================================

; History:
; Version 1.2
;   - Removed the autoclean on #PB_Event_CloseWindow
;   + ReleaseOne() to remove Timer and delete LockFile
;   + changed Example to reflect the changes

; Version 1.1
;   + IsWindow() in CloseWinCB()
;   + Compiler OS Directive for PostEvent in CloseWinCB()
;
; Version 1.0
;   change timestamp to filedate
;   + some optimise
;
; Version 0.9
;   + timestamp for lockfile (lockfile is outdated after 5 minutes)
;   + PostEvent to CloseWinCB()

; Version 0.8
;   + unbind timer
;   + some small corrections
;
; Version 0.7
;  + some sanichecks
;  + removetimer
;  + unbindevent
;
; Version 0.6
;  + createmutex for windows, to make sure it works after crash (lockfile not deleted!)

DeclareModule OnlyOne
  EnableExplicit
  
  Declare.i InitOne(wID,                                      ; ID of the main window.
                    OnlyOneName.s,                            ; Unique name for Lockfile.
                    CustomEvent = #PB_Event_FirstCustomValue, ; User-defined event in which the parameters are processed.
                    TimerID = 1,                              ; TimerID to use.
                    TimeOut = 2000)                           ; Interval in ms, to check the passed parameter queries.
                    
  Declare.i ReleaseOne()                                      ; Delete LockFile and removes Timer.
  
  Declare.s GetParameters()                                   ; The parameter string, separated with #LF$
EndDeclareModule

Module OnlyOne
  Global.i Event, win, timer
  Global.s LockFile, Paras
  
  Procedure TimerCB()
    Static oldtime = 0
    If oldtime = 0 : oldtime = Date() : EndIf
    Protected FF, time
    
    If FileSize(LockFile) > 0
      FF = ReadFile(#PB_Any, LockFile)
      If FF
        Paras = ""
        While Not Eof(FF)
          Paras + ReadString(FF) + #LF$
        Wend
        CloseFile(FF)
        FF = CreateFile(#PB_Any, LockFile)
        If FF
          CloseFile(FF)
        EndIf
        PostEvent(Event)
      EndIf
    CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
      CompilerDefault
        Else
          time = AddDate(oldtime, #PB_Date_Minute, 3)
          If time < Date()
            oldtime = Date()
            SetFileDate(LockFile, #PB_Date_Modified, Date())
          EndIf
      CompilerEndSelect
    EndIf

  EndProcedure
  
  Procedure.i ReleaseOne()
    RemoveWindowTimer(win, timer)
    UnbindEvent(#PB_Event_Timer, @TimerCB(), win, timer)
    ProcedureReturn DeleteFile(LockFile)
  EndProcedure
  
  Procedure.s GetParameters()
    ProcedureReturn Paras
  EndProcedure
  
  Procedure InitOne(wID, OnlyOneName.s, CustomEvent = #PB_Event_FirstCustomValue, TimerID = 1, TimeOut = 2000)
    Protected.i FF, i, j, time, result = #False
    
    If Not IsWindow(wID)
      Debug "You have to open a window, before you call this function!"
      ProcedureReturn #False
    EndIf
    win = wID
    timer = TimerID
    Event = CustomEvent
    LockFile = GetTemporaryDirectory() + OnlyOneName

    CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
        Protected Mutex = CreateMutex_(0, 0, OnlyOneName)
        If GetLastError_() = #ERROR_ALREADY_EXISTS
          ReleaseMutex_(Mutex)
          CloseHandle_(Mutex)
        Else
          DeleteFile(LockFile)
        EndIf
      CompilerDefault
        time = AddDate(GetFileDate(LockFile, #PB_Date_Modified), #PB_Date_Minute, 5)
        If time < Date()
          DeleteFile(LockFile)
        EndIf
    CompilerEndSelect  

    If FileSize(LockFile) = -1
      FF = CreateFile(#PB_Any, LockFile)
      If FF
        CloseFile(FF)
        AddWindowTimer(wID, TimerID, TimeOut)
        BindEvent(#PB_Event_Timer, @TimerCB(), wID, TimerID)
        j = CountProgramParameters()
        If j
          For i = 0 To j
            Paras + ProgramParameter(i) + #LF$
          Next
          Paras = Left(Paras, Len(Paras) - 1)
          PostEvent(Event)
        EndIf
        HideWindow(wID, #False)
        result = #True
      EndIf
    Else
      FF = OpenFile(#PB_Any, LockFile, #PB_File_Append)
      If FF
        j = CountProgramParameters()
        If j
          For i = 1 To j
            Paras + ProgramParameter(i - 1) + #LF$
          Next
          Paras = Left(Paras, Len(Paras) - 1)
          WriteStringN(FF, Paras)
        EndIf
        CloseFile(FF)        
      EndIf
      End
    EndIf
    
    ProcedureReturn result
  EndProcedure
EndModule

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
  
  Enumeration #PB_Event_FirstCustomValue
    #Event_NewParams
  EndEnumeration
  
  OpenWindow(0, #PB_Ignore, #PB_Ignore, 640, 480, "Test", #PB_Window_SystemMenu | #PB_Window_Invisible)
  ListViewGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20)
  OnlyOne::InitOne(0, "Example_Application", #Event_NewParams)

  Define i, j, paras.s
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        OnlyOne::ReleaseOne()
        Break
      
      Case #Event_NewParams
        SetActiveWindow(0)
        CompilerIf #PB_Compiler_OS = #PB_OS_Windows
          SetForegroundWindow_(WindowID(0))
        CompilerEndIf
        paras = OnlyOne::GetParameters()
        j = CountString(paras, #LF$)
        For i = 1 To j
          AddGadgetItem(0, -1, StringField(paras, i, #LF$))
        Next
    EndSelect
  ForEver
CompilerEndIf
Have fun

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 3:33 am
by ts-soft
Update:
History wrote:; Version 0.6
; + createmutex for windows, to make sure it works after crash (if lockfile not deleted!)

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 10:45 am
by srod
That is pretty cool Thomas - works well here.

If I were you, I would also issue some UnBindEvent() statements just as the window is being closed. I have found that, especially with timers, if you then recreate another window with the same ID and another timer with the same timerID then the original event proc will continue to be called. You could well end up with the timer proc being called twice as often as you would like if you then proceed to use BindEvent() again. :) I cannot make my mind up if this is a bug with PB or not since UnbindEvent() does sort the issue out? :)

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 12:19 pm
by ts-soft
Thanks stephen.

Update:
History wrote:; Version 0.7
; + some sanichecks
; + removetimer
; + unbindevent

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 12:48 pm
by srod
You'll want to unbind the #PB_Event_Timer event as well. ;) Just to be safe.

Thanks for sharing.

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 12:59 pm
by ts-soft
Update:
History wrote:; Version 0.8
; + unbind timer
; + some small corrections
If someone have a idea to add a function like "named-mutex" for linux and macos, please tell me :wink:

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 5:20 pm
by Tenaja
ts-soft wrote:If someone have a idea to add a function like "named-mutex" for linux and macos, please tell me :wink:
Thanks for sharing! I have found about a dozen Single-Instance examples on the forum, but never one that passed parameters (cross platform). I made my own version for a text editor, but without parm passing, you can't double-click a file to open it into the running program. Since it was not critical to me, I haven't yet made the time to implement parameter passing; I will probably switch mine out for this. Thanks again.

One idea for Linux and Mac, since you cannot name a Mutex (with native PB) is to write a time stamp in the file. If you write it maybe only once every few minutes, you can test to see if the time-stamp is old enough to account for a crash, and then display a MessageBox to verify, if it is within an allowable margin (maybe 2x).

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 7:50 pm
by ts-soft
Tenaja wrote:One idea for Linux and Mac, since you cannot name a Mutex (with native PB) is to write a time stamp in the file
done, thanks

Update:
History wrote:; Version 0.9
; + timestamp for lockfile (lockfile is outdated after 5 minutes)
; + PostEvent to CloseWinCB()

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 8:15 pm
by srod
Thomas, rather than writing a time stamp could you not just use GetFileDate() with the modified flag instead?

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 8:19 pm
by ts-soft
srod wrote:Thomas, rather than writing a time stamp could you not just use GetFileDate() with the modified flag instead?
I have test this in my first version, but without success (only on linux tested).

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 8:21 pm
by srod
Ah, did wonder how reliable the values returned from that function might be. Never used it myself.

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 10:03 pm
by ts-soft
Okay, it works with FileDate :wink:

Update:
History wrote:; Version 1.0
; change timestamp to filedate
; + some optimise

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 10:27 pm
by srod
Works great.

Thanks. Will save this one for future use.

One question; the PostEvent(#PB_Event_CloseWindow, win, 0)... is that needed on Linux then because on Windows the main #PB_Event_CloseWindow event loop runs automatically and exits the loop without the PostEvent() ?

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 10:49 pm
by ts-soft
Yes, the postevent() is required on linux or you have to press the closebutton twice :wink:

Should i add a IsWindow() between the postevent and removeanything?

Re: OnlyOne Module Crossplattform

Posted: Fri Feb 14, 2014 11:32 pm
by srod
It doesn't cause any problems on Win 7 PB 5.21 LTS since the bound event procedure runs first. Beside's your code doesn't actually close the window anyhow!

I should have Ubuntu up and running tomorrow so I can get back to testing with Linux. :)