#
# Analyse the system to diagnose installation failures, etc. Generates a nice HTML report for 
# displaying to the user and for sending to the AWS support team.
#
require 'ftools'
require 'date'
require 'logger'
require 'cgi'
require 'erb'
require 'base64'

HOME = File.expand_path("~")
BASE_GUIDE_URL = "http://cdn.agile.ws/docs/1Password"

RESULT_TEMPLATE = <<result
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>1Password Diagnostics Report for <%= %x{whoami} %></title>
	<style type="text/css">
	  .indent { margin-left: 25px; }
	  .log { margin-left: 25px; }
	  h5.header {text-transform:uppercase;}
	</style>
</head>
<body>
	<h3>System Diagnostics for <%= %x{whoami} %></h3>

	<p>Generated: <%= Time.now() %></p>
	<p>This information will be used by Agile Web Solutions to help resolve any issues you may have.<\/p>

	<h3>Configuration</h3>
	<%= configuration.add_brs %>

	<h3>Summary</h3>
	<%= summary.add_brs %>

	<h3>System Details</h3>

	<h5>Table of Contents</h5>
  <ol>
  <%= @@table_of_contents %>
  </ol>
	<%= details.add_brs %>
	<h3>Logs</h3>
	<%= logs.add_brs %>
</body>
</html>
result

class String
  def add_brs
    return self.gsub(/([^>])\n/, "\\1<br/>")
  end
  
  def htmlize
    CGI::escapeHTML(self)
  end
end

def processStarted?(name)
  return `ps -Ajc | grep -v grep | grep '#{name}'` =~ /#{name}$/ ? true : false
end

def nsUserDefault(bundle_path, key)
  nsDefault = `defaults read "#{bundle_path}" #{key} 2>&1`
  return nil if nsDefault =~ /The domain\/default pair of \(.*\) does not exist/m 
  return nsDefault.chomp
end

def ns1PUserDefault(key)
  return nsUserDefault('ws.agile.1Password', key)
end

def print(s)
  @@result += s.to_s + "\n"
end

class Print
  def Print.header(txt)
    print "<h5 class='header' id='header_#{txt.hash.abs}'>#{txt}</h5>"
    @@table_of_contents += "<li><a href='#header_#{txt.hash.abs}'>#{txt}</a></li>"
  end
  
  def Print.start_section(txt=nil)
    Print.header(txt) unless txt.nil?
    print "<div class='indent'>"
  end
  
  def Print.end_section
    print "</div>"
  end
  
  def Print.writable(file)
    print "#{file} writable?: #{File.stat(File.expand_path(file)).writable?}"
  end

  def Print.exists(file)
    print "#{file} exists?: #{File.exists?(File.expand_path(file))}"
  end

  def Print.dir(dir, sort_option="")
    dir = File.expand_path(dir)
    
    print("#{dir} is symlinked to #{File.readlink(dir)}") if File.symlink?(dir)
    
    unless (File.exists?(dir))
      print "#{dir} does not exist"
      return
    end
    # dir.gsub!(/\s/, "\\ ")
    cmd = "ls -FaBl@We#{sort_option} \"#{dir}\""
    print "<pre>#{cmd}\n#{`#{cmd}`}</pre>"
  end
  
  def Print.stat(dir)
    dir = File.expand_path(dir)
    unless (File.exists?(dir))
      print "#{dir} does not exist"
      return
    end
    # dir.gsub!(/\s/, "\\ ")
    cmd = "stat \"#{dir}\""
    print "<pre>#{cmd}\n#{`#{cmd}`}</pre>"
  end
  
  def Print.file_contents(filename)
    if File.exists?(filename) && File.file?(filename)
      filename = File.expand_path(filename)
      file = File.open(filename) rescue nil
    
      unless file 
        print "Could not load file #{filename}"
        return
      end
      print "<i><u>Contents of #{filename}</u></i>\n"
      print file.read() + "\n\n"
    else
      print "File [#{filename}] does not exist."
    end
  end
  
  def Print.print_all_files_in_folder(folder)
    dir = File.expand_path(folder)
    unless File.exists?(dir)
      print "Folder &lt;#{folder}&gt; does not exist"
      return
    end
    
    files = Dir["#{dir}/*"]
    files.each { |f|
      Print.file_contents(f) if File.should_print?(f)
    }
  end
  
  def Print.exception(clazz, exc)
    Print.header("Exception in #{clazz.class}")
    print $!.message + "<br />"
    print $!.backtrace * "<br/>"
  end
end

class BaseTestClass 
  def run_tests
    begin
      tests
    rescue
      Print.exception(self, $!)
    end
  end
end

class Sys < BaseTestClass
  attr :lsregister_dump
  
  def initialize
    lsregister = "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
    unless File.exists?(lsregister)
      lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
    end
    @lsregister_dump = `#{lsregister} -dump`
    print "Result of lsregister -dump: #{$?}"
  end
  
  def tests
    Print.start_section "System Information"
    print "User home folder: #{HOME}"
    print `sw_vers`
    print `system_profiler SPHardwareDataType`.gsub(/^\s*Serial Number.*$/, "").gsub(/^\s*Hardware UUID:.*$/, "")
    Print.end_section

    Print.start_section "User &amp; Group Details"
    id_output = `id`
    print "Output from 'id' command: #{id_output}"
    dscl_output = `dscl . -read /Groups/wheel GroupMembership`
    print "Output from 'dscl' command: #{dscl_output}" if id_output =~ /wheel/
    Print.end_section

    Print.start_section("OpenSSL Information")
    print `openssl version`
    Print.end_section
    
    Print.start_section("Bonjour Information")
    print processStarted?('mDNSResponder') ? "mDNSResponder Bonjour process is running." : "mDNSResponder Bonjour process is not running."
    Print.end_section
    
    Print.start_section("Potentially Relevant Currently Running Processes")
    print %x{ps -Ajc | grep -i 1P}
    print %x{ps -Ajc | grep -i Quicksilver}
    print %x{ps -Ajc | grep -i virus}
    Print.end_section
    
    Print.start_section("Dropbox Information")
    print find_app("Dropbox") == nil ? "Could not locate Dropbox.app" : "Dropbox installed at #{find_app("Dropbox")}"
    print %x{ps -Ajc | grep -i Dropbox} =~ /Dropbox/i ? "Dropbox is running. Process information: #{%x{ps -Ajc | grep -i Dropbox}}" : "Dropbox process is not started"
    print "Dropbox path: #{dropbox_path}"
    print "Dropbox 1Password configuration file: 'stat \"#{dropbox_path}\"' &rarr;  " << %x{stat "#{dropbox_path}"} unless dropbox_path =~ /Could not locate/
    Print.file_contents(File.join(dropbox_path, ".ws.agile.1Password.settings")) unless dropbox_path =~ /Could not locate/
    Print.end_section
    
  end
  
  def dropbox_path
    path = File.expand_path("~/.dropbox/host.db")
    return "Could not locate Dropbox host.db file" unless File.exist?(path)
    lines = File.readlines(File.expand_path("~/.dropbox/host.db"))
    return Base64.decode64(lines[1])
  end
  
  def Sys.is_snow_leopard?
    `sw_vers` =~ /ProductVersion:\t10.6/
  end
  
  def Sys.is_leopard?
    `sw_vers` =~ /ProductVersion:\t10.5/
  end
  
  def Sys.is_tiger?
    `sw_vers` =~ /ProductVersion:\t10.4/
  end
  
  def find_app(name)
    result = $1 if @lsregister_dump =~ /^.*path:\s*(.*#{name}.*)$/
    return result
  end
end

class Summary

  def Summary.summary
    begin
      _summary
    rescue
      Print.exception(self, $!)
    end
  end

  def Summary._summary
    result = ""
    
    result << "1Password Version: #{OnePasswd.new.version}\n"
    sys_ver = "Mac OS X Version: "
    `sw_vers` =~ /\s*ProductVersion:\s*([0-9\.]+)$/i
    sys_ver << $1
    `sw_vers` =~ /\s*BuildVersion:\s*(\w+)$/i
    sys_ver << " (#{$1})\n"
    result << sys_ver
    
    result << "Active Keychain Format: "
    if Keychain.new.keychain_type =~ /html/i
      result << "Agile Keychain"
    else
      result << "OS X Keychain"
    end
    result << "\n"
    result << "Agile Keychain location: #{ns1PUserDefault("AgileKeychainLocation")}\n" if Keychain.new.agile_keychain?
    keychain_data = File.expand_path(Keychain.new.agileKeychainFolder) << "/data/default"
    opfiles = Dir["#{keychain_data}/*[^)].1password"]
    result << "Number of 1Password files: #{opfiles.length}\n" if Keychain.new.agile_keychain?
    
    # Is this user an admin?
    id_output = `id`
    id_output =~ /uid=\d+\(([^\)]+)\)/
    shortname = $1
    if id_output =~ /groups=.*?admin/
      result << "User #{shortname} is in admin group"
    else
      result << "User #{shortname} is <b><i>not</i></b> in admin group"
    end
    result << "\n\n"
    
    # What InputManagers do they have installed?
    result << "Installed extensions: "
    result << "<div class='indent'>"
    extension_locations = ["/Library/InputManagers", "#{HOME}/Library/InputManagers", "/Library/Internet Plug-Ins", "#{HOME}/Library/Internet Plug-Ins"]
    begin
      extension_locations.each do |path|
      if File.exist?(path)
        dir = Dir.new(path).entries.reject { |entry| entry =~ /^\./ }
        unless dir.length == 0
          result << "<h5>#{path}</h5>"
          result << "<ul>"
          dir.each { |entry| result << "<li>" << entry << "</li>" }
          result << "</ul>"
        end
      end
    end
    result << "</div>"
    rescue
    end
    result << "Firewall Software"
    result << "<div class='indent'>"
    little_snitch_installed = File.exist? '/Library/LaunchDaemons/at.obdev.littlesnitchd.plist'
    

    result << "LittleSnitch installed? <b>#{little_snitch_installed ? "Yes" : "No"}</b><br />"
    result << "Intego VirusBarrier installed? <b>#{VirusBarrier.installed? ? "Yes" : "No"}</b><br />"
    result << "Intego VirusBarrier started? <b>#{VirusBarrier.started?}</b><br />" if VirusBarrier.installed?
    result << "<i>Firewall software such as LittleSnitch or Intego VirusBarrier can cause issues with syncing with 1Password on Apple devices such as iPhone, iPod touch, and iPad. Be sure to whitelist 1Password for outbound network activity.</i>" if little_snitch_installed or VirusBarrier.installed?
    result << "</div>"
    result << "</div>"
    
    result.gsub!(/, \]/, "]")
    
    return result
    
  end
end

class Configuration

  def Configuration.summary
    begin
      _summary
    rescue
      Print.exception(self, $!)
    end
  end

  # Detect common configuration errors
  def Configuration._summary
    errors = []

    keychain = Keychain.new
    errors << "Settings show you are using the Agile Keychain, but no Agile Keychain exists at <code>#{keychain.agileKeychainFolder}</code>." if keychain.agile_keychain? and !File.exist?(File.expand_path(keychain.agileKeychainFolder))
    
#    errors << "Your Safari application has an invalid code signature. Please refer to <a href='#{BASE_GUIDE_URL}/invalid_code_signature.html'>Fixing Safari's Code Signature</a> to see how this happened and how to resolve the problem." if safari_code_signature_invalid? 
    
    errors << "Your OS X user account has an invalid shortname. Short names can only contain lowercase letters and an invalid shortname can cause many different problems with 1Password. Apple has a technote about how to <a href=\"http://support.apple.com/kb/ht1428\">change the shortname</a> for your account." if %x{whoami}.chomp =~ /[^a-z]/
    errors << "Your OS X user account belongs to the 'wheel' super user group. Please refer to <a href='#{BASE_GUIDE_URL}/fix_wheel_group.html'>User Belongs to Wheel Group</a>" if user_in_wheel? 
    
    errors << "Your OS X user account is launching processes as the 'root' super user. Please refer to <a href='#{BASE_GUIDE_URL}/running_as_root.html'>Processes Running as Root</a> to see a description of the problem and how to fix it." if keychain_file_owned_by_root? 

    errors << "Your 1Password keychain file is owned by the 'root' super user. Please refer to <a href='#{BASE_GUIDE_URL}/running_as_root.html'>Keychain Owned by Root</a> to see a description of the problem and how to fix it." if running_as_root? 
    
    errors << "The old 1Passwd Internet Plugin is installed which can prevent the 1Password icon from appearing in Safari and other WebKit browsers. Please use Finder to move this folder into the Trash: '#{File.expand_path('~/Library/Internet Plug-Ins/1PasswdInternetPlugin00.plugin')}'. Once in the trash, restart the browser and the icon should appear." if Configuration.old_internet_plugin_installed?

    valid, error = OnePasswd.verify_scripting_addition
    errors << "1Password Scripting Addition is invalid and could prevent browser integration. Go to 1Password > Preferences > Browsers and click Remove All Extensions and then Install All Extensions to reinstall." unless valid

    valid, error = OnePasswd.verify_versioned_extensions
    errors << "The versioned extensions files do not match the files in the main 1Password application and could affect browser integration. Reinstall these files by going to 1Password > Preferences > Browsers and clicking Remove All Extensions and then Install All Extensions." unless valid
    
    safari = Safari.new
    unless safari.build_number_valid?
      errors << "Your Safari version (build ##{safari.build_number}) is not yet officially supported. This usually occurs when Apple introduces a new update to Safari. A new release of 1Password will be available soon, usually just a few days following a new Safari release. Please check for an update by selecting 1Password > Check For Updates menu item. If no update is available, check the <a href='http://switchersblog.com'>Agile Weblog</a> as often a workaround will be posted there." if safari.build_number > safari.max_supported
      errors << "Your Safari version (build ##{safari.build_number}) is no longer supported. Please upgrade to the latest version of Safari by visiting <a href=\"http://apple.com/safari\">Apple's Safari download page.</a>" if safari.build_number < safari.min_supported
    end
    errors << "The Bonjour process mDNSResponder is not running. This can cause problems with synching to 1Password for iPhone OS." unless processStarted?('mDNSResponder')
    
    theTouchables = [File.expand_path('~/Library/Preferences/ws.agile.1Password.plist'), File.expand_path('~/Library/LaunchAgents/ws.agile.1PasswordAgent.plist'), File.expand_path('~/Library/Preferences/ws.agile.1Password.plist'), File.expand_path('~/Library/Application Support/1Password'), Keychain.new.agileKeychainFolder, File.join(Keychain.new.agileKeychainFolder, "data/default")]
    theTouchables.each do |touchable|
       errors << "Permission denied for #{touchable.inspect}" if %x{touch "#{touchable}"} =~ /Permission denied/i
    end
    
    return "<p>No known configuration issues detected.</p>" if errors.empty? 

    result = "<ol>"
    errors.each { |e|
      result += "<li>#{e}</li>"
    }
    result += "</ol>"

  end
  
  def Configuration.old_internet_plugin_installed?
    `ls -la "#{HOME}/Library/Internet Plug-Ins"` =~ /1PasswdInternetPlugin00/
  end
  
  def Configuration.safari_code_signature_invalid?
    return false if Sys.is_tiger?

    sig = `/usr/bin/codesign -vvvvv /Applications/Safari.app 2>&1`
    
    return false if sig == "/Applications/Safari.app: a sealed resource is missing or invalid\n/Applications/Safari.app/Contents/Resources/CacheController.nib/objects.nib: resource added\n/Applications/Safari.app/Contents/Resources/ErrorConsole.nib/objects.nib: resource added\n/Applications/Safari.app/Contents/Resources/RenderTree.nib/objects.nib: resource added\n/Applications/Safari.app/Contents/Resources/Snippet.nib/objects.nib: resource added\n/Applications/Safari.app/Contents/Resources/ViewTree.nib/objects.nib: resource added\n"
    
    return sig =~ /valid on disk/ ? false : true
  end
  
  def Configuration.user_in_wheel?
    return false if Sys.is_tiger?
    return `id` =~ /\(wheel\)/
  end

  def Configuration.keychain_file_owned_by_root?
    return true if `ls -la@ "#{HOME}/Library/Keychains/1Password.keychain" 2>/dev/null` =~ /root/
    return false
  end
  
  def Configuration.running_as_root?
    return false if Sys.is_tiger?
    return true if `ps -Aj | grep -v grep | grep 1Password` =~ /^root/
    return false
  end

  def Configuration.is_admin_root?(folder)
    return false unless `stat "#{folder}" | awk '{print $5 $6}'` =~ /rootadmin/
    
    Dir.foreach(folder) do |x| 
      next if x == "." || x == ".."
      full_path = folder + "/" + x
      unless `stat "#{full_path}" | awk '{print $5 $6}'` =~ /rootadmin/
        print "#{full_path} does not have root:admin"
        return false 
      end
    end
    
    return true
  end

  def Configuration.input_manager_permissions_correct?
    return true if Sys.is_tiger?
    return true unless `stat -q "/Library/InputManagers/1PasswdIM"` =~ /1PasswdIM/
    return Configuration.is_admin_root?("/Library/InputManagers/1PasswdIM") &&
           Configuration.is_admin_root?("/Library/InputManagers/1PasswdIM/1PasswdIM.bundle") &&
           Configuration.is_admin_root?("/Library/InputManagers/1PasswdIM/1PasswdIM.bundle/Contents") &&
           Configuration.is_admin_root?("/Library/InputManagers/1PasswdIM/1PasswdIM.bundle/Contents/MacOS") &&
           Configuration.is_admin_root?("/Library/InputManagers/1PasswdIM/1PasswdIM.bundle/Contents/Resources") &&
           Configuration.is_admin_root?("/Library/InputManagers/1PasswdIM/1PasswdIM.bundle/Contents/Resources/English.lproj")
  end

  def Configuration.leopard_input_manager_installed?
    return true if Sys.is_tiger? || OnePasswd.new.version =~ /\A3/
    return `stat -q "/Library/InputManagers/1PasswdIM"` =~ /1PasswdIM/
  end
  
  def Configuration.leopard_input_manager_in_wrong_place?
    return false if Sys.is_tiger?
    return `stat -q "#{HOME}/Library/InputManagers/1PasswdIM"` =~ /1PasswdIM/
  end
end

class Simbl < BaseTestClass
  def version
    begin
      info = File.new("#{HOME}/Library/InputManagers/SIMBL/SIMBL.bundle/Contents/Info.plist") 
      lines = info.read
      lines =~ /<key>CFBundleShortVersionString<\/key>$\s*<string>(.*?)<\/string>/i
      return $1
    rescue 
      return "not installed"
    end
  end
  
  def tests
    Print.start_section("Input Managers")
    Print.stat('/Library/InputManagers')
    
    unless Sys.is_tiger?
      print("xattr /Library/InputManagers")
      print(`xattr -l "/Library/InputManagers" 2>/dev/null`)
    end
    
    Print.dir('/Library/InputManagers', 't')
    Print.dir('/Library/InputManagers/1PasswdIM', 't')
    
    unless Sys.is_tiger?
      print("xattr /Library/InputManagers/1PasswdIM")
      print(`xattr -l /Library/InputManagers/1PasswdIM 2>/dev/null`)
    else
      Print.dir("#{HOME}/Library/InputManagers/1PasswdIM", 't')
    end
    
    Print.stat('/Library/Application Support')
    Print.stat('/Library/Application Support/SIMBL')

    Print.stat("#{HOME}/Library")
    Print.stat("#{HOME}/Library/Application Support")
    Print.stat("#{HOME}/Library/InputManagers")
    Print.dir("#{HOME}/Library/InputManagers", 't')
    Print.dir("#{HOME}/Library/Application Support/SIMBL", 't')
    Print.dir("#{HOME}/Library/Application Support/SIMBL/Plugins", 't')
    Print.end_section
  end
end

class App
  def initialize(appname)
    @appname = appname
  end
end

class Browser
  def name
    raise "Must override name method in subclass. Should return application's full name; i.e. `Safari.app`"
  end
  
  def bundle_path
    raise "Must override bundle_path in subclass. Should be the bundle path used by NSUserDefaults."
  end
  
  def version
    begin
      File.new("/Applications/#{name}/Contents/Info.plist").read =~ /<key>CFBundleShortVersionString<\/key>$\s*<string>(.*?)<\/string>/i
      return $1
    rescue 
      return "#{name} not installed in /Applications folder."
    end
  end
  
  def build_number
    begin
      File.new("/Applications/#{name}/Contents/Info.plist").read =~ /<key>CFBundleVersion<\/key>$\s*<string>(.*?)<\/string>/i
      return $1.to_f
    rescue 
      return -1.0
    end
  end
  
  def min_supported
    begin
      File.new("#{COCOA_BUNDLE_PATH}/Contents/Resources/SupportedBrowsers.plist").read =~ /<key>#{bundle_path.gsub('.','\.')}<\/key>.*?<key>MinBundleVersion<\/key>.*?<string>(.*?)<\/string>/ixm
      return $1.to_f
    rescue Exception => e
      return -1.0
    end
  end
  
  def max_supported
      begin
        File.new("#{COCOA_BUNDLE_PATH}/Contents/Resources/SupportedBrowsers.plist").read =~ /<key>#{bundle_path.gsub!('.', '\.')}<\/key>.*?<key>MaxBundleVersion<\/key>.*?<string>(.*?)<\/string>/ixm
        return $1.to_f
      rescue Exception => e
        return -1.0
      end
  end
  
  def build_number_valid?(build=-1)
    begin
      raise if min_supported == -1.0 || max_supported == -1.0
      return build != -1 && build >= min_supported && build <= max_supported
    rescue
      return false
    end
  end
  
  def toolbar_identifier
    begin
      File.new("#{COCOA_BUNDLE_PATH}/Contents/Resources/SupportedBrowsers.plist").read =~ /<key>#{bundle_path.gsub('.','\.')}<\/key>.*?<key>ToolbarIdentifier<\/key>.*?<string>(.*?)<\/string>/ixm
      return $1
    rescue
      return "Could not locate toolbar identifier for #{bundle_path}"
    end
  end
  
  def toolbar_button_index
    r = `defaults read #{bundle_path} ws.agile.1Password.toolbarButtonIndex 2>&1`
    return r unless r =~ /does not exist/
    return "No toolbar index for #{name}."
  end
  
  def extension_loaded
    r = `defaults read #{bundle_path} ws.agile.1PasswordExtensionLoaded 2>&1`
    return r unless r =~ /does not exist/
    return "Extension for #{name} has not been loaded"
  end
  
  def hide_toolbar_button
    r = `defaults read #{bundle_path} ws.agile.1Password.hideToolbarButton 2>&1`
    return r unless r =~ /does not exist/
    return "ws.agile.1Password.hideToolbarButton not set for #{name}"
  end
  
  def toolbar_configuration
    r = `defaults read #{bundle_path} "NSToolbar Configuration #{toolbar_identifier}" 2>&1`
    return r unless r =~ /does not exist/
    return "Could not load toolbar configuration for #{name}."
  end
  
end    

class Safari < Browser
  
  def name
    'Safari.app'
  end
  
  def bundle_path
    'com.apple.Safari'
  end
  
  def toolbar_installed?
    tb = `defaults read com.apple.Safari "NSToolbar Configuration SafariToolbarIdentifier" 2>&1`
    return tb =~ /OPToolbar1Passwd/
  end

  def button_installed_automatically?
    r = `defaults read #{bundle_path} OPToolbar1PasswdDisplayed 2>&1`
    return false if r == ""
    return r == "1\n"
  end
  
  def autofill
    `defaults read com.apple.Safari | grep AutoFill`
  end
  
  def tests
    Print.start_section("Safari")
    print "Version: #{version}"
    if build_number_valid?(build_number)
      print "Build #{build_number} is in supported range of #{min_supported} to #{max_supported}."
    else
      print "Build #{build_number} is not supported. Build number must be between #{min_supported} and #{max_supported}."
    end
    print "Toolbar button index: #{toolbar_button_index}"
    print "Extension loaded: #{extension_loaded}"
    print "Toolbar Configuration: #{toolbar_configuration}"
    print "Safari AutoFill settings:\n#{autofill}"
    
    if File.exists?("/usr/bin/codesign")
      sig = `/usr/bin/codesign -vvvvv /Applications/Safari.app 2>&1`
      print "Safari Signature: #{sig}"
    end
    
    Print.end_section
  end
  
end

class OmniWeb < Browser
  
  def name
    'OmniWeb.app'
  end
  
  def bundle_path
    'com.omnigroup.OmniWeb5'
  end
  
  def button_in_toolbar?
    tb = `defaults read com.omnigroup.OmniWeb5 "NSToolbar Configuration BrowserWindow" 2>&1`
    return tb =~ /OPToolbar1Passwd/
  end

  def button_installed_automatically?
    r = `defaults read #{bundle_path} OPToolbar1PasswdDisplayed 2>&1`
    return false if r == ""
    return r == "1\n"
  end
  
  def autofill
    `defaults read com.omnigroup.OmniWeb5 2>&1 | grep AutoFill`
  end
  
  def tests
    Print.start_section("OmniWeb")
    print "Version: #{version}"
    print "Toolbar button index: #{toolbar_button_index}"
    print "Extension loaded: #{extension_loaded}"
    print "Toolbar Configuration: #{toolbar_configuration}"
    print "OmniWeb AutoFill settings:\n#{autofill}"
    Print.end_section
  end
end

class NetNewsWire < Browser
  def name
    'NetNewsWire.app'
  end
  
  def bundle_path
    'com.ranchero.NetNewsWire'
  end
  
  def tests
    Print.start_section("NetNewsWire")
    print "Version: #{version}"
    Print.end_section
  end
end

class Fluid < Browser
  def name
    'Fluid.app'
  end
  
  def bundle_path
    'com.fluidapp.Fluid'
  end
  
  def tests
    Print.start_section("Fluid")
    print "Version: #{version}"
    
    instances = `ls "#{HOME}/Library/Preferences/com.fluidapp.FluidInstance*" 2>/dev/null`.split("\n")
    if instances.length < 1
      print "No Fluid instances created."
      return
    end
    
    instances.each { |instance| 
      name = $1 if instance =~ /com\.fluidapp\.FluidInstance\.(.*)?\.plist/
      path = "com.fluidapp.FluidInstance.#{name}"
      print "Fluid instance #{name}"
    }
    Print.end_section
  end
end

class Camino < Browser
  
  def name
    'Camino.app'
  end
  
  def bundle_path
    'org.mozilla.camino'
  end

  def toolbar_installed?
    tb = `defaults read org.mozilla.camino 2>&1`
    return tb =~ /OPToolbar1Passwd,/
  end

  def tests
    Print.start_section("Camino")
    print "Version: #{version}"
    print "Toolbar button index: #{toolbar_button_index}"
    print "Extension loaded: #{extension_loaded}"
    print "Toolbar Configuration: #{toolbar_configuration}"
    Print.end_section
  end
end

class Firefox < Browser
  attr_accessor :profiles, :profile_ini_contents
  def name
    "Firefox.app"
  end
  
  def bundle_path
    "org.mozilla.Firefox"
  end
  
  def load_profiles
    begin
      info = File.open("#{HOME}/Library/Application Support/Firefox/Profiles.ini") 
      lines = info.read
      @profile_ini_contents = lines
      
      result = ""
      @profiles = []
      current = {}
      lines.each { |l|
        result = "#{result}\n    #{l.inspect}"
        
        if l =~ /\[(.*)\]$/
          @profiles << current unless current.empty?
          current = {}
        end
        
        unless current.nil?
          current[:name] = $1 if l =~ /^Name=(.*)/
          current[:path] = $1 if l =~ /^Path=(.*)/
          current[:relative] = $1 == "1" if l =~ /^IsRelative=(.*)/
        end
      }
      @profiles << current unless current.nil?
    rescue
      print("Error occured while reading the Profiles.ini file: #{$!}")
    end
    
  end

  def tests
    Print.start_section("Firefox")
    load_profiles
    
    print "Version: #{version}"
    print "Profiles.ini contents:<br/><blockquote>#{@profile_ini_contents}</blockquote>"
    
    print "Application Support Files"
    Print.start_section
    Print.dir("#{HOME}/Library/Application Support/Firefox")
    Print.end_section
    
    print "Profile Files"
    Print.start_section
    Print.dir("#{HOME}/Library/Application Support/Firefox/Profiles", 't')
    Print.end_section

    if @profiles.nil?
      print "No Profiles found"
    else
      @profiles.each { |profile| 
        print "Profile '#{profile[:name]}' details"
        Print.start_section
        path = profile[:relative] ? "#{HOME}/Library/Application Support/Firefox/#{profile[:path]}" : profile[:path]
        path = File.expand_path(path)

        print "1Password Extension File Contents\n"
        Print.file_contents("#{path}/extensions/firefox3@1password.com")
        Print.file_contents("#{path}/extensions/firefox4@1password.com")
        
        print "Installed Extensions"
        Print.dir("#{path}/extensions", 't')

        print "Root Profile Folder Contents"
        Print.dir(path, 't')
        Print.end_section
      }
    end

    print "Crash Logs"
    Print.start_section
    Print.dir("#{HOME}/Library/Application Support/Firefox/Crash Reports", 'tr')
    print "<b>Crash Logs from #{HOME}/Library/Application Support/Firefox/Crash Reports/pending</b>\n"
    Dir.glob("#{HOME}/Library/Application Support/Firefox/Crash Reports/pending/*").reject { |item| item =~ /\.dmp$/ }.each { |file| Print.file_contents(file) }

    print "<b>Crash Logs from #{HOME}/Library/Application Support/Firefox/Crash Reports/submitted</b>\n"
    Dir.glob("#{HOME}/Library/Application Support/Firefox/Crash Reports/submitted/*").reject { |item| item =~ /\.dmp$/ }.each { |file| Print.file_contents(file) }

    Print.end_section
    Print.end_section
  end
end

class OnePasswd < BaseTestClass
  def version
    begin
      info = File.new("/#{COCOA_BUNDLE_PATH}/Contents/Info.plist")
      lines = info.read
      build = $1 if lines =~ /<key>CFBundleVersion<\/key>$\s*<string>(.*?)<\/string>/i
      version = $1 if lines =~ /<key>CFBundleShortVersionString<\/key>$\s*<string>(.*?)<\/string>/i
      
      return "#{version} (build ##{build})"
    rescue
      return "not installed in /Applications/1Password.app"
    end
  end

  def tests()
    Print.start_section("1Password Settings")
    print "Version: #{version}"
    print "Actual Bundle path from Cocoa: #{COCOA_BUNDLE_PATH}"
    print "1Password folder (as per User Defaults): #{`defaults read ws.agile.1Password | grep AppPath`}"
    print "Build # (as per User Defaults): #{`defaults read ws.agile.1Password | grep Build`}"
    Print.start_section("Architectures")
    print %x{file "#{COCOA_BUNDLE_PATH}/Contents/MacOS/1Password"}
    Print.end_section
    Print.start_section("Extensions")
    print "<div style='padding-left:25px;'>#{`defaults read ws.agile.1Password | grep 'Extension Enabled'`}</div>"
    Print.end_section
    print "Autosave:#{`defaults read ws.agile.1Password | grep 'autosave'`}"
    print "Autosubmit:#{`defaults read ws.agile.1Password | grep 'autosubmit'`}"
    print "DisableAutoSavePopup:#{`defaults read ws.agile.1Password | grep 'disableAutoSavePopup'`}"
    print "SULastCheckTime:#{`defaults read ws.agile.1Password | grep 'SULastCheckTime'`}"
    Print.end_section
    
    Print.start_section("1Password Versioned Extensions")
    Print.dir("#{HOME}/Library/Application Support/1Password/Extensions")
    
    valid, msg = OnePasswd.verify_versioned_extensions
    if valid
      print "All versioned extension files match manifest."
    else
      print "Versioned extension files do not match manifest: #{msg}"
    end
    Print.end_section
    
    Print.start_section("Installed Scripting Additions")
    print "~/Library/ScriptingAdditions"
    
    valid, msg = OnePasswd.verify_scripting_addition
    if valid
      print "All 1Password Addition files match manifest."
    else
      print "1Password Addition files do not match manifest: #{msg}"
    end
    
    Print.dir(File.expand_path('~/Library/ScriptingAdditions'))
    
    print %x{file "#{HOME}/Library/ScriptingAdditions/1Password Addition.osax/Contents/MacOS/1Password Addition"} if File.exist? "#{HOME}/Library/ScriptingAdditions/1Password Addition.osax/Contents/MacOS/1Password Addition"
    print %x{file "#{HOME}/Library/ScriptingAdditions/1Password Addition.osax/Contents/MacOS/1Password Addition"} if File.exist? "#{HOME}/Library/ScriptingAdditions/1Password Addition Leopard.osax/Contents/MacOS/1Password Addition Leopard"
    
    print "/Library/ScriptingAdditions"
    Print.dir('/Library/ScriptingAdditions')
    Print.end_section
    
    Print.start_section("Installed Internet Plugins")
    Print.dir('/Library/Internet Plug-Ins', 't')
    Print.dir("#{HOME}/Library/Internet Plug-Ins", 't')
    Print.end_section

    Print.start_section("License information from User Defaults")
    print ns1PUserDefault("License") # `defaults read ws.agile.1Password License`
    Print.end_section

    Print.start_section("License Files")
    Print.dir("#{HOME}/Library/Application Support/1Passwd/License", 't')
    Print.dir("#{HOME}/Library/Application Support/1Password/License", 't')
    Print.end_section
    
    Print.start_section("License File Contents")
    dir = File.expand_path("~/Library/Application Support/1Password/License")
    files = Dir["#{dir}/*.plist"] + Dir["#{dir}/*.awslicense"] + Dir["#{File.expand_path("~/Library/Application Support/1Passwd/License")}/*.plist"] + Dir["#{File.expand_path("~/Library/Application Support/1Passwd/License")}/*.awslicense"]
    files.each { |plist|
      print "#{plist} contents:"
      print "<pre>"
      print File.new(plist).readlines.to_s.htmlize
      print "</pre>"
    }
    Print.end_section
    
    verify_installation
  end
  
  # @return is_valid, error_message
  def OnePasswd.verify_scripting_addition(manifest_file_path = "#{COCOA_BUNDLE_PATH}/.manifest")
    manifest = File.readlines(manifest_file_path) rescue []
    return false, "Manifest file missing" if manifest.length == 0
    
    scripting_addition_name = Sys.is_snow_leopard? ? "1Password Addition.osax" : "1Password Addition Leopard.osax"
    scripting_additions_folder = File.expand_path("~/Library/ScriptingAdditions")
    manifest.each do |line|
      file, expected_size = $1, $2 if line =~ /^([^,]+?), ([^,]+?)$/
      next if file.to_s.length == 0 || expected_size.to_s.length == 0
      
      addition_file = $1 if file =~ /Contents\/Extensions\/#{scripting_addition_name}\/(.*)/
      next if addition_file.nil?

      full_path = File.join(scripting_additions_folder, scripting_addition_name, addition_file)
      return false, "#{full_path.inspect} is missing" unless File.exists?(full_path)
      
      actual_size = File.size(full_path).to_s rescue ""
      return false, "#{full_path.inspect} is #{actual_size.inspect} but should be #{expected_size.inspect}" unless expected_size == actual_size
    end
    return true
  end

  # @return is_valid, error_message
  def OnePasswd.verify_versioned_extensions(manifest_file_path = "#{COCOA_BUNDLE_PATH}/.manifest")
    manifest = File.readlines(manifest_file_path) rescue []
    return false, "Manifest file missing" if manifest.length == 0
    
    versioned_ext_folder = File.expand_path("~/Library/Application Support/1Password/Extensions") 
    buildnum = OnePasswdAgent.buildnum

    manifest.each do |line|
      file, expected_size = $1, $2 if line =~ /^([^,]+?), ([^,]+?)$/
      next if file.to_s.length == 0 || expected_size.to_s.length == 0
      next unless file =~ /^Contents\/Extensions\//
      next if file =~ /^Contents\/Extensions\/1Password Addition/

      ext_file = $1 if file =~ /Contents\/Extensions\/(.*)/
      next if ext_file.nil?

      full_path = File.join(versioned_ext_folder.to_s, buildnum.to_s, ext_file.to_s) rescue nil
      return false, "#{full_path.inspect} is missing" unless full_path and File.exists?(full_path)
      
      actual_size = File.size(full_path).to_s rescue ""
      return false, "#{full_path.inspect} is #{actual_size.inspect} but should be #{expected_size.inspect}" unless expected_size == actual_size
    end
    return true
  end

  def verify_installation(manifest_file_path = "#{COCOA_BUNDLE_PATH}/.manifest")
    Print.start_section("1Password installation validation")
    is_valid = true
    
    manifest = File.readlines(manifest_file_path) rescue []
    if manifest.length == 0
      print "Manifest file (#{manifest_file_path}) not found. Skipping validation."
      Print.end_section
      return
    end
    expected_files = []
    
    manifest.each do |line|
      fields = line.strip.split(',')
      if fields.length > 2
      file, expected_size = fields.slice(0...-1).join(',').strip, fields[-1].strip
      else
      file, expected_size = fields[0].strip, fields[1].strip
      end
      if file.to_s.length == 0 || expected_size.to_s.length == 0
        print "Ignoring unexpected line in manifest: #{line.inspect}"
        next
      end

      expected_files << file
      unless File.exists?(File.join(COCOA_BUNDLE_PATH, file))
        print "#{file.inspect} is missing"
        is_valid = false
      end
    
      actual_size = File.size(File.join(COCOA_BUNDLE_PATH, file)).to_s rescue ""
      unless expected_size == actual_size
        print "#{file.inspect} is #{actual_size.inspect} but should be #{expected_size.inspect}"
        is_valid = false
      end
    end
    
    Dir.chdir(COCOA_BUNDLE_PATH) do
      (Dir["**/*"]-expected_files).each do |f| 
        next unless File.file?(f)
        next if f =~ /Growl.framework/
        print "Found unexpected file: #{f.inspect}"
        is_valid = false
      end
    end

    print is_valid ? "Manifest validation passed: all files exist, their sizes match, and no extra files were found." : "Validation was not 100%. Inconsistencies listed above."

    Print.end_section
  end
end

class Keychain < BaseTestClass
  
  def agile_keychain?
    return keychain_type =~ /AGHtmlDatabase/i
  end
  
  def keychain_type
    result = ns1PUserDefault('databaseClass')
    result ||= ns1PUserDefault('defaultDatabaseClass')
    result ||= "AGHtmlDatabase"
    return result
  end
  
  def legacyOSXKeychainBackupFolder
    return "#{HOME}/Library/Keychains/backup"
  end

  def agileBackupFolder
    path = ns1PUserDefault('keychainBackupFolderPath')
    return "#{HOME}/Library/Application Support/1Password/Backups" if path.nil?
    return path
  end
  
  def agileKeychainFolder
    folder = ns1PUserDefault('AgileKeychainLocation')
    folder = "#{HOME}/Library/Application Support/1Password/1Password.agilekeychain" if folder.nil?
    return File.expand_path(folder) 
  end

  def tests()
    Print.start_section("Keychain Information")
    print("Keychain type: #{keychain_type}")
    print("Raw defaults value for databaseClass: #{ns1PUserDefault("databaseClass").inspect}")
    print("Raw defaults value for defaultDatabaseClass: #{ns1PUserDefault("defaultDatabaseClass").inspect}")
    print("AgileKeychainLocation: #{agileKeychainFolder}")
    print("LockAgileKeychainOnIdle: #{ns1PUserDefault('LockAgileKeychainOnIdle')}")
    print("AgileKeychainLockTimeout: #{ns1PUserDefault('AgileKeychainLockTimeout')}")
    print("LockAgileKeychainOnSleep: #{ns1PUserDefault('LockAgileKeychainOnSleep')}")
    print("LockAgileKeychainOnScreenSaver: #{ns1PUserDefault('LockAgileKeychainOnScreenSaver')}")
    print("LockWhenAllAppsClosed: #{ns1PUserDefault('LockWhenAllAppsClosed')}")
    print("AlwaysStart1PasswordLocked: #{ns1PUserDefault('AlwaysStart1PasswordLocked')}")
    
    keysfile = "#{agileKeychainFolder}/data/default/1password.keys"
    if File.exists?(keysfile)
      keys = File.open(keysfile).read
      print("Encryption key IDs: #{$1.gsub(/\s+/, '').gsub(/</, '&lt;').gsub(/>/, '&gt;')}") if keys =~ /<dict>(.*?)<key>list/m
    else
      print("File [#{keysfile}] does not exist.")
    end 

    Print.stat("#{HOME}/Library/Keychains")
    Print.dir("#{HOME}/Library/Keychains", 't')

    Print.stat(agileKeychainFolder)
    Print.dir(agileKeychainFolder, 't')
    Print.dir("#{agileKeychainFolder}/data", 't')
    Print.dir("#{agileKeychainFolder}/data/default", 't')

    Print.start_section("Keychain Backups")
    
    case ns1PUserDefault('keychainBackupFrequency')
      when "0" then print("Backup Frequency: Daily")
      when "1" then print("Backup Frequency: Weekly")
      when "2" then print("Backup Frequency: Monthly")
      else print("Backup Frequency: Daily")
    end
    
    print("Number of Backups to Keep: #{ns1PUserDefault('keychainRotationFrequency').nil? ? 50 : ns1PUserDefault('keychainRotationFrequency')}")
    Print.stat(legacyOSXKeychainBackupFolder)
    Print.dir(legacyOSXKeychainBackupFolder, 't')
    Print.stat(agileBackupFolder)
    Print.dir(agileBackupFolder, 't')
    Print.end_section
    
    Logs.printLog(:file => "#{HOME}/Library/Preferences/com.apple.security.plist")
    Print.end_section
  end
end

class OnePasswdAgent < BaseTestClass
  
  def OnePasswdAgent.buildnum
    return $1 if OnePasswdAgent.full_version =~ /.*\(build #(\d+)\)/
  end
  
  def OnePasswdAgent.full_version(agent_install_path='~/Library/Application Support/1Password/Agent/1PasswordAgent.app')
    begin
      lines = File.new(File.expand_path("#{agent_install_path}/Contents/Info.plist")).read rescue nil
      build = $1 if lines =~ /<key>CFBundleVersion<\/key>$\s*<string>(.*?)<\/string>/i
      version = $1 if lines =~ /<key>CFBundleShortVersionString<\/key>$\s*<string>(.*?)<\/string>/i
      return "#{version} (build ##{build})"
    rescue
      return "not installed in #{path}"
    end
  end

  def tests()
    Print.start_section("1Password Agent")
    print "Agent started? #{processStarted?("1PasswordAgent")}"
    
    Print.start_section("Launch Agents Information")
    Print.stat(File.expand_path('~/Library/LaunchAgents'))
    Print.stat(File.expand_path('~/Library/LaunchAgents/ws.agile.1PasswordAgent.plist'))
    print "~/Library/LaunchAgents has the following ACLs: [" +  Dir.new("#{HOME}/Library/LaunchAgents").list_acls + "]" rescue nil
    print "/Library/LaunchAgents has the following ACLs: [" + Dir.new("/Library/LaunchAgents").list_acls + "]" rescue nil
    Print.end_section
    
    Print.start_section("1PasswordAgent details")
    print "Installed version: #{OnePasswdAgent.full_version}"
    print "Version in #{COCOA_BUNDLE_PATH}: #{OnePasswdAgent.full_version("#{COCOA_BUNDLE_PATH}/Contents/Executables/1PasswordAgent.app")}"
    Print.stat File.expand_path('~/Library/Application Support/1Password')
    Print.stat File.expand_path('~/Library/Application Support/1Password/Agent')
    Print.stat File.expand_path('~/Library/Application Support/1Password/Agent/1PasswordAgent.app')
    Print.stat File.expand_path('~/Library/Application Support/1Password/Agent/1PasswordAgent.app/Contents/MacOS/1PasswordAgent')
    Print.end_section
    
    Print.start_section("launchctl list ws.agile.1PasswordAgent")
    print `launchctl list ws.agile.1PasswordAgent 2>&1`
    Print.end_section

    Print.start_section("Contents of ws.agile.1PasswordAgent.plist")
    print `cat "#{HOME}/Library/LaunchAgents/ws.agile.1PasswordAgent.plist"`.htmlize
    Print.end_section
    Print.end_section
  end
end

class OnePasswdUpdater < BaseTestClass
  def version(path)
    begin
      path = File.expand_path(path)
      
      info = File.new("#{path}/Contents/Info.plist")
      lines = info.read
      build = $1 if lines =~ /<key>CFBundleVersion<\/key>$\s*<string>(.*?)<\/string>/i
      version = $1 if lines =~ /<key>CFBundleShortVersionString<\/key>$\s*<string>(.*?)<\/string>/i
      
      return "#{version} (build ##{build})"
    rescue
      return "not installed in #{path}"
    end
  end

  def tests()
    Print.start_section("1Password Updater")
    
    Print.start_section("Launch Agents Information")
    Print.stat(File.expand_path('~/Library/LaunchAgents'))
    Print.stat(File.expand_path('~/Library/LaunchAgents/ws.agile.1PasswordUpdater.plist'))
    print "~/Library/LaunchAgents has the following ACLs: [" +  Dir.new("#{HOME}/Library/LaunchAgents").list_acls + "]" rescue nil
    print "/Library/LaunchAgents has the following ACLs: [" + Dir.new("/Library/LaunchAgents").list_acls + "]" rescue nil
    Print.end_section
    
    Print.start_section("1PasswordUpdater details")
    print "Version: #{version("#{COCOA_BUNDLE_PATH}/Contents/Executables/1PasswordUpdater.app")}"
    Print.stat File.expand_path("#{COCOA_BUNDLE_PATH}/Contents/MacOS/1PasswordUpdater.app")
    Print.stat File.expand_path("#{COCOA_BUNDLE_PATH}/Contents/MacOS/1PasswordUpdater.app/Contents/MacOS/1PasswordUpdater")
    Print.end_section
    
    Print.start_section("launchctl list ws.agile.1PasswordUpdater")
    print `launchctl list ws.agile.1PasswordUpdater 2>&1`
    Print.end_section

    Print.start_section("Contents of ws.agile.1PasswordUpdater.plist")
    print `cat "#{HOME}/Library/LaunchAgents/ws.agile.1PasswordUpdater.plist" 2>&1`.htmlize
    Print.end_section
    Print.end_section
  end
end

class Logs
  def Logs.collect
    begin
      _collect
    rescue
      Print.exception(self, $!)
    end
  end
  
  def Logs._collect
    logFolder = File.expand_path('~/Library/Logs/1Password')
    Print.header("Logs from #{logFolder}")
    
    if File.exists?(logFolder)
      Dir.foreach(logFolder) do |x| 
        next if x == "." || x == ".." || x == "keychain_backup.log" || x == "firefox-import-log.plist"
        printLog(:file => "#{logFolder}/#{x}") if File.should_print?("#{logFolder}/#{x}")
      end
    end

    crashFolder = File.expand_path("#{HOME}/Library/Logs/CrashReporter")
    Print.header("Crash Logs")
    Print.dir(crashFolder, 't')
    if File.exists?(crashFolder)
      Dir.foreach(crashFolder) do |log|
        if log =~ /securityd/ || log =~ /Safari/ || log =~ /Camino/ || log =~ /irefox/ || log =~ /1Password/ || log =~ /DEVONa/ || log =~ /OmniW/ || log =~ /WebKit/ || log =~ /icab/i
          printLog(:file => "#{crashFolder}/#{log}", :unlimited => true) if File.should_print?("#{crashFolder}/#{log}")
        end
      end
    end

    # Print.header("Mobile Device Crash Logs")
    # Dir[File.expand_path('~/Library/Logs/CrashReporter/MobileDevice/*/*/1Password*')].each
    
    printLog(:file => '/var/log/system.log')

    Print.header("Firewall Logs")
    printLog(:file => '/var/log/ipfw.log')
    printLog(:file => '/var/log/appfirewall.log')
  end
  
    def Logs.printLog(opts)
        filename = File.expand_path(opts[:file])
        unlimited = opts[:unlimited] == true
        
        unless File.exists?(filename)
            Print.header("File #{filename} does not exist.")
        else
            Print.header("Contents of #{filename}")
            print "<pre class='log'>"
            if unlimited
                print `cat "#{filename}"`.htmlize
            else
                print `tail -400 "#{filename}"`.htmlize
            end
            print "</pre>"
        end
    end
end

class File
  def File.should_print?(filename)
    result = false
    
    f = File.new(filename)
    begin
      t = Time.now-5184000 # 60*24*60*60 Days*Hours*Minutes*Seconds
      n = f.mtime <=> t
      result = (n >= 0)
    rescue
      result = false
    ensure
      return result
    end
  end
end

class Dir
  def list_acls
    # Found here: http://www.macosxhints.com/comment.php?mode=view&cid=101947
    # There is one *very* edge case that will give a false ACL, but I don't think that's something we should worry about.
    `ls -alde "#{self.path}" 2> /dev/null | awk '{if(match($0,"^ [0-9]+:")>0){if(prevline!=""){print prevline;prevline=""}print $0}else{prevline=$0}}'`
  end
end

class VirusBarrier
  def self.installed?
    File.exist?("/Library/LaunchDaemons/com.intego.commonservices.daemon.plist") || File.exist?("/Library/LaunchDaemons/com.intego.task.manager.daemon.plist") || File.exist?("/Library/LaunchDaemons/com.intego.task.manager.daemon") || File.exist?("/Library/LaunchDaemons/com.intego.commonservices.icalserver.plist") || File.exist?("/Library/LaunchAgents/com.intego.task.manager.notifier.plist") || File.exist?("/Library/Intego/IntegoStatusItem.bundle/Contents/Resources/IntegoStatusItemEnabler") || File.exist?("/Library/StartupItems/IntegoCommon")
  end
  
  def self.started?
    %x{ps -Ajc | grep -i virusbarrier}.chomp != ""
  end
end
if ARGV[0] =~ /BUNDLE_PATH=(.*)/
  COCOA_BUNDLE_PATH = $1
elsif Dir.pwd =~ /(1P.*?\.app)/
  COCOA_BUNDLE_PATH = Dir.pwd.gsub!('/Contents/Scripts','')
else
  COCOA_BUNDLE_PATH = "/Applications/1Password.app"
end

@@result = ""
@@table_of_contents = ""
@@sys = Sys.new
@@sys.tests
@@logger = Logger.new(File.expand_path("~/Library/Logs/1Password/diagnostics.log"))
begin
  OnePasswd.new.tests
  Keychain.new.tests
  OnePasswdAgent.new.tests
  OnePasswdUpdater.new.tests
  Simbl.new.tests
  Safari.new.tests
  OmniWeb.new.tests
  Camino.new.tests
  Firefox.new.tests
  NetNewsWire.new.tests
  Fluid.new.tests

  details = @@result
  @@result = ""
  Logs.collect

  logs = @@result

  summary = Summary.summary
  configuration = Configuration.summary

  template = ERB.new(RESULT_TEMPLATE)
  template = template.result(binding)
  folder = File.expand_path("#{HOME}/Library/Application Support/1Password/Diagnostics")
  if ARGV.to_s =~ /-O=stdout/
    STDOUT.write template
  else
    File.makedirs(folder)
    File.open("#{folder}/1Password Troubleshooting.html", 'w') {|f| f.write(template) }
    %x{open "#{folder}/1Password Troubleshooting.html"} if(ENV["TM_FILEPATH"])
  end
rescue Exception => e
  @@logger.error(e.message)
  @@logger.error(e.backtrace.join("\n"))
  puts "Generation failed. See ~/Library/Logs/1Password/diagnostics.log for details"
end
