import sublime import sublime_plugin from datetime import datetime import xdrlib import os,sys,re,json import shutil import subprocess,threading import webbrowser import random import platform from time import sleep from .salesforce import * from . import baseutil from .baseutil import SysIo from .uiutil import SublConsole from .setting import SfBasicConfig from .const import AURA_DEFTYPE_EXT ########################################################################################## #Salesforce Util ########################################################################################## def sf_login(sf_basic_config, project_name='', Soap_Type=Soap): project = settings = sf_basic_config.get_setting() sublconsole = SublConsole(sf_basic_config) try: # project = {} # if project_name: # projects = settings["projects"] # project = projects[project_name] # else: # project = settings["default_project_value"] # sublconsole.debug('settings--->') # sublconsole.debug(json.dumps(settings, indent=4)) # sublconsole.debug(json.dumps(project, indent=4)) if project_name: # TODO sublconsole.debug('>>>>sf_login [project_name]: ' + project_name) sf = Soap_Type(username=project["username"], password=project["password"], security_token=project["security_token"], sandbox=project["is_sandbox"], version=project["api_version"], myconsole=sublconsole ) elif settings["use_oauth2"]: sublconsole.debug('>>>>sf_login : use oauth2') sf = Soap_Type( session_id=project["access_token"] , instance_url=project["instance_url"], sandbox=project["is_sandbox"], version=project["api_version"], myconsole=sublconsole ) # sf = Soap_Type(username=project["username"], # session_id=project["sessionId"] , # instance_url=project["instanceUrl"], # sandbox=project["is_sandbox"], # version=project["api_version"], # client_id=project["id"], # settings=settings # # password=None, # # security_token=None, # # instance=None, # # organizationId=None, # # proxies=None, # # session=None, # # project=None # ) elif settings["use_password"]: sublconsole.debug('>>>>sf_login : use password') sf = Soap_Type(username=project["username"], password=project["password"], security_token=project["security_token"], sandbox=project["is_sandbox"], version=project["api_version"], myconsole=sublconsole ) sf.settings = settings # sublconsole.debug(sf.session_id) return sf except Exception as e: if settings["use_oauth2"]: sublconsole.showlog('please login salesforce on your browser!') sf_oauth2(sf_basic_config) else: sublconsole.showlog(e) sublconsole.show_in_dialog('Login Error! ' + baseutil.xstr(e)) return ########################################################################################## #Salesforce Auth ########################################################################################## from SalesforceXyTools.libs import server sfdc_oauth_server = None def re_auth(sf_basic_config): settings = sf_basic_config.get_setting() if settings["use_oauth2"]: sf_oauth2(settings) def sf_oauth2(sf_basic_config): sublconsole = SublConsole(sf_basic_config) settings = sf_basic_config.get_setting() from SalesforceXyTools.libs import auth project_setting = settings # project_setting = settings["default_project_value"] is_sandbox = project_setting["is_sandbox"] if refresh_token(sf_basic_config): return server_info = sublime.load_settings("sfdc.server.sublime-settings") client_id = server_info.get("client_id") client_secret = server_info.get("client_secret") redirect_uri = server_info.get("redirect_uri") oauth = auth.SalesforceOAuth2(client_id, client_secret, redirect_uri, is_sandbox) authorize_url = oauth.authorize_url() sublconsole.debug('authorize_url-->') sublconsole.debug(authorize_url) start_server() open_in_default_browser(sf_basic_config, authorize_url) def start_server(): global sfdc_oauth_server if sfdc_oauth_server is None: sfdc_oauth_server = server.Server() def stop_server(): global sfdc_oauth_server if sfdc_oauth_server is not None: sfdc_oauth_server.stop() sfdc_oauth_server = None def refresh_token(sf_basic_config): settings = sf_basic_config.get_setting() sublconsole = SublConsole(sf_basic_config) sublconsole.debug('>>>>start to refresh token') from SalesforceXyTools.libs import auth if not settings["use_oauth2"]: return False project_setting = settings is_sandbox = project_setting["is_sandbox"] if "refresh_token" not in project_setting: sublconsole.debug("refresh token missing") return False sublconsole.debug('>>>>load server info') server_info = sublime.load_settings("sfdc.server.sublime-settings") client_id = server_info.get("client_id") client_secret = server_info.get("client_secret") redirect_uri = server_info.get("redirect_uri") oauth = auth.SalesforceOAuth2(client_id, client_secret, redirect_uri, is_sandbox) _refresh_token = project_setting["refresh_token"] # sublconsole.debug(refresh_token) response_json = oauth.refresh_token(_refresh_token) # sublconsole.debug(response_json) if "error" in response_json: sublconsole.debug('>>>>response josn error') sublconsole.debug(response_json) return False if "refresh_token" not in response_json: response_json["refresh_token"] = _refresh_token sublconsole.debug('>>>>refresh_token:') sublconsole.debug(refresh_token) sublconsole.debug('>>>>save session') sf_basic_config.save_session(response_json) sublconsole.debug("------->refresh_token ok!") return True class OauthUtil(): def __init__(self, sf_basic_config): self.sf_basic_config = sf_basic_config self.settings = sf_basic_config.get_setting() def is_use_oauth(self): return self.settings["use_oauth2"] ########################################################################################## #Metadata Cache ########################################################################################## class CacheLoader(object): def __init__(self, file_name, always_reload=False, sf_basic_config = None): if sf_basic_config: self.sf_basic_config = sf_basic_config else: self.sf_basic_config = SfBasicConfig() self.sublconsole = SublConsole(self.sf_basic_config) self.settings = self.sf_basic_config.get_setting() self.MAX_DEEP = 2 self.file_name = file_name self.save_dir = self.sf_basic_config.get_config_dir() self.full_path = os.path.join(self.save_dir, self.file_name) self.all_cache = None self.always_reload = always_reload def is_exist(self): return os.path.exists(self.full_path) def get_cache(self): if self.all_cache: return self.all_cache sub_thread = threading.Thread(target=self._load) sub_thread.start() if sub_thread.is_alive(): sleep(1) self.all_cache = {} if self.is_exist(): data = {} encoding = 'utf-8' with open(self.full_path, "r", encoding=encoding) as fp: data = json.loads(fp.read(),encoding) self.all_cache = data # self.all_cache = self._load() return self.all_cache def reload(self): return def clean(self): if os.path.exists(self.full_path): os.remove(self.full_path) def save_dict(self, dict_obj, encoding='utf-8'): self.save(json.dumps(dict_obj, ensure_ascii=False, indent=4), encoding=encoding) def save(self, json_str, encoding='utf-8'): full_path = self.full_path if not os.path.exists(os.path.dirname(full_path)): self.sublconsole.showlog("mkdir: " + os.path.dirname(full_path)) os.makedirs(os.path.dirname(full_path)) try: fp = open(full_path, "w", encoding=encoding) fp.write(json_str) except Exception as e: self.sublconsole.showlog('save file error! ' + full_path) self.sublconsole.showlog(e) finally: fp.close() def _load(self, deep=1, not_null_id=True, encoding='utf-8'): if self.always_reload: self.reload() if self.is_exist(): data = {} with open(self.full_path, "r", encoding=encoding) as fp: data = json.loads(fp.read(),encoding) return data elif deep <= self.MAX_DEEP: self.reload() deep = deep + 1 return self._load(deep, not_null_id) return class SobjectUtil(CacheLoader): def __init__(self, sf_basic_config = None): super(SobjectUtil, self).__init__("sobject.cache", always_reload=True, sf_basic_config=sf_basic_config) self.meta_api = sf_login(self.sf_basic_config, Soap_Type=MetadataApi) def reload(self): self.sublconsole.showlog("start to reload metadata cache, please wait...") self.clean() allMetadataResult = self.meta_api.describe() allMetadataMap = {} allMetadataMap["sobjects"] = {} for meta in allMetadataResult["sobjects"]: name = meta["name"] allMetadataMap["sobjects"][name] = meta allMetadataMap["lastUpdated"] = str(datetime.now()) self.save_dict(allMetadataMap) self.sublconsole.showlog("load metadata cache done.") # self.sublconsole.save_and_open_in_panel(json.dumps(allMetadataMap, ensure_ascii=False, indent=4), self.save_dir, self.file_name , is_open=False) # get all fields from sobject def get_sobject_fields(self, sobject_name): sftype = self.meta_api.get_sobject(sobject_name) sftypedesc = sftype.describe() return [baseutil.xstr(field["name"]) for field in sftypedesc["fields"]] def soql_format(self, soql_str): soql = baseutil.del_comment(soql_str) match = re.match("select\s+\*\s+from[\s\t]+(\w+)([\t\s\S]*)", soql, re.I|re.M) if match: sobject_name = match.group(1) condition = match.group(2) fields = self.get_sobject_fields(sobject_name) fields_str = ','.join(fields) soql = ("select %s from %s %s" % (fields_str, sobject_name, condition)) return soql def get_sobject_info(self, sobject_name): all_cache = self.get_cache() sobject_info = None if sobject_name in all_cache["sobjects"]: sobject_info = all_cache["sobjects"][sobject_name] sobject_info["sftypedesc"] = self.meta_api.get_sobject(sobject_name).describe() sobject_info["fields"] = [baseutil.xstr(field["name"]) for field in sobject_info["sftypedesc"]["fields"]] sobject_info["fields_obj"] = sobject_info["sftypedesc"]["fields"] sobject_info["soql"] = self.get_soql(sobject_name, sobject_info["fields"]) return sobject_info def get_soql(self, sobject_name, fields): return "SELECT %s FROM %s" % (" , ".join(fields), sobject_name) def get_sobject_info_list(self, sobject_name_list): return [ self.get_sobject_info(sobject_name) for sobject_name in sobject_name_list] # def _get_sobject_custom_fields(self, sftypedesc): # return [field for field in sftypedesc["fields"]] def get_sobject_name_list_for_sel(self): all_cache = self.get_cache() sobject_name_list = [] sobject_show_list = [] for sobject_info in all_cache["sobjects"].values(): sobject_show_list.append( "%s , %s , %s" % (str(sobject_info["name"]), str(sobject_info["label"]), str(sobject_info["keyPrefix"]))) sobject_name_list.append(str(sobject_info["name"])) return sobject_show_list, sobject_name_list def open_in_web(self, sobject_name, view_type="data_list"): sobject_info = self.get_sobject_info(sobject_name) returl = sobject_info["keyPrefix"] if returl: # open in browser sf = sf_login(self.sf_basic_config) login_url = ('https://{instance}/secur/frontdoor.jsp?sid={sid}&retURL={returl}' .format(instance=sf.sf_instance, sid=sf.session_id, returl=returl)) self.sublconsole.debug(login_url) open_in_default_browser(self.sf_basic_config, login_url) else: self.sublconsole.showlog("metadata is null!") class MetadataUtil(CacheLoader): def __init__(self, sf_basic_config = None): super(MetadataUtil, self).__init__("metadata.cache", always_reload=False, sf_basic_config=sf_basic_config) self.desc_meta_file = "describe_metadata.cache" self.all_cache = None def reload(self): self.sublconsole.showlog("start to reload metadata cache, please wait...") self.clean() meta_api = sf_login(self.sf_basic_config, Soap_Type=MetadataApi) allMetadataResult = meta_api.getAllMetadataMap() allMetadataResult["AuraDefinition"] = self._load_lux_cache() self.save_dict(allMetadataResult) self.sublconsole.showlog("load metadata cache done.") def _covert_AuraDefinition_to_cache_dict(self, AuraDefinition_records): AuraDefinition_MetadataMap = {} try: for AuraDefinition in AuraDefinition_records: deftype = AuraDefinition["DefType"] if deftype in AURA_DEFTYPE_EXT: aura_name = AuraDefinition["AuraDefinitionBundle"]["DeveloperName"] aura_key = "aura/%s/%s%s" % (aura_name, aura_name, AURA_DEFTYPE_EXT[deftype]) AuraDefinition_MetadataMap[aura_key] = { "id": AuraDefinition["Id"], "fileName": aura_key, "fullName": aura_name + AURA_DEFTYPE_EXT[deftype], "createdById": AuraDefinition["CreatedById"], "createdByName": AuraDefinition["CreatedBy"]["Name"] if AuraDefinition["CreatedBy"] else '', "createdDate": AuraDefinition["CreatedDate"], "lastModifiedById": AuraDefinition["LastModifiedById"], "lastModifiedByName": AuraDefinition["LastModifiedBy"]["Name"] if AuraDefinition["LastModifiedBy"] else '', "lastModifiedDate": AuraDefinition["LastModifiedDate"], "manageableState": "", "type": "AuraDefinition", "AuraDefinitionSrc": True, "DefType": AuraDefinition["DefType"], "DefExt": AURA_DEFTYPE_EXT[deftype], "Format": AuraDefinition["Format"], "AuraDefinitionBundleId": AuraDefinition["AuraDefinitionBundleId"], "AuraDefinitionBundleName": aura_name } return AuraDefinition_MetadataMap except Exception as ex: self.sublconsole.showlog(ex, 'error') return AuraDefinition_MetadataMap def _load_lux_cache(self, attr=None): try: aura_soql = 'SELECT Id, CreatedDate, CreatedById, CreatedBy.Name, LastModifiedDate, LastModifiedById, LastModifiedBy.Name, AuraDefinitionBundle.DeveloperName, AuraDefinitionBundleId, DefType, Format FROM AuraDefinition' if attr: aura_soql = aura_soql + " Where AuraDefinitionBundle.DeveloperName = '%s' and DefType = '%s' limit 1" % (attr["lux_name"], attr["lux_type"]) meta_api = sf_login(self.sf_basic_config, Soap_Type=MetadataApi) AuraDefinition = meta_api.query(aura_soql) AuraDefinition_MetadataMap = {} if AuraDefinition and 'records' in AuraDefinition: AuraDefinition_MetadataMap = self._covert_AuraDefinition_to_cache_dict(AuraDefinition['records']) if attr: if len(AuraDefinition_MetadataMap) > 0 : return list(AuraDefinition_MetadataMap.values())[0] else : return {} return AuraDefinition_MetadataMap except Exception as ex: self.sublconsole.showlog(ex, 'error') return {} def get_meta_attr(self, full_path): sysio = SysIo() attr = sysio.get_file_attr(full_path) if attr and "file_key" in attr: meta = self.get_meta(attr["metadata_type"], attr["file_key"]) if not meta: print('load from server') self.update_metadata_cache_by_filename(full_path) meta = self.get_meta(attr["metadata_type"], attr["file_key"]) if meta: attr.update(meta) return attr def get_describe_metadata_cache(self, deep=1): self.sublconsole.debug("build describeMetadata Cache") desc_meta_path = os.path.join(self.sf_basic_config.get_config_dir(), self.desc_meta_file) if os.path.exists(desc_meta_path): data = {} with open(desc_meta_path, "r", encoding='utf-8') as fp: data = json.loads(fp.read(),'utf-8') return data elif deep <= self.MAX_DEEP: meta_api = sf_login(self.sf_basic_config, Soap_Type=MetadataApi) describeMetadataResult = meta_api.describeMetadata() self.sublconsole.save_and_open_in_panel(json.dumps(describeMetadataResult, ensure_ascii=False, indent=4), self.save_dir, self.desc_meta_file, is_open=False) deep = deep + 1 return self.get_describe_metadata_cache(deep) return def update_metadata_cache(self, full_path, id): server_meta = self._get_server_meta(full_path, id) if server_meta: self._save_meta(server_meta) def delete_metadata_cache(self, full_path): attr = self.get_meta_attr(full_path) if attr and "file_key" in attr: meta = self.get_meta(attr["metadata_type"], attr["file_key"]) self._del_meta(meta) def get_meta_category(self): all_cache = self.get_cache() return list(all_cache.keys()) def get_meta_namelist(self, category): all_cache = self.get_cache() category_meta = all_cache[category] return list(category_meta.keys()) def get_meta_by_category(self, category): all_cache = self.get_cache() return all_cache[category] if category in all_cache else None def get_meta(self, category, fileName): category_meta = self.get_meta_by_category(category) if category_meta and fileName in category_meta: meta = category_meta[fileName] meta["webUrl"] = self.get_web_url(meta) return meta return def is_modified(self, full_path, id): attr = self.get_meta_attr(full_path) server_meta = self._get_server_meta(full_path, id) if not server_meta: return "Not Found metadata, id=%s" % id if not attr: self._save_meta(server_meta) return "Maybe the server metadata is lasted!" self.sublconsole.debug('>>>check is modified') self.sublconsole.debug(attr) self.sublconsole.debug(server_meta) self.sublconsole.debug('File=%s, Id=%s, local : %s , server : %s' % (attr["file_key"], attr["id"], attr["lastModifiedDate"], server_meta["lastModifiedDate"])) if attr["lastModifiedDate"].replace('+0000', 'Z') != server_meta["lastModifiedDate"].replace('+0000', 'Z'): return "%s is modified by %s, %s, are you sure to update it?" % (server_meta["fileName"], server_meta["lastModifiedByName"], server_meta["lastModifiedDate"]) return "" def get_web_url(self, sel_meta): self.sublconsole.debug(sel_meta) returl = "" if "type" in sel_meta: sel_category = sel_meta["type"] if sel_category == "Workflow": returl = '/01Q?setupid=WorkflowRules' elif sel_category == "CustomLabels": returl = '/101' elif sel_category == "AuraDefinition" and all (k in sel_meta for k in ("id", "DefType", "Format", "AuraDefinitionBundleId")): returl = '/_ui/common/apex/debug/ApexCSIPage?action=openFile&extent=AuraDefinition&Id=%s&AuraDefinitionBundleId=%s&DefType=%s&Format=%s' % (sel_meta["id"], sel_meta["AuraDefinitionBundleId"], sel_meta["DefType"],sel_meta["Format"]) elif sel_meta and "id" in sel_meta and sel_meta["id"]: returl = '/' + sel_meta["id"] elif sel_category == "CustomObject": returl = '/p/setup/layout/LayoutFieldList?type=' + sel_meta["fullName"] return returl def open_in_web(self, sel_meta): returl = self.get_web_url(sel_meta) if returl: import urllib.parse # open in browser sf = sf_login(self.sf_basic_config) login_url = ('https://{instance}/secur/frontdoor.jsp?sid={sid}&retURL={returl}' .format(instance=sf.sf_instance, sid=sf.session_id, returl=urllib.parse.quote(returl))) self.sublconsole.debug(login_url) open_in_default_browser(self.sf_basic_config, login_url) else: self.sublconsole.showlog("metadata is null!") def run_test(self, id_list): tooling_api = sf_login(self.sf_basic_config, Soap_Type=ToolingApi) status_code, result = tooling_api.runTestSynchronous(id_list) if "codeCoverageWarnings" in result: del result["codeCoverageWarnings"] if "codeCoverage" in result: del result["codeCoverage"] if "apexLogId" in result: log_status_code, log = tooling_api.getLog(result["apexLogId"]) coverage_info = "\n".join(self.get_apex_coverage()) file_name = datetime.now().strftime('Test_%Y%m%d_%H%M%S.log') split_str = "\n" + ("~" * 120) + "\n" test_result = split_str.join([ json.dumps(result, ensure_ascii=False, indent=4), coverage_info, log]) self.sublconsole.save_and_open_in_panel(test_result, self.sf_basic_config.get_test_dir(), file_name , is_open=True) def get_apex_coverage(self): tooling_api = sf_login(self.sf_basic_config, Soap_Type=ToolingApi) ApexCodeCoverageAggregate = "SELECT ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered FROM ApexCodeCoverageAggregate ORDER BY ApexClassOrTrigger.Name" coverage_status_code, coverage_result = tooling_api.toolingQuery(ApexCodeCoverageAggregate) if not "records" in coverage_result: return [] total_NumLinesCovered = 0 total_NumLinesUncovered = 0 LINE_SPLIT = "~" * 106 coverage_info = [LINE_SPLIT, "Name".ljust(50) + "NumLinesCovered".ljust(22) + "NumLinesUncovered".ljust(22) + "Coverage%".ljust(22), LINE_SPLIT] for record in coverage_result["records"]: total_NumLinesCovered = total_NumLinesCovered + record["NumLinesCovered"] total_NumLinesUncovered = total_NumLinesUncovered + record["NumLinesUncovered"] lines = int(record["NumLinesCovered"]) + int(record["NumLinesUncovered"]) coverage_percent = record["NumLinesCovered"]/lines if lines != 0 else 0 coverage_percent_str = "%.2f%%" % (coverage_percent * 100) tmp = record["ApexClassOrTrigger"]["Name"].ljust(50) + str(record["NumLinesCovered"]).ljust(22) + str(record["NumLinesUncovered"]).ljust(22) + coverage_percent_str.ljust(22) coverage_info.append(tmp) coverage_info.append(LINE_SPLIT) total_lines = total_NumLinesCovered + total_NumLinesUncovered total_coverage_percent_str = "%.2f%%" % ((total_NumLinesCovered/total_lines if total_lines != 0 else 0) * 100) coverage_info.append("Overall".ljust(50) + str(total_NumLinesCovered).ljust(22) + str(total_NumLinesUncovered).ljust(22) + total_coverage_percent_str.ljust(22)) return coverage_info def _del_meta(self, meta): all_cache = self.get_cache() del all_cache[meta["type"]][meta["fileName"]] self.sublconsole.debug("_del_meta") self.sublconsole.debug(meta) self.sublconsole.save_and_open_in_panel(json.dumps(all_cache, ensure_ascii=False, indent=4), self.save_dir, self.file_name , is_open=False) def _save_meta(self, server_meta): self.sublconsole.debug("_save_meta") all_cache = self.get_cache() print(server_meta) if server_meta and "type" in server_meta: if server_meta["type"] not in all_cache: all_cache[server_meta["type"]] = {} all_cache[server_meta["type"]][server_meta["fileName"]] = server_meta self.sublconsole.debug(server_meta) self.sublconsole.save_and_open_in_panel(json.dumps(all_cache, ensure_ascii=False, indent=4), self.save_dir, self.file_name , is_open=False) def _get_server_meta(self, full_path, id): attr = self.get_meta_attr(full_path) attr["id"] = id if attr["is_lux"]: return self._load_lux_cache(attr) else: soql = "SELECT Id, Name, CreatedDate, CreatedById, CreatedBy.Name, LastModifiedDate, LastModifiedById, LastModifiedBy.Name FROM %s Where Id = '%s'" % (attr["metadata_type"], id) tooling_api = sf_login(self.sf_basic_config, Soap_Type=ToolingApi) result = tooling_api.query(soql) if 'records' in result and len(result['records']) > 0: record = result['records'][0] meta_cache_bean = { "createdById": record["CreatedById"], "createdByName": record["CreatedBy"]["Name"] if record["CreatedBy"] else '', "createdDate": record["CreatedDate"], "fileName": attr["metadata_folder"] + "/" + attr["file_name"], "fullName": attr["name"], "id": record["Id"], "lastModifiedById": record["LastModifiedById"], "lastModifiedByName": record["LastModifiedBy"]["Name"] if record["LastModifiedBy"] else '', "lastModifiedDate": record["LastModifiedDate"], "manageableState": "", "type": attr["metadata_type"] } return meta_cache_bean return def update_metadata_cache_by_filename(self, full_path): # attr = self.get_meta_attr(full_path) sysio = SysIo() attr = sysio.get_file_attr(full_path) if attr["is_lux"] : self._save_meta(self._load_lux_cache(attr)) else: if "metadata_type" not in attr or "name" not in attr: return if attr["metadata_type"] == "AuraDefinitionBundle": soql = "SELECT Id, CreatedDate, CreatedById, CreatedBy.Name, LastModifiedDate, LastModifiedById, LastModifiedBy.Name FROM %s Where DeveloperName = '%s' limit 1" % (attr["metadata_type"], attr["name"]) else: soql = "SELECT Id, Name, CreatedDate, CreatedById, CreatedBy.Name, LastModifiedDate, LastModifiedById, LastModifiedBy.Name FROM %s Where Name = '%s' limit 1" % (attr["metadata_type"], attr["name"]) tooling_api = sf_login(self.sf_basic_config, Soap_Type=ToolingApi) result = tooling_api.query(soql) if 'records' in result and len(result['records']) > 0: record = result['records'][0] meta_cache_bean = { "createdById": record["CreatedById"], "createdByName": record["CreatedBy"]["Name"] if record["CreatedBy"] else '', "createdDate": record["CreatedDate"], "fileName": attr["metadata_folder"] + "/" + attr["file_name"], "fullName": attr["name"], "id": record["Id"], "lastModifiedById": record["LastModifiedById"], "lastModifiedByName": record["LastModifiedBy"]["Name"] if record["LastModifiedBy"] else '', "lastModifiedDate": record["LastModifiedDate"], "manageableState": "", "type": attr["metadata_type"] } self._save_meta(meta_cache_bean) return def update_lux_metas(self, AuraDefinition_records): all_cache = self.get_cache() if "AuraDefinition" not in all_cache: all_cache["AuraDefinition"] = {} all_cache["AuraDefinition"].update( self._covert_AuraDefinition_to_cache_dict(AuraDefinition_records) ) self.sublconsole.save_and_open_in_panel(json.dumps(all_cache, ensure_ascii=False, indent=4), self.save_dir, self.file_name , is_open=False) class SfDataUtil(): def __init__(self, sf_basic_config = None): if sf_basic_config: self.sf_basic_config = sf_basic_config else: self.sf_basic_config = SfBasicConfig() self.tooling_api = sf_login(self.sf_basic_config, Soap_Type=ToolingApi) def get_profiles(self): soql = "SELECT Name From Profile" status_code, result = self.tooling_api.toolingQuery(soql) if not "records" in result: return [] return [ record["Name"] for record in result["records"] ] class DownloadUtil(object): def __init__(self, sf_basic_config = None, is_auto_download=False): if sf_basic_config: self.sf_basic_config = sf_basic_config else: self.sf_basic_config = SfBasicConfig() self.sublconsole = SublConsole(self.sf_basic_config) self.settings = self.sf_basic_config.get_setting() # v2.0.4 donot auto download if is_auto_download: self.download_jar() def download_jar(self): jar_home = self.sf_basic_config.get_default_jar_home() jar_full_path = self.get_jar_path() if not os.path.exists(jar_home): self.sublconsole.showlog("mkdir : " + jar_home) os.makedirs(jar_home) if not os.path.exists( jar_full_path ): self.sublconsole.showlog("download jar : " + jar_full_path) self._start_to_download_jar() def _start_to_download_jar(self): jar_full_path = self.get_jar_path() jar_name = self.get_jar_name() url = "http://salesforcexytools.com/mystatic/jar/" + jar_name import urllib.request try: with urllib.request.urlopen(url) as response, open(jar_full_path, 'wb') as out_file: shutil.copyfileobj(response, out_file) except Exception as e: help_msg = "please copy %s to %s" % (jar_name, self.sf_basic_config.get_default_jar_home()) self.sublconsole.showlog( "download %s error, %s, \n%s" % (url, str(e), help_msg)) pass def get_jar_url_path(self): url = "http://salesforcexytools.com/mystatic/jar/" + self.get_jar_name() return url def get_jar_path(self): jar_home = self.sf_basic_config.get_default_jar_home() jar_name = self.get_jar_name() return os.path.join(jar_home, jar_name) def get_jar_name(self): jar_name = "" return jar_name class DataloaderUtil(DownloadUtil): def __init__(self, sf_basic_config = None): super(DataloaderUtil, self).__init__(sf_basic_config=sf_basic_config, is_auto_download=True) def getEncryptionPassword(self): password = "" try: dl_jar_path = self.get_jar_path() cmd = "java -cp %s com.salesforce.dataloader.security.EncryptionUtil -e %s" % (dl_jar_path, self.settings["password"]) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout_data, stderr_data = p.communicate() ret_data = stdout_data.split(self._get_split_code())[0].decode('utf-8') password = ret_data[-32:] except Exception as e: self.sublconsole.showlog("getEncryptionPassword exception: " + str(e)) return password def _get_split_code(self): if sublime.platform() == "windows": return b"\r\n" else: return b"\n" def get_jar_name(self): jar_name = "dataloader-40.0.0-uber.jar" return jar_name class MigrationToolUtil(DownloadUtil): def __init__(self, sf_basic_config = None, is_auto_download = False): super(MigrationToolUtil, self).__init__(sf_basic_config=sf_basic_config, is_auto_download=is_auto_download) self.sysio = SysIo() def get_jar_name(self): jar_name = "ant-salesforce.jar" return jar_name def _del_old_dir(self, deploy_root_dir): if os.path.exists(deploy_root_dir): shutil.rmtree(deploy_root_dir, ignore_errors=True) sleep(1) def _copy_folder_xml(self, org_file_path, save_full_path): file_path, file_name = os.path.split(org_file_path) xml_file_path = file_path + "-meta.xml" if os.path.isfile(xml_file_path): # shutil.copyfile(xml_file_path, save_full_path) self.copyfile(xml_file_path, save_full_path) self.copy_file_list.append((xml_file_path, save_full_path)) def _copy_file_xml(self, org_file_path, deploy_dir): meta_xml = org_file_path + "-meta.xml" if os.path.isfile(meta_xml): # shutil.copyfile(meta_xml, deploy_dir) self.copyfile(meta_xml, deploy_dir) self.copy_file_list.append((meta_xml, deploy_dir)) ### delete start: ant xml and copyfile.txt # def _build_ant_xml_copy_task(self, deploy_root_dir): # template = """<project name="SalesforceXyTools Migration tools" default="copyfile" basedir="."> # <target name="copyfile"> # <delete dir="src" /> # {copy_task} # </target> # </project>""" # template_copy_task = """ <copy preservelastmodified="true" # file="{from_file}" # tofile="{to_file}" />""" # copy_task = [] # for from_file, to_file in self.copy_file_list: # copy_task.append(template_copy_task.format(from_file=from_file, to_file=to_file)) # copy_task_str = template.replace("{copy_task}", "\n".join(copy_task)) # save_path = os.path.join(deploy_root_dir, "build.copyfile.xml") # self.sysio.save_file(save_path, copy_task_str) # def _build_copy_files_md(self, deploy_root_dir): # copy_task = [] # for from_file, to_file in self.copy_file_list: # copy_task.append(from_file) # save_path = os.path.join(deploy_root_dir, "copyfile.txt") # self.sysio.save_file(save_path, "\n".join(copy_task)) ### delete end def _build_module_json(self, key, files): module_json = CacheLoader(file_name="module.json", always_reload=False, sf_basic_config = self.sf_basic_config) if module_json.is_exist(): module_json_cache = module_json.get_cache() else: module_json_cache = {} module_json_cache[key] = { "desc" : "", "created" : str(datetime.now()), "files" : files } module_json.save_dict(module_json_cache) def copyfile(self, file, to_file, encoding='utf-8'): with open(file, 'rU', encoding=encoding) as infile, \ open(to_file, 'w', newline='\n', encoding=encoding) as outfile: outfile.writelines(infile.readlines()) def copy_deploy_files(self, file_list, deploy_root_dir, api_version="40.0"): self._del_old_dir(deploy_root_dir) copy_org_file_list = [] self.copy_file_list = [] for file in file_list: attr = self.sysio.get_file_attr(file) if not attr or not attr["is_sfdc_file"] or not attr["metadata_folder"]: continue metadata_folder = os.path.join(deploy_root_dir, "src", attr["metadata_folder"], attr["metadata_sub_folder"]) # deploy lightning component folder if attr["is_lux"] or ("is_lwc" in attr and attr["is_lwc"]): if not os.path.exists(metadata_folder): shutil.copytree(attr['file_path'] , metadata_folder) continue if not os.path.exists(metadata_folder): os.makedirs(metadata_folder, exist_ok=True) to_file = os.path.join(metadata_folder, attr["file_name"]) if attr["metadata_folder"] in ["aura", "classes", "components", "objects", "pages", "triggers"]: self.copyfile(file, to_file) else: shutil.copyfile(file, to_file) self.copy_file_list.append((file, to_file)) # copy_org_file_list.append(os.path.join(".", "src", attr["metadata_folder"], attr["metadata_sub_folder"], attr["file_name"])) copy_org_file_list.append(file) self._copy_folder_xml(file, metadata_folder + "-meta.xml") self._copy_file_xml(file, os.path.join(metadata_folder, attr["file_name"] + "-meta.xml")) self._build_module_json(os.path.basename(deploy_root_dir), copy_org_file_list) # self._build_ant_xml_copy_task(deploy_root_dir) # self._build_copy_files_md(deploy_root_dir) self.build_package_xml(os.path.join(deploy_root_dir, "src", "package.xml"), file_list, api_version) def build_package_xml(self, save_path, file_list, api_version="40.0"): print('build package.xml to deploy') file_attr_map = {} for file in file_list: attr = self.sysio.get_file_attr(file) if not attr or not attr["is_sfdc_file"]: continue if attr["metadata_type"] in file_attr_map: file_attr_map[attr["metadata_type"]].append(attr) else: file_attr_map[attr["metadata_type"]] = [attr] packagexml = self._get_package_xml(file_attr_map, api_version) self.sysio.save_file(save_path, packagexml) return packagexml def _get_package_xml(self, file_attr_map, api_version="40.0"): packagexml_types = "" for metadata_type, file_attr_list in file_attr_map.items(): members = [] members_check = [] for attr in file_attr_list: if ("is_lux" in attr and attr["is_lux"]) or ("is_lwc" in attr and attr["is_lwc"]): member = attr["metadata_sub_folder"] else: member = attr["name"] if not attr["metadata_sub_folder"] else "%s/%s" % (attr["metadata_sub_folder"],attr["name"]) if member in members_check: continue members_check.append(member) members.append(""" <members>{member}</members>""".format(member=member)) metadata_type = "AuraDefinitionBundle" if attr["is_lux"] else metadata_type packagexml_types = packagexml_types + PACKAGEXML_TYPE.format(members='\n'.join(members),name=metadata_type) packagexml = PACKAGE_XML.format(types=packagexml_types, version=api_version) return packagexml PACKAGEXML_TYPE = """ <types> {members} <name>{name}</name> </types>""" PACKAGE_XML = """<?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> {types} <version>{version}</version> </Package> """ class OsUtil(): def __init__(self, sf_basic_config = None): if sf_basic_config: self.sf_basic_config = sf_basic_config else: self.sf_basic_config = SfBasicConfig() self.sublconsole = SublConsole(self.sf_basic_config) self.settings = self.sf_basic_config.get_setting() self.sys_encoding = sys.getfilesystemencoding() def run_in_sublime_cmd(self, cmd_list, encoding=None): if not encoding: encoding = self.sys_encoding self.sublconsole.thread_run(target=self._run_cmd, args=(cmd_list, encoding,)) def _run_cmd(self, cmd_list, encoding): self.sublconsole.showlog("*" * 80) cmd_str = self._get_cmd_str(cmd_list) process = subprocess.Popen(cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # encoding = 'shift-jis' while True: line = process.stdout.readline() if line != '' and line != b'' : #the real code does filtering here try: msg = line.rstrip().decode(encoding) except UnicodeDecodeError as ex: msg = line.rstrip().decode(self.sys_encoding) except Exception as ex: msg = line.rstrip() self.sublconsole.showlog(msg, show_time=False) else: break self.sublconsole.showlog("*" * 80) def os_run(self, cmd_list): if self.sf_basic_config.is_use_os_terminal(): self.run_in_os_termial(cmd_list) else: self.run_in_sublime_cmd(cmd_list) def run_in_os_termial(self, cmd_list): if sublime.platform() == "windows": cmd_list.append("pause") cmd_str = self._get_cmd_str(cmd_list) thread = threading.Thread(target=os.system, args=(cmd_str,)) thread.start() def _get_cmd_str(self, cmd_list): cmd_str = " & ".join(cmd_list) return cmd_str def get_cd_cmd(self, path): if sublime.platform() == "windows": return "cd /d \"%s\"" % (path) else: return "cd \"%s\"" % (path) class DiffUtil(): def __init__(self, sf_basic_config = None): if sf_basic_config: self.sf_basic_config = sf_basic_config else: self.sf_basic_config = SfBasicConfig() self.sublconsole = SublConsole(self.sf_basic_config) self.settings = self.sf_basic_config.get_setting() def diff(self, local_file, server_file): self.sf_basic_config = SfBasicConfig() winmerge = self.sf_basic_config.get_winmerge() if winmerge and os.path.exists(winmerge): cmd = "%s %s %s" % (winmerge, local_file, server_file) subprocess.Popen(cmd) else: self._default_diff_util(local_file, server_file) def _default_diff_util(self, local_file, server_file): import difflib local_txt = self._read_file(local_file) server_txt = self._read_file(server_file) local_sign = "localfile" server_sign = "serverfile" diff = difflib.unified_diff(local_txt, server_txt, local_sign, server_sign, local_file, server_file, lineterm='') difftxt = u"\n".join(line for line in diff) if difftxt == "": self.sublconsole.showlog("There is no difference !") else: self.sublconsole.show_in_new_tab(difftxt, "local <-> server") def _read_file(self, file): f = open(file, "r", encoding='utf-8') file_txt = f.readlines() f.close() return file_txt ########################################################################################## #browser Util ########################################################################################## def open_in_browser(sf_basic_config, url, browser_name = '', browser_path = ''): settings = sf_basic_config.get_setting() if not browser_path or not os.path.exists(browser_path) or browser_name == "default": webbrowser.open_new_tab(url) elif browser_name == "chrome-private": # os.system("\"%s\" --incognito %s" % (browser_path, url)) browser = webbrowser.get('"' + browser_path +'" --incognito %s') browser.open(url) else: try: # show_in_panel("33") # browser_path = "\"C:\Program Files\Google\Chrome\Application\chrome.exe\" --incognito" webbrowser.register('chromex', None, webbrowser.BackgroundBrowser(browser_path)) webbrowser.get('chromex').open_new_tab(url) except Exception as e: webbrowser.open_new_tab(url) def open_in_default_browser(sf_basic_config, url): browser_map = sf_basic_config.get_default_browser() browser_name = browser_map['name'] browser_path = browser_map['path'] if not browser_path or not os.path.exists(browser_path) or browser_name == "default": webbrowser.open_new_tab(url) elif browser_map['name'] == "chrome-private": # chromex = "\"%s\" --incognito %s" % (browser_path, url) # os.system(chromex) browser = webbrowser.get('"' + browser_path +'" --incognito %s') browser.open(url) # os.system("\"%s\" -ArgumentList @('-incognito', %s)" % (browser_path, url)) else: try: webbrowser.register('chromex', None, webbrowser.BackgroundBrowser(browser_path)) webbrowser.get('chromex').open_new_tab(url) except Exception as e: webbrowser.open_new_tab(url) ########################################################################################## #END ##########################################################################################