Group configs based on the directory structure to support a nested view

- Group all configs in a subdirectory with directory name
  as the label.

- If any connection is active, newly found configs are
  added to the root group to keep the logic simple.

- Directory hierarchy is scanned up to a depth of 4: i.e.,
  config_dir and global_config_dir and its subdirectories
  up to 3 levels down.

Only support for scanning configs and attaching group labels
is added here. Rendering the nested menu is the subject of
a later commit.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
pull/298/head
Selva Nair 2018-04-19 21:43:17 -04:00
parent 0702acf70c
commit 398a771840
4 changed files with 147 additions and 7 deletions

17
main.c
View File

@ -879,3 +879,20 @@ MsgToEventLog(WORD type, wchar_t *format, ...)
msg[1] = buf;
ReportEventW(o.event_log, type, 0, 0, NULL, 2, 0, msg, NULL);
}
void
ErrorExit(int exit_code, const wchar_t *msg)
{
if (msg)
MessageBoxExW(NULL, msg, TEXT(PACKAGE_NAME),
MB_OK | MB_SETFOREGROUND|MB_ICONERROR, GetGUILanguage());
if (o.hWnd)
{
StopAllOpenVPN();
PostQuitMessage(exit_code);
}
else
{
exit(exit_code);
}
}

2
main.h
View File

@ -133,4 +133,6 @@ DWORD GetDllVersion(LPCTSTR lpszDllName);
#define DPI_SCALE(x) MulDiv(x, o.dpi_scale, 100)
void MsgToEventLog(WORD type, wchar_t *format, ...);
void ErrorExit(int exit_code, const wchar_t *msg);
#endif

View File

@ -135,9 +135,81 @@ AddConfigFileToList(int config, const TCHAR *filename, const TCHAR *config_dir)
}
}
#define FLAG_WARN_DUPLICATES (0x1)
#define FLAG_WARN_MAX_CONFIGS (0x2)
#define FLAG_ADD_CONFIG_GROUPS (0x4)
/*
* Create a new group with the given name as a child of the
* specified parent group and return pointer to the new group.
* If FLAG_ADD_CONFIG_GROUPS is not enabled, returns the
* parent itself.
*/
static config_group_t *
NewConfigGroup(const wchar_t *name, config_group_t *parent, int flags)
{
if (!(flags & FLAG_ADD_CONFIG_GROUPS))
return parent;
if (!o.groups || o.num_groups == o.max_groups)
{
o.max_groups += 10;
void *tmp = realloc(o.groups, sizeof(*o.groups)*o.max_groups);
if (!tmp)
{
o.max_groups -= 10;
ErrorExit(1, L"Out of memory while grouping configs");
}
o.groups = tmp;
}
config_group_t *cg = &o.groups[o.num_groups];
memset(cg, 0, sizeof(*cg));
_sntprintf_0(cg->name, L"%s", name);
cg->id = o.num_groups++;
cg->parent = parent;
cg->active = false; /* activated later if not empty */
return cg;
}
/*
* All groups that link at least one config to the root are
* enabled. Dangling entries with no terminal configs will stay
* disabled and are not displayed in the menu tree.
*/
static void
BuildFileList0(const TCHAR *config_dir, int recurse_depth, bool warn_duplicates)
ActivateConfigGroups(void)
{
/* the root group is always active */
o.groups[0].active = true;
/* activate all groups that connect a config to the root */
for (int i = 0; i < o.num_configs; i++)
{
config_group_t *cg = o.conn[i].group;
while (cg)
{
cg->active = true;
cg = cg->parent;
}
}
}
/* Scan for configs in config_dir recursing down up to recurse_depth.
* Input: config_dir -- root of the directory to scan from
* group -- the group into which add the configs to
* flags -- enable warnings, use directory based
* grouping of configs etc.
* Currently configs in a directory are grouped together and group is
* a pointer to the current parent group in the global group array |o.groups|
* This may be recursively called until depth becomes 1 and each time
* the group is changed to that of the directory being recursed into.
*/
static void
BuildFileList0(const TCHAR *config_dir, int recurse_depth, config_group_t *group, int flags)
{
WIN32_FIND_DATA find_obj;
HANDLE find_handle;
@ -163,13 +235,16 @@ BuildFileList0(const TCHAR *config_dir, int recurse_depth, bool warn_duplicates)
{
if (ConfigAlreadyExists(find_obj.cFileName))
{
if (warn_duplicates)
if (flags & FLAG_WARN_DUPLICATES)
ShowLocalizedMsg(IDS_ERR_CONFIG_EXIST, find_obj.cFileName);
continue;
}
if (CheckReadAccess (config_dir, find_obj.cFileName))
AddConfigFileToList(o.num_configs++, find_obj.cFileName, config_dir);
{
AddConfigFileToList(o.num_configs, find_obj.cFileName, config_dir);
o.conn[o.num_configs++].group = group;
}
}
} while (FindNextFile(find_handle, &find_obj));
@ -193,7 +268,9 @@ BuildFileList0(const TCHAR *config_dir, int recurse_depth, bool warn_duplicates)
{
/* recurse into subdirectory */
_sntprintf_0(subdir_name, _T("%s\\%s"), config_dir, find_obj.cFileName);
BuildFileList0(subdir_name, recurse_depth - 1, warn_duplicates);
config_group_t *sub_group = NewConfigGroup(find_obj.cFileName, group, flags);
BuildFileList0(subdir_name, recurse_depth - 1, sub_group, flags);
}
}
} while (FindNextFile(find_handle, &find_obj));
@ -205,7 +282,9 @@ void
BuildFileList()
{
static bool issue_warnings = true;
int recurse_depth = 2; /* read config_dir and sub-directories */
int recurse_depth = 4; /* read config_dir and 3 levels of sub-directories */
int flags = 0;
config_group_t *root = NULL;
if (o.silent_connection)
issue_warnings = false;
@ -216,15 +295,31 @@ BuildFileList()
* rescan to add any new one's found
*/
if (CountConnState(disconnected) == o.num_configs)
{
o.num_configs = 0;
o.num_groups = 0;
flags |= FLAG_ADD_CONFIG_GROUPS;
root = NewConfigGroup(L"User Profiles", NULL, flags);
}
else
root = &o.groups[0];
BuildFileList0 (o.config_dir, recurse_depth, issue_warnings);
if (issue_warnings)
{
flags |= FLAG_WARN_DUPLICATES | FLAG_WARN_MAX_CONFIGS;
}
BuildFileList0 (o.config_dir, recurse_depth, root, flags);
root = NewConfigGroup(L"System Profiles", root, flags);
if (_tcscmp (o.global_config_dir, o.config_dir))
BuildFileList0 (o.global_config_dir, recurse_depth, issue_warnings);
BuildFileList0 (o.global_config_dir, recurse_depth, root, flags);
if (o.num_configs == 0 && issue_warnings)
ShowLocalizedMsg(IDS_NFO_NO_CONFIGS, o.config_dir, o.global_config_dir);
ActivateConfigGroups();
issue_warnings = false;
}

View File

@ -87,10 +87,30 @@ typedef struct {
#define FLAG_SAVE_AUTH_PASS (1<<5)
#define FLAG_DISABLE_SAVE_PASS (1<<6)
#define CONFIG_VIEW_AUTO (0)
#define CONFIG_VIEW_FLAT (1)
#define CONFIG_VIEW_NESTED (2)
typedef struct {
unsigned short major, minor, build, revision;
} version_t;
/* A node of config groups tree that can be navigated from the end
* node (where config file is attached) to the root. The nodes are stored
* as array (o.groups[]) with each node linked to its parent.
* Not a complete tree: only navigation from child to parent is supported
* which is enough for our purposes.
*/
typedef struct config_group {
int id; /* A unique id for the group */
wchar_t name[40]; /* Name of the group -- possibly truncated */
struct config_group *parent; /* Pointer to parent group */
BOOL active; /* Displayed in the menu if true -- used to prune empty groups */
int children; /* Number of children groups and configs */
int pos; /* Index within the parent group -- used for rendering */
HMENU menu; /* Handle to menu entry for this group */
} config_group_t;
/* Connections parameters */
struct connection {
TCHAR config_file[MAX_PATH]; /* Name of the config file */
@ -105,6 +125,8 @@ struct connection {
int failed_auth_attempts; /* # of failed user-auth attempts */
time_t connected_since; /* Time when the connection was established */
proxy_t proxy_type; /* Set during querying proxy credentials */
config_group_t *group; /* Pointer to the group this config belongs to */
int pos; /* Index of the config within its group */
struct {
SOCKET sk;
@ -137,7 +159,10 @@ typedef struct {
/* Connection parameters */
connection_t conn[MAX_CONFIGS]; /* Connection structure */
config_group_t *groups; /* Array of nodes defining the config groups tree */
int num_configs; /* Number of configs */
int num_groups; /* Number of config groups */
int max_groups; /* Current capacity of groups array */
service_state_t service_state; /* State of the OpenVPN Service */
@ -169,6 +194,7 @@ typedef struct {
DWORD connectscript_timeout; /* Connect Script execution timeout (sec) */
DWORD disconnectscript_timeout; /* Disconnect Script execution timeout (sec) */
DWORD preconnectscript_timeout; /* Preconnect Script execution timeout (sec) */
DWORD config_menu_view; /* 0 for auto, 1 for original flat menu, 2 for hierarchical */
#ifdef DEBUG
FILE *debug_fp;