1#!/usr/bin/env python3 2# 3# Copyright 2018 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Config class. 18 19History: 20 version 2: Record the user's each preferred ide version by the key name 21 [ide_base.ide_name]_preferred_version. E.g., the key name of the 22 preferred IntelliJ is IntelliJ_preferred_version and the example 23 is as follows. 24 "Android Studio_preferred_version": "/opt/android-studio-3.0/bin/ 25 studio.sh" 26 "IntelliJ_preferred_version": "/opt/intellij-ce-stable/bin/ 27 idea.sh" 28 29 version 1: Record the user's preferred IntelliJ version by the key name 30 preferred_version and doesn't support any other IDEs. The example 31 is "preferred_version": "/opt/intellij-ce-stable/bin/idea.sh". 32""" 33 34import copy 35import json 36import logging 37import os 38import re 39 40from aidegen import constant 41from aidegen import templates 42from aidegen.lib import common_util 43 44_DIR_LIB = 'lib' 45 46 47class AidegenConfig: 48 """Class manages AIDEGen's configurations. 49 50 Attributes: 51 _config: A dict contains the aidegen config. 52 _config_backup: A dict contains the aidegen config. 53 """ 54 55 # Constants of AIDEGen config 56 _DEFAULT_CONFIG_FILE = 'aidegen.config' 57 _CONFIG_DIR = os.path.join( 58 os.path.expanduser('~'), '.config', 'asuite', 'aidegen') 59 _CONFIG_FILE_PATH = os.path.join(_CONFIG_DIR, _DEFAULT_CONFIG_FILE) 60 _KEY_APPEND = 'preferred_version' 61 _KEY_PLUGIN_PREFERENCE = 'Asuite_plugin_preference' 62 63 # Constants of enable debugger 64 _ENABLE_DEBUG_CONFIG_DIR = 'enable_debugger' 65 _ENABLE_DEBUG_CONFIG_FILE = 'enable_debugger.iml' 66 _ENABLE_DEBUG_DIR = os.path.join(_CONFIG_DIR, _ENABLE_DEBUG_CONFIG_DIR) 67 _DIR_SRC = 'src' 68 _DIR_GEN = 'gen' 69 DEBUG_ENABLED_FILE_PATH = os.path.join(_ENABLE_DEBUG_DIR, 70 _ENABLE_DEBUG_CONFIG_FILE) 71 72 # Constants of checking deprecated IntelliJ version. 73 # The launch file idea.sh of IntelliJ is in ASCII encoding. 74 ENCODE_TYPE = 'ISO-8859-1' 75 ACTIVE_KEYWORD = '$JAVA_BIN' 76 77 def __init__(self): 78 self._config = {} 79 self._config_backup = {} 80 self._create_config_folder() 81 82 def __enter__(self): 83 self._load_aidegen_config() 84 self._config_backup = copy.deepcopy(self._config) 85 return self 86 87 def __exit__(self, exc_type, exc_val, exc_tb): 88 self._save_aidegen_config() 89 90 def preferred_version(self, ide=None): 91 """AIDEGen configuration getter. 92 93 Args: 94 ide: The string of the relevant IDE name, same as the data of 95 IdeBase._ide_name or IdeUtil.ide_name(). None represents the 96 usage of the version 1. 97 98 Returns: 99 The preferred version item of configuration data if exists and is 100 not deprecated, otherwise None. 101 """ 102 key = '_'.join([ide, self._KEY_APPEND]) if ide else self._KEY_APPEND 103 preferred_version = self._config.get(key, '') 104 # Backward compatible check. 105 if not preferred_version: 106 preferred_version = self._config.get(self._KEY_APPEND, '') 107 108 if preferred_version: 109 real_version = os.path.realpath(preferred_version) 110 if ide and not self.deprecated_version(ide, real_version): 111 return preferred_version 112 # Backward compatible handling. 113 if not ide and not self.deprecated_intellij_version(real_version): 114 return preferred_version 115 return None 116 117 def set_preferred_version(self, preferred_version, ide=None): 118 """AIDEGen configuration setter. 119 120 Args: 121 preferred_version: A string, user's preferred version to be set. 122 ide: The string of the relevant IDE name, same as the data of 123 IdeBase._ide_name or IdeUtil.ide_name(). None presents the 124 usage of the version 1. 125 """ 126 key = '_'.join([ide, self._KEY_APPEND]) if ide else self._KEY_APPEND 127 self._config[key] = preferred_version 128 129 @property 130 def plugin_preference(self): 131 """Gets Asuite plugin user's preference 132 133 Returns: 134 A string of the user's preference: yes/no/auto. 135 """ 136 return self._config.get(self._KEY_PLUGIN_PREFERENCE, '') 137 138 @plugin_preference.setter 139 def plugin_preference(self, preference): 140 """Sets Asuite plugin user's preference 141 142 Args: 143 preference: A string of the user's preference: yes/no/auto. 144 """ 145 self._config[self._KEY_PLUGIN_PREFERENCE] = preference 146 147 def _load_aidegen_config(self): 148 """Load data from configuration file.""" 149 if os.path.exists(self._CONFIG_FILE_PATH): 150 try: 151 with open(self._CONFIG_FILE_PATH, 'r', 152 encoding='utf-8') as cfg_file: 153 self._config = json.load(cfg_file) 154 except ValueError as err: 155 info = '{} format is incorrect, error: {}'.format( 156 self._CONFIG_FILE_PATH, err) 157 logging.info(info) 158 except IOError as err: 159 logging.error(err) 160 raise 161 162 def _save_aidegen_config(self): 163 """Save data to configuration file.""" 164 if self._is_config_modified(): 165 with open(self._CONFIG_FILE_PATH, 'w', 166 encoding='utf-8') as cfg_file: 167 json.dump(self._config, cfg_file, indent=4) 168 169 def _is_config_modified(self): 170 """Check if configuration data is modified.""" 171 return any(key for key in self._config if key not in self._config_backup 172 or self._config[key] != self._config_backup[key]) 173 174 def _create_config_folder(self): 175 """Create the config folder if it doesn't exist.""" 176 if not os.path.exists(self._CONFIG_DIR): 177 os.makedirs(self._CONFIG_DIR) 178 179 def _gen_enable_debug_sub_dir(self, dir_name): 180 """Generate a dir under enable debug dir. 181 182 Args: 183 dir_name: A string of the folder name. 184 """ 185 _dir = os.path.join(self._ENABLE_DEBUG_DIR, dir_name) 186 if not os.path.exists(_dir): 187 os.makedirs(_dir) 188 189 def _gen_androidmanifest(self): 190 """Generate an AndroidManifest.xml under enable debug dir. 191 192 Once the AndroidManifest.xml does not exist or file size is zero, 193 AIDEGen will generate it with default content to prevent the red 194 underline error in IntelliJ. 195 """ 196 _file = os.path.join(self._ENABLE_DEBUG_DIR, constant.ANDROID_MANIFEST) 197 if not os.path.exists(_file) or os.stat(_file).st_size == 0: 198 common_util.file_generate(_file, templates.ANDROID_MANIFEST_CONTENT) 199 200 def _gen_enable_debugger_config(self, android_sdk_version): 201 """Generate the enable_debugger.iml config file. 202 203 Re-generate the enable_debugger.iml everytime for correcting the Android 204 SDK version. 205 206 Args: 207 android_sdk_version: The version name of the Android Sdk in the 208 jdk.table.xml. 209 """ 210 content = templates.XML_ENABLE_DEBUGGER.format( 211 ANDROID_SDK_VERSION=android_sdk_version) 212 common_util.file_generate(self.DEBUG_ENABLED_FILE_PATH, content) 213 214 def create_enable_debugger_module(self, android_sdk_version): 215 """Create the enable_debugger module. 216 217 1. Create two empty folders named src and gen. 218 2. Create an empty file named AndroidManifest.xml 219 3. Create the enable_debugger.iml. 220 221 Args: 222 android_sdk_version: The version name of the Android Sdk in the 223 jdk.table.xml. 224 225 Returns: True if successfully generate the enable debugger module, 226 otherwise False. 227 """ 228 try: 229 self._gen_enable_debug_sub_dir(self._DIR_SRC) 230 self._gen_enable_debug_sub_dir(self._DIR_GEN) 231 self._gen_androidmanifest() 232 self._gen_enable_debugger_config(android_sdk_version) 233 return True 234 except (IOError, OSError) as err: 235 logging.warning(('Can\'t create the enable_debugger module in %s.\n' 236 '%s'), self._CONFIG_DIR, err) 237 return False 238 239 @staticmethod 240 def deprecated_version(ide, script_path): 241 """Check if the script_path belongs to a deprecated IDE version. 242 243 Args: 244 ide: The string of the relevant IDE name, same as the data of 245 IdeBase._ide_name or IdeUtil.ide_name(). 246 script_path: The path string of the IDE script file. 247 248 Returns: True if the preferred version is deprecated, otherwise False. 249 """ 250 if ide == constant.IDE_ANDROID_STUDIO: 251 return AidegenConfig.deprecated_studio_version(script_path) 252 if ide == constant.IDE_INTELLIJ: 253 return AidegenConfig.deprecated_intellij_version(script_path) 254 return False 255 256 @staticmethod 257 def deprecated_intellij_version(idea_path): 258 """Check if the preferred IntelliJ version is deprecated or not. 259 260 The IntelliJ version is deprecated once the string "$JAVA_BIN" doesn't 261 exist in the idea.sh. 262 263 Args: 264 idea_path: the absolute path to idea.sh. 265 266 Returns: True if the preferred version was deprecated, otherwise False. 267 """ 268 if os.path.isfile(idea_path): 269 file_content = common_util.read_file_content( 270 idea_path, AidegenConfig.ENCODE_TYPE) 271 return AidegenConfig.ACTIVE_KEYWORD not in file_content 272 return False 273 274 @staticmethod 275 def deprecated_studio_version(script_path): 276 """Check if the preferred Studio version is deprecated or not. 277 278 The Studio version is deprecated once the /android-studio-*/lib folder 279 doesn't exist. 280 281 Args: 282 script_path: the absolute path to the ide script file. 283 284 Returns: True if the preferred version is deprecated, otherwise False. 285 """ 286 if not os.path.isfile(script_path): 287 return True 288 script_dir = os.path.dirname(script_path) 289 if not os.path.isdir(script_dir): 290 return True 291 lib_path = os.path.join(os.path.dirname(script_dir), _DIR_LIB) 292 return not os.path.isdir(lib_path) 293 294 295class IdeaProperties: 296 """Class manages IntelliJ's idea.properties attribute. 297 298 Class Attributes: 299 _PROPERTIES_FILE: The property file name of IntelliJ. 300 _KEY_FILESIZE: The key name of the maximum file size. 301 _FILESIZE_LIMIT: The value to be set as the max file size. 302 _RE_SEARCH_FILESIZE: A regular expression to find the current max file 303 size. 304 _PROPERTIES_CONTENT: The default content of idea.properties to be 305 generated. 306 307 Attributes: 308 idea_file: The absolute path of the idea.properties. 309 For example: 310 In Linux, it is ~/.IdeaIC2019.1/config/idea.properties. 311 In Mac, it is ~/Library/Preferences/IdeaIC2019.1/ 312 idea.properties. 313 """ 314 315 # Constants of idea.properties 316 _PROPERTIES_FILE = 'idea.properties' 317 _KEY_FILESIZE = 'idea.max.intellisense.filesize' 318 _FILESIZE_LIMIT = 100000 319 _RE_SEARCH_FILESIZE = r'%s\s?=\s?(?P<value>\d+)' % _KEY_FILESIZE 320 _PROPERTIES_CONTENT = """# custom IntelliJ IDEA properties 321 322#------------------------------------------------------------------------------- 323# Maximum size of files (in kilobytes) for which IntelliJ IDEA provides coding 324# assistance. Coding assistance for large files can affect editor performance 325# and increase memory consumption. 326# The default value is 2500. 327#------------------------------------------------------------------------------- 328idea.max.intellisense.filesize=100000 329""" 330 331 def __init__(self, config_dir): 332 """IdeaProperties initialize. 333 334 Args: 335 config_dir: The absolute dir of the idea.properties. 336 """ 337 self.idea_file = os.path.join(config_dir, self._PROPERTIES_FILE) 338 339 def _set_default_idea_properties(self): 340 """Create the file idea.properties.""" 341 common_util.file_generate(self.idea_file, self._PROPERTIES_CONTENT) 342 343 def _reset_max_file_size(self): 344 """Reset the max file size value in the idea.properties.""" 345 updated_flag = False 346 properties = common_util.read_file_content(self.idea_file).splitlines() 347 for index, line in enumerate(properties): 348 res = re.search(self._RE_SEARCH_FILESIZE, line) 349 if res and int(res.group('value')) < self._FILESIZE_LIMIT: 350 updated_flag = True 351 properties[index] = '%s=%s' % (self._KEY_FILESIZE, 352 str(self._FILESIZE_LIMIT)) 353 if updated_flag: 354 common_util.file_generate(self.idea_file, '\n'.join(properties)) 355 356 def set_max_file_size(self): 357 """Set the max file size parameter in the idea.properties.""" 358 if not os.path.exists(self.idea_file): 359 self._set_default_idea_properties() 360 else: 361 self._reset_max_file_size() 362