{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.7 10/26/2004 10:20:04 PM JPMugaas Updated refs. Rev 1.6 2004.02.03 5:45:14 PM czhower Name changes Rev 1.5 1/31/2004 1:18:40 PM JPMugaas Illiminated Todo; item so it should work in DotNET. Rev 1.4 1/21/2004 3:11:04 PM JPMugaas InitComponent Rev 1.3 10/19/2003 4:51:34 PM DSiders Added localization comments. Rev 1.2 2003.10.12 3:53:12 PM czhower compile todos Rev 1.1 3/5/2003 11:41:14 PM BGooijen Added IdCoreGlobal to the uses, this file was needed for the call to Sleep(...) Rev 1.0 12/28/2002 3:04:52 PM DSiders Initial revision. } unit IdIPAddrMon; { TIdIPAddrMon Monitors adapters known to the IP protocol stack for changes in any of the IP addresses. Similar to TIdIPWatch, but monitors all IP addresses/adapters. Does not keep a permanent IP address history list. But does trigger a TIdIPAddrMonEvent event to signal the adapter number, old IP, and new IP for the change in status. OnStatusChanged is used to capture changed IP addresses, and/or to sync with GUI display controls. If you do not assign a procedure for the event handler, this component essentially does nothing except eat small amounts of CPU time. The thread instance is created and freed when the value in Active is changed. TIdIPAddrMonEvent An procedure use to handle notifications from the component. Includes parameters that represent the adapter number, previous IP or '', and the current IP or ''. TIdIPAddrMonThread Timer thread for the IP address monitor component. Based on TIdIPWatchThread. Sleeps in increments of .5 seconds until the Interval has elapsed, and fires the timer event. Sleep is called in increments to allow checking for Terminated when a long Interval has been specified. Original Author: Don Siders, Integral Systems, Fri 27 Dec 2002 Donated to the Internet Direct (Indy) Project for use under the terms of the Indy Dual License. } interface {$i IdCompilerDefines.inc} uses Classes, IdGlobal, IdComponent, IdThread; const IdIPAddrMonInterval = 500; type TIdIPAddrMonEvent = procedure(ASender: TObject; AAdapter: Integer; AOldIP, ANewIP: string) of object; TIdIPAddrMonThread = class(TIdThread) protected FInterval: UInt32; FOnTimerEvent: TNotifyEvent; procedure Run; override; procedure DoTimerEvent; end; TIdIPAddrMon = class(TIdComponent) private FActive: Boolean; FBusy: Boolean; FInterval: UInt32; FAdapterCount: Integer; FThread: TIdIPAddrMonThread; // TODO: replace these with TIdStackLocalAddressList FIPAddresses: TStrings; FPreviousIPAddresses: TStrings; FOnStatusChanged: TIdIPAddrMonEvent; procedure SetActive(Value: Boolean); procedure SetInterval(Value: UInt32); procedure GetAdapterAddresses; procedure DoStatusChanged; protected procedure InitComponent; override; procedure Loaded; override; public destructor Destroy; override; procedure CheckAdapters(Sender: TObject); procedure ForceCheck; property AdapterCount: Integer read FAdapterCount; property Busy: Boolean read FBusy; property IPAddresses: TStrings read FIPAddresses; property Thread: TIdIPAddrMonThread read FThread; published property Active: Boolean read FActive write SetActive; property Interval: UInt32 read FInterval write SetInterval default IdIPAddrMonInterval; property OnStatusChanged: TIdIPAddrMonEvent read FOnStatusChanged write FOnStatusChanged; end; implementation uses {$IFDEF DOTNET} {$IFDEF USE_INLINE} System.Threading, {$ENDIF} {$ENDIF} {$IFDEF USE_VCL_POSIX} Posix.SysSelect, Posix.SysTime, {$ENDIF} IdStack, SysUtils; procedure TIdIPAddrMon.InitComponent; begin inherited InitComponent; FInterval := IdIPAddrMonInterval; FActive := False; FBusy := False; FAdapterCount := 0; // TODO: replace these with TIdStackLocalAddressList FIPAddresses := TStringList.Create; FPreviousIPAddresses := TStringList.Create; // FThread created when component becomes Active end; destructor TIdIPAddrMon.Destroy; begin Active := False; FBusy := False; FIPAddresses.Free; FPreviousIPAddresses.Free; // FThread freed on Terminate inherited Destroy; end; procedure TIdIPAddrMon.Loaded; begin inherited Loaded; // Active = True must not be performed before all other props are loaded if Active then begin FActive := False; Active := True; end; end; procedure TIdIPAddrMon.CheckAdapters(Sender: TObject); begin // previous check could still be running... if FBusy then begin Exit; end; FBusy := True; try try GetAdapterAddresses; if IsDesignTime then begin Exit; end; // TODO: replace with TIdStackLocalAddressList { LChanged := FPreviousIPAddresses.Count <> FIPAddresses.Count; if not LChanged then begin for I := 0 to FIPAddresses.Count-1 do begin LChanged := FPreviousIPAddresses[I].IPAddress.Count <> FIPAddresses[I].IPAddress; if LChanged then begin Break; end; end; end; if LChanged then begin // something changed at runtime DoStatusChanged; end; } if (FPreviousIPAddresses.Count <> FIPAddresses.Count) or (FPreviousIPAddresses.Text <> FIPAddresses.Text) then begin // something changed at runtime DoStatusChanged; end; except // eat any exception end; finally FBusy := False; end; end; procedure TIdIPAddrMon.DoStatusChanged; var iOldCount: Integer; iNewCount: Integer; iAdapter: Integer; sOldIP: string; sNewIP: string; begin if not Assigned(FOnStatusChanged) then begin Exit; end; // figure out the change... new, removed, or altered IP for adapter(s) iOldCount := FPreviousIPAddresses.Count; iNewCount := FIPAddresses.Count; // find the new adapter IP address if iOldCount < iNewCount then begin sOldIP := ''; {do not localize} for iAdapter := 0 to iNewCount - 1 do begin // TODO: replace with TIdStackLocalAddressList { sNewIP := FIPAddresses[iAdapter].IPAddress; if FPreviousIPAddresses.IndexOfIP(sNewIP, FIPAddresses[iAdapter].IPVersion) = -1 then begin FOnStatusChanged(Self, iAdapter, sOldIP, sNewIP); end; } sNewIP := FIPAddresses[iAdapter]; if FPreviousIPAddresses.IndexOf(sNewIP) = -1 then begin FOnStatusChanged(Self, iAdapter, sOldIP, sNewIP); end; end; end // find the missing adapter IP address else if iOldCount > iNewCount then begin sNewIP := ''; {do not localize} for iAdapter := 0 to iOldCount - 1 do begin // TODO: replace with TIdStackLocalAddressList { sOldIP := FPreviousIPAddresses[iAdapter].IPAddress; if FIPAddresses.IndexOfIP(sOldIP, FPreviousIPAddresses[iAdapter].IPVersion) = -1 then begin FOnStatusChanged(Self, iAdapter, sOldIP, sNewIP); end; } sOldIP := FPreviousIPAddresses[iAdapter]; if FIPAddresses.IndexOf(sOldIP) = -1 then begin FOnStatusChanged(Self, iAdapter, sOldIP, sNewIP); end; end; end // find the altered adapter IP address else begin for iAdapter := 0 to AdapterCount - 1 do begin // TODO: replace with TIdStackLocalAddressList { sOldIP := FPreviousIPAddresses[iAdapter].IPAddress; sNewIP := FIPAddresses[iAdapter].IPAddress; if (FPreviousIPAddresses[iAdapter].IPVersion <> FIPAddresses[iAdapter].IPVersion) or (sOldIP <> sNewIP) then begin FOnStatusChanged(Self, iAdapter, sOldIP, sNewIP); end; } sOldIP := FPreviousIPAddresses[iAdapter]; sNewIP := FIPAddresses[iAdapter]; if sOldIP <> sNewIP then begin FOnStatusChanged(Self, iAdapter, sOldIP, sNewIP); end; end; end; end; procedure TIdIPAddrMon.ForceCheck; begin CheckAdapters(nil); end; procedure TIdIPAddrMon.SetActive(Value: Boolean); begin if Value <> FActive then begin if Value then begin // get initial addresses at start-up and allow display in IDE GetAdapterAddresses; end; if (not IsDesignTime) and (not IsLoading) then begin if Value then begin FThread := TIdIPAddrMonThread.Create(True); FThread.FOnTimerEvent := CheckAdapters; FThread.FInterval := Self.Interval; FThread.Start; end else begin if FThread <> nil then begin FThread.TerminateAndWaitFor; FreeAndNil(FThread); end; end; end; FActive := Value; end; end; procedure TIdIPAddrMon.SetInterval(Value: UInt32); begin FInterval := Value; if Assigned(FThread) then begin FThread.FInterval := FInterval; end; end; procedure TIdIPAddrMonThread.Run; var lInterval: Integer; begin lInterval := FInterval; while lInterval > 0 do begin // force a check for terminated every .5 sec if lInterval > 500 then begin IndySleep(500); lInterval := lInterval - 500; end else begin IndySleep(lInterval); LInterval := 0; end; if Terminated then begin Exit; end; end; // interval has elapsed... fire the thread timer event Synchronize(DoTimerEvent); end; procedure TIdIPAddrMonThread.DoTimerEvent; begin if Assigned(FOnTimerEvent) then begin FOnTimerEvent(Self); end; end; procedure TIdIPAddrMon.GetAdapterAddresses; var LAddresses: TIdStackLocalAddressList; I: Integer; begin { Doesn't keep a permanent history list like TIdIPWatch... but does track previous IP addresses to detect changes. } FPreviousIPAddresses.Assign(FIPAddresses); FIPAddresses.Clear; LAddresses := TIdStackLocalAddressList.Create; try GStack.GetLocalAddressList(LAddresses); for I := 0 to LAddresses.Count-1 do begin FIPAddresses.Add(LAddresses[I].IPAddress); end; finally LAddresses.Free; end; FAdapterCount := FIPAddresses.Count; end; end.