#!/usr/bin/perl
#
# Catorise - create and maintain menus corresponding to
# ~~~~~~~~   package sections.            (c) Andrew Flegg 2010.
#                                         Released under the Artistic Licence.

BEGIN { use lib '/opt/catorise/lib'; }

use strict;
use warnings;
use Config::Tiny;
use Locale::gettext;

# -- Valid sections...
#
my %validSections = map { $_ => 1} qw(
  desktop development education games graphics other ovi
  multimedia navigation network office science system utilities
  _root
);


# -- Section overrides...
#
my %sectionOverrides = (
  'web'              => 'network',
  'audiovideo'       => 'multimedia',
  'game'             => 'games',
  'settings'         => 'system',
  'utility'          => 'utilities',
);


# -- Read pre-stored menu...
#
my %overrides = ();
if (open(IN, "</opt/catorise/menu")) {
  %overrides = map { chomp; split /:\s*/ } <IN>;
  close(IN);
}


# -- Index packages as quickly as possible...
#
my %packageIndex = ();
foreach my $line (`fgrep /usr/share/applications/hildon/ /var/lib/dpkg/info/*.list`) {
  chomp($line);
  my ($list, $desktop) = split /:/, $line;
  my ($listPackage) = $list =~ /([^\/]+)\.list$/;

  $packageIndex{$desktop} = $listPackage if $desktop =~ m{^/usr/share/applications/hildon/[^/]+\.desktop$};
}

my %oviApps = ();
if (open(IN, "</var/lib/apt/lists/downloads.maemo.nokia.com_fremantle_ovi_._Packages")) {
  while (<IN>) {
    chomp;
    next unless /^Package: (.*)$/;
    $oviApps{lc($1)} = 'ovi';
  }
  close(IN);
}


# -- Back-up hildon.menu if necessary...
#
my $needRestart = 0;
unless (-f '/etc/xdg/menus/hildon.catorise') {
  system('cp', '/etc/xdg/menus/hildon.menu',
               '/etc/xdg/menus/hildon.catorise');
  $needRestart = 1;
}


# -- Set the locale for proper sorting...
#
bindtextdomain("hildon-application-manager-categories", "/usr/share/locale");
bindtextdomain("maemo-af-desktop", "/usr/share/locale");


# -- Loop over all desktop files...
#
my %sections = ( _root => [] );
my @allApps = ();
my $excludeFromAll = '';

open(OUT, ">/opt/catorise/menu") or die "Unable to store new menu: $!";
$| = 1;
foreach my $desktop (</usr/share/applications/hildon/*.desktop>) {
  $desktop = readlink($desktop) || $desktop;
  next if $desktop =~ /\/catorise-/;
  print '#';

  # Check it's a launcher icon
  my $configFull = Config::Tiny->read($desktop);
  my $config     = $configFull->{'Desktop Entry'};
  next unless $config;
  next if lc($config->{'Type'} || '') ne 'application';
  next if lc($config->{'NoDisplay'} || 'false') eq 'true';

  # Check if there's an override...
  my ($name)  = $desktop =~ /([^\/]+)\.desktop$/;
  my $section = $overrides{$name};

  # Temporary fix for MB#8111
  if (lc($config->{'X-Maemo-Prestarted'} || '') eq 'always') {
    my $shim = "/usr/share/applications/hildon/catorise-$name.desktop";
    unless (-f $shim) {
      foreach my $i (keys(%$configFull)) {
        delete $configFull->{$i} unless $i eq 'Desktop Entry';
      }
      foreach my $i (keys(%$config)) {
        delete $config->{$i} if $i =~ /^(MimeType|X-Maemo|X-Hildon|X-Osso-)/;
      }
      $config->{'Exec'} = "dbus-send --dest=com.nokia.HildonDesktop.AppMgr /com/nokia/HildonDesktop/AppMgr com.nokia.HildonDesktop.AppMgr.LaunchApplication string:$name";
      $configFull->write($shim);
    }

    $name = "catorise-$name";
  }

  # -- Check categories in .desktop...
  #
  if (!$section) {
    foreach my $category (split /\;/, lc($config->{'Categories'} || '')) {
      $category = $sectionOverrides{$category} || $category;
      if ($validSections{$category}) {
        $section = $category;
        last;
      }
    }
  }

  # -- Find using dpkg...
  #
  if (!$section or $section eq 'other') {
    my $package = $packageIndex{$desktop};
    if ($package) {
      $section = (grep { /^Section: / } `dpkg -s "$package"`)[0];
      $section =~ s/.*?: (.+?\/)?//;
      chomp($section);
 
      # Is it Ovi?
      $section = $oviApps{$package} || '' unless $validSections{$section};
    } else {
      $section = '';
    }
  }

  # -- Additional heuristics...
  #
  $section = $sectionOverrides{$section} || $section || '';
  $section = 'other' unless $validSections{$section};

  # -- Store the shortcut...
  #
  my $entry = { name    => $config->{'Name'} || $name,
                file    => $name,
                section => $section };

  # ovi.desktop doesn't have X-Text-Domain variable set - workaround it
  $config->{'X-Text-Domain'} ||= 'maemo-af-desktop' if lc($name) eq 'ovi';

  if ($config->{'X-Text-Domain'}) {
    textdomain($config->{'X-Text-Domain'});
    $entry->{name} = &l10n($entry->{name});
  }

  push @{ $sections{$section} }, $entry;
  if ($name =~ s/^catorise-//) {
    $excludeFromAll .= "    <Filename>$entry->{file}.desktop</Filename>\n";
    $entry = { name => $entry->{name}, file => $name, section => $section };
  }
  push @allApps, $entry;
  print OUT "$name: $section\n";
}
close(OUT) or die "Unable to close new menu: $!";


# -- Output the menu...
#
open(OUT, ">/etc/xdg/menus/hildon.menu") or die "Unable to write to menu: $!";
print OUT <<EOH;
<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN"
 "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">

<Menu>

  <Name>Main</Name>

  <AppDir>/usr/share/applications/hildon</AppDir>
  <DirectoryDir>/opt/catorise/share</DirectoryDir>
EOH

my $rootEntries = &build_entries($sections{'_root'});
my $allEntries  = &build_entries(\@allApps);
print OUT "  <Include>\n$rootEntries   </Include>\n  <Layout>\n    <Menuname>all</Menuname>\n";
my $menus = '';

# -- Sort the menu folders using the localized names...
#
textdomain("hildon-application-manager");
foreach my $s (sort { &l10n($a, 'ai_category_') cmp &l10n($b, 'ai_category_') } keys %sections) {
  next if $s eq '_root';
  print OUT "    <Menuname>$s</Menuname>\n" unless $s eq 'other';
  
  my $sectionEntries = &build_entries($sections{$s});
  $sectionEntries =~ s!^(.*)( +<Filename>ovi.desktop</Filename>\n)(.*)$!$2$1$3!s;
  $menus .= "  <Menu>\n    <Name>$s</Name>\n".
            "    <Directory>$s.directory</Directory>\n".
            "    <Include>\n$sectionEntries    </Include>\n".
            "    <Layout>\n$sectionEntries    </Layout>\n".
            "  </Menu>\n";
}
print OUT <<EOF;
    <Menuname>other</Menuname>
    $rootEntries
  </Layout>
$menus
  <Menu>
    <Name>all</Name>
    <Directory>all.directory</Directory>
    <Include>
      <All/>
    </Include>
    <Layout>
$allEntries
    <Merge type="all"/>
    </Layout>
    <Exclude>
$excludeFromAll
    </Exclude>
  </Menu>
</Menu>                    
EOF
close(OUT) or die "Unable to close menu: $!";


# -- Restart hildon-desktop if necessary...
#
system("killall", "hildon-desktop") if $needRestart;
exit;


# ===========================================================================
# Sort and return entries
# ---------------------------------------------------------------------------
sub build_entries {
  my ($entries) = @_;
  my $result = '';
  foreach my $entry (sort { lc($a->{name}) cmp lc($b->{name}) } @$entries) {
    $result .= "    <Filename>$entry->{file}.desktop</Filename>\n";
  }
  return $result;
}


# ===========================================================================
# Return the lower-case and trimmed version of the given key
# ---------------------------------------------------------------------------
sub l10n {
  my ($key, $prefix) = @_;
  my $fullKey = $prefix ? $prefix.$key : $key;
  my $result = gettext($fullKey);
  $result = $key if $result eq $fullKey;
  $result =~ s/^\s+//g;
  return lc($result);
}

