View Javadoc
1   /*
2    * Copyright 2025 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.linux;
6   
7   import oshi.software.os.ApplicationInfo;
8   import oshi.util.ExecutingCommand;
9   import oshi.util.ParseUtil;
10  
11  import java.util.ArrayList;
12  import java.util.Collections;
13  import java.util.HashMap;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.LinkedHashSet;
17  import java.util.LinkedHashMap;
18  import java.util.Set;
19  import java.util.regex.Pattern;
20  
21  public final class LinuxInstalledApps {
22  
23      private static final Pattern PIPE_PATTERN = Pattern.compile("\\|");
24      private static final Map<String, String> PACKAGE_MANAGER_COMMANDS = initializePackageManagerCommands();
25  
26      private LinuxInstalledApps() {
27      }
28  
29      private static Map<String, String> initializePackageManagerCommands() {
30          Map<String, String> commands = new HashMap<>();
31  
32          if (isPackageManagerAvailable("dpkg")) {
33              commands.put("dpkg",
34                      "dpkg-query -W -f=${Package}|${Version}|${Architecture}|${Installed-Size}|${db-fsys:Last-Modified}|${Maintainer}|${Source}|${Homepage}\\n");
35          } else if (isPackageManagerAvailable("rpm")) {
36              commands.put("rpm",
37                      "rpm -qa --queryformat %{NAME}|%{VERSION}-%{RELEASE}|%{ARCH}|%{SIZE}|%{INSTALLTIME}|%{PACKAGER}|%{SOURCERPM}|%{URL}\\n");
38          }
39  
40          return commands;
41      }
42  
43      /**
44       * Retrieves the list of installed applications on a Linux system. This method determines the appropriate package
45       * manager and parses the installed application details.
46       *
47       * @return A list of {@link ApplicationInfo} objects representing installed applications.
48       */
49      public static List<ApplicationInfo> queryInstalledApps() {
50          List<String> output = fetchInstalledApps();
51          return parseLinuxAppInfo(output);
52      }
53  
54      /**
55       * Fetches the list of installed applications by executing the appropriate package manager command. The package
56       * manager is determined during class initialization and stored in {@code PACKAGE_MANAGER_COMMANDS}. If no supported
57       * package manager is found, an empty list is returned.
58       *
59       * @return A list of strings, where each entry represents an installed application with its details. Returns an
60       *         empty list if no supported package manager is available.
61       */
62      private static List<String> fetchInstalledApps() {
63          if (PACKAGE_MANAGER_COMMANDS.isEmpty()) {
64              return Collections.emptyList();
65          }
66  
67          // Get the first available package manager's command
68          String command = PACKAGE_MANAGER_COMMANDS.values().iterator().next();
69          return ExecutingCommand.runNative(command);
70      }
71  
72      private static boolean isPackageManagerAvailable(String packageManager) {
73          List<String> result = ExecutingCommand.runNative(packageManager + " --version");
74          // If the command executes fine the result is non-empty else empty
75          return !result.isEmpty();
76      }
77  
78      private static List<ApplicationInfo> parseLinuxAppInfo(List<String> output) {
79          Set<ApplicationInfo> appInfoSet = new LinkedHashSet<>();
80  
81          for (String line : output) {
82              // split by the pipe character
83              String[] parts = PIPE_PATTERN.split(line, -1); // -1 to keep empty fields
84  
85              // Check if we have all 8 fields
86              if (parts.length >= 8) {
87                  // Additional info map
88                  Map<String, String> additionalInfo = new LinkedHashMap<>();
89                  additionalInfo.put("architecture", ParseUtil.getStringValueOrUnknown(parts[2]));
90                  additionalInfo.put("installedSize", String.valueOf(ParseUtil.parseLongOrDefault(parts[3], 0L)));
91                  additionalInfo.put("source", ParseUtil.getStringValueOrUnknown(parts[6]));
92                  additionalInfo.put("homepage", ParseUtil.getStringValueOrUnknown(parts[7]));
93  
94                  ApplicationInfo app = new ApplicationInfo(ParseUtil.getStringValueOrUnknown(parts[0]), // Package name
95                          ParseUtil.getStringValueOrUnknown(parts[1]), // Version
96                          ParseUtil.getStringValueOrUnknown(parts[5]), // Vendor
97                          ParseUtil.parseLongOrDefault(parts[4], 0L), // Date Epoch
98                          additionalInfo);
99  
100                 appInfoSet.add(app);
101             }
102         }
103 
104         return new ArrayList<>(appInfoSet);
105     }
106 }