AppExcludeEntry

1. Overview

The AppExcludeEntry class represents and manages application-specific files and directories that can be converted into include or exclude patterns.

The class scans Linux user home directories, identifies hidden configuration files and application data directories, groups related paths under a normalized application name, and generates matching patterns for both regular users and the root account.

The implementation is written in Vala and relies on GLib, GIO, Gee collections, and multiple TeeJee utility modules.

The class integrates the following components:

  • GLib.Object – base object type and GLib runtime integration
  • Gio.File – filesystem access and directory enumeration
  • Gio.FileEnumerator – iteration over directory contents
  • Gio.FileInfo – retrieval of filesystem entry information
  • Gee.ArrayList – storage of paths, patterns, and result entries
  • Gee.HashMap – centralized application-entry registry
  • TeeJee.FileSystem – filesystem utility functions such as dir_exists() and file_basename()
  • TeeJee.Logging – debug and error logging

2. Class Definition

The class derives from GLib.Object:

public class AppExcludeEntry : GLib.Object

Each instance represents a logical application or configuration group. A single entry can contain multiple filesystem paths and multiple generated filter patterns.


3. Instance Properties

3.1 Application Name

public string name = "";

The name property contains the normalized application or configuration name used to identify the entry inside the shared application map.

The name is normally derived from the basename of a discovered file or directory. Known file extensions and an initial dot are removed before the name is stored.


3.2 Include or Exclude Mode

public bool is_include = false;

The is_include property determines whether generated patterns are include rules or exclude rules.

  • false – generates a standard exclusion-style pattern
  • true – prefixes the generated pattern with +

3.3 File-Type Flag

public bool is_file = false;

The is_file field is intended to indicate whether the entry represents a file instead of a directory.

The discovery methods calculate this information through:

!dir_exists(item)

and pass it to add_item(). However, the current implementation does not assign the received value to the instance-level is_file field.


3.4 Enabled State

public bool enabled = false;

The enabled property indicates whether the application entry has been selected by the caller.

This state is updated by get_apps_list() using a list of selected application names.


3.5 Discovered Items

public Gee.ArrayList<string> items;

The items collection contains the user-relative filesystem paths associated with the application entry.

Example values:

~/.config/firefox
~/.mozilla
~/.local/share/firefox

Duplicate paths are prevented through an explicit contains() check before insertion.


3.6 Generated Patterns

public Gee.ArrayList<string> patterns;

The patterns collection contains the final include or exclude rules generated for root and regular user home directories.

For an item such as:

~/.config/example

the generated patterns are:

/root/.config/example
/home/*/.config/example

When the entry is configured as an include rule, the patterns become:

+ /root/.config/example
+ /home/*/.config/example

4. Shared Application Registry

public static Gee.HashMap<string, AppExcludeEntry> app_map;

The class uses a static Gee.HashMap as a centralized registry of all discovered application entries.

The map key is the normalized application name, while the associated value is the corresponding AppExcludeEntry object.

Because the map is static, all instances and static discovery methods operate on the same shared dataset.


5. Constructor

public AppExcludeEntry(string _name, bool _is_include = false)

The constructor initializes a new application entry and assigns:

  • The application name
  • The include/exclude mode
  • An empty item list
  • An empty pattern list

Initialization logic:

name = _name;
is_include = _is_include;

items = new Gee.ArrayList<string>();
patterns = new Gee.ArrayList<string>();

6. Tooltip Generation

6.1 tooltip_text()

public string tooltip_text()

The tooltip_text() method converts all paths stored in items into a newline-separated string suitable for display inside a graphical tooltip.

Processing steps:

  1. Iterate over every item
  2. Append the item followed by a newline
  3. Remove the final trailing newline
  4. Return the formatted string

Example output:

~/.config/example
~/.local/share/example
~/.example

If the entry contains no items, the method returns an empty string.


7. Registry Reset

7.1 clear()

public static void clear()

The clear() method resets the shared application registry.

Its behavior depends on the current state of app_map:

  • If the map has not yet been created, a new empty Gee.HashMap is allocated
  • If the map already exists, all existing entries are removed through app_map.clear()

The operation is recorded through the debug logger:

log_debug("AppExcludeEntry: clear()");

8. Home Directory Discovery

8.1 Scanning a Parent Home Directory

public static void add_app_exclude_entries_from_home(string home)

This method scans a parent directory containing one or more user home directories.

For each direct child found under the supplied path, it calls:

add_app_exclude_entries_from_path(item);

Before processing an entry, the method excludes temporary, backup, and log-related files whose names end with:

  • .lock
  • .log
  • .old
  • ~

If the supplied directory does not exist, the method returns without performing any operation.

Filesystem errors are caught and written to the application log through:

log_error(e.message);

8.2 Scanning an Individual User Home

public static void add_app_exclude_entries_from_path(string user_home)

This method performs the main application-data discovery process for a single user home directory.

It scans three separate locations:

  1. The top level of the user home directory
  2. The ~/.config directory
  3. The ~/.local/share directory

8.3 Top-Level Hidden Entries

During the first scan, only entries whose names begin with a dot are considered:

if (!name.has_prefix(".")){ continue; }

The following standard or non-application paths are excluded:

  • .config
  • .local
  • .gvfs
  • .thumbnails
  • .cache
  • .temp
  • .sudo_as_admin_successful

Temporary and generated files are also ignored when their names end with:

  • .lock
  • .log
  • .old
  • ~

Accepted entries are converted to user-relative paths:

~/.application-name

and passed to:

add_item(relpath, !dir_exists(item), false);

8.4 XDG Configuration Directory

The method separately scans:

~/.config

Every accepted child is represented using the following format:

~/.config/application-name

Entries ending in .lock, .log, .old, or ~ are skipped.


8.5 XDG Shared Data Directory

The method also scans:

~/.local/share

Every accepted child is represented using the following format:

~/.local/share/application-name

In addition to temporary and backup files, the following generic directories are excluded:

  • applications
  • Trash

This prevents desktop launcher data and the user trash directory from being treated as application-specific exclusion entries.


9. Entry Creation and Name Normalization

9.1 add_item()

public static void add_item(
    string item_path,
    bool is_file,
    bool is_include
)

The add_item() method converts a discovered path into an application entry and generates its corresponding patterns.

If the shared map does not yet exist, it is initialized before processing begins.


9.2 Basename Extraction

The initial application name is derived from the final component of the supplied path:

var name = file_basename(item_path);

For example:

~/.config/example.conf

initially produces:

example.conf

9.3 Known Extension Removal

The method removes one recognized suffix from the application name.

Supported suffixes include:

  • .ini
  • .sh
  • .json
  • .conf
  • .list
  • .xbel
  • .xbel.tbcache
  • .bz2
  • .old
  • .dirs
  • .locale
  • .dockitem
  • .xml
  • .log
  • .txt

Only one suffix is removed because the checks are implemented as an if / else if chain.


9.4 Hidden-File Prefix Removal

If the resulting name begins with a dot, the first dot is removed:

if (name.has_prefix(".")){
    name = name[1:name.length];
}

Leading and trailing whitespace is then removed through:

name = name.strip();

If the normalized name is empty, the method returns without creating an entry.


9.5 Registry Lookup

The method attempts to locate an existing entry using:

  1. The exact normalized name
  2. The lowercase version of the normalized name
if (app_map.has_key(name)){
    entry = app_map[name];
}
else if (app_map.has_key(name.down())){
    entry = app_map[name.down()];
}

If no matching entry exists, a new AppExcludeEntry is created and inserted into the map:

entry = new AppExcludeEntry(name, is_include);
app_map[name] = entry;

9.6 Path Association

The original relative path is added to the entry only when it is not already present:

if (!entry.items.contains(item_path)){
    entry.items.add(item_path);
}

This allows paths from different configuration locations to be grouped under the same logical application name.

Example:

~/.example
~/.config/example
~/.local/share/example

10. Pattern Generation

For every discovered path, the method generates two patterns:

  • One for the root account
  • One for all regular user home directories

The target prefixes are:

/root
/home/*

The initial tilde is removed from item_path before the path is appended:

str += item_path[1:item_path.length];

Pattern generation is performed using:

foreach(bool root_user in new bool[] { true, false } ){
    string str = (is_include) ? "+ " : "";
    str += (root_user) ? "/root" : "/home/*";
    str += item_path[1:item_path.length];
    entry.patterns.add(str);
}

For the input:

~/.config/example

the generated exclusion patterns are:

/root/.config/example
/home/*/.config/example

The generated include patterns are:

+ /root/.config/example
+ /home/*/.config/example

The commented line:

//str += (is_file) ? "" : "/**";

indicates that recursive directory matching was considered but is currently disabled.


11. Application List Generation

11.1 get_apps_list()

public static Gee.ArrayList<AppExcludeEntry> get_apps_list(
    Gee.ArrayList<string> selected_app_names
)

The get_apps_list() method converts the shared map into a sorted list suitable for presentation in a user interface.

The method performs the following operations:

  1. Initialize the shared map if necessary
  2. Mark requested application entries as enabled
  3. Copy all map values into a new Gee.ArrayList
  4. Sort the list alphabetically without case sensitivity
  5. Log the total number of applications
  6. Return the resulting list

11.2 Selected Application State

For every name in selected_app_names, the corresponding map entry is marked as enabled:

app_map[selected_name].enabled = true;

The current implementation contains an invalid fallback branch:

else{
    app_map[selected_name].enabled = false;
}

This branch attempts to access a map entry after confirming that the key does not exist. Depending on Gee map behavior and runtime checks, this can produce a null-access error.

A safer implementation would ignore unknown names or explicitly retrieve and validate the entry before changing its state.


11.3 Case-Insensitive Sorting

The resulting list is sorted alphabetically using lowercase versions of the application names:

GLib.CompareDataFunc<AppExcludeEntry> entry_compare = (a, b) => {
    return strcmp(a.name.down(), b.name.down());
};

list.sort((owned) entry_compare);

This provides consistent display ordering regardless of capitalization.

The total number of entries is written to the debug log:

log_debug("apps: %d".printf(list.size));

12. Filesystem Error Handling

Directory enumeration operations are enclosed in try/catch blocks.

Any GLib.Error generated by GIO is captured and logged:

catch(Error e){
    log_error(e.message);
}

Filesystem errors do not propagate to the caller. The discovery operation stops for the affected path, while the application remains active.


13. Data Flow

The complete discovery and conversion flow is:

  1. A parent home directory or individual user home is supplied
  2. Filesystem entries are enumerated using GIO
  3. Temporary and generic paths are filtered out
  4. Accepted paths are converted to tilde-relative paths
  5. The basename is normalized into an application name
  6. Paths with the same application name are grouped together
  7. Root and regular-user patterns are generated
  8. The shared map is converted into a sorted application list
  9. The resulting entries can be displayed and selected by the UI

14. Example Entry

Given the following discovered paths:

~/.example
~/.config/example.conf
~/.local/share/example

the normalized application name is:

example

The associated items collection contains:

~/.example
~/.config/example.conf
~/.local/share/example

The generated patterns contain root and regular-user variants for each item:

/root/.example
/home/*/.example

/root/.config/example.conf
/home/*/.config/example.conf

/root/.local/share/example
/home/*/.local/share/example

15. Runtime and Implementation Considerations

  • Shared mutable state: app_map is static and is shared across all callers.
  • Thread safety: no locking mechanism protects the shared map or its entries from concurrent modifications.
  • Filesystem validation: paths are checked for existence before directory enumeration begins.
  • Error containment: GIO errors are logged instead of being propagated to the caller.
  • Duplicate item prevention: duplicate values in items are explicitly rejected.
  • Pattern duplication: generated patterns are added without checking whether an identical pattern already exists.
  • Case normalization: lookup is partially case-insensitive, but new map keys retain their original capitalization.
  • File-type metadata: the is_file argument is calculated but is not stored in the corresponding object field.
  • Selection-state reset: entries not included in selected_app_names are not explicitly reset to false.
  • Unknown selection handling: the current fallback branch attempts to modify a missing map entry and should be corrected.
  • Path assumptions: add_item() expects paths beginning with ~, because the first character is removed during pattern generation.
  • Recursive matching: automatic addition of /** for directories is currently disabled.

16. Licensing

The source file is distributed under the GNU General Public License, version 2 or, at the user’s option, any later version.

The original copyright notice identifies:

Copyright 2012-2018 Tony George
<teejeetech@gmail.com>

The software is provided without any warranty, including implied warranties of merchantability or fitness for a particular purpose.