def uploadNoteImageToWordpress(self,
                                   curNoteDetail,
                                   curResource,
                                   curResList=None):
        """Upload note single imges to wordpress, and sync to note (replace en-media to img) 

        Args:
            curNote (Note): evernote Note
            curResource (Resource): evernote Note Resource
            curResList (list): evernote Note Resource list
        Returns:
            upload image url(str)
        Raises:
        """
        if not curResList:
            curResList = curNoteDetail.resources

        uploadedImgUrl = ""

        curResInfoStr = crifanEvernote.genResourceInfoStr(curResource)

        isImg = self.evernote.isImageResource(curResource)
        if not isImg:
            logging.warning("Not upload resource for NOT image for %s",
                            curResInfoStr)
            return uploadedImgUrl

        foundResEnMediaSoup = crifanEvernote.findResourceSoup(
            curResource, curNoteDetail=curNoteDetail)
        if not foundResEnMediaSoup:
            logging.warning(
                "Not need upload for not found related <en-media> node for %s",
                curResInfoStr)
            return uploadedImgUrl

        isUploadOk, respInfo = self.uploadImageToWordpress(curResource)
        if isUploadOk:
            # {'id': 70491, 'url': 'https://www.crifan.com/files/pic/uploads/2020/11/c8b16cafe6484131943d80267d390485.jpg', 'slug': 'c8b16cafe6484131943d80267d390485', 'link': 'https://www.crifan.com/c8b16cafe6484131943d80267d390485/', 'title': 'c8b16cafe6484131943d80267d390485'}
            uploadedImgUrl = respInfo["url"]
            logging.info("Uploaded url %s", uploadedImgUrl)
            # "https://www.crifan.com/files/pic/uploads/2020/03/f6956c30ef0b475fa2b99c2f49622e35.png"
            # relace en-media to img
            respNote = self.syncNoteImage(curNoteDetail, curResource,
                                          uploadedImgUrl, curResList)
            # logging.info("Complete sync image %s to note %s", uploadedImgUrl, respNote.title)
        else:
            logging.warning(
                "Failed to upload image resource %s to wordpress, respInfo=%s",
                curResInfoStr, respInfo)

        return uploadedImgUrl
    def syncNoteImage(self,
                      curNoteDetail,
                      curResource,
                      uploadedImgUrl,
                      curResList=None):
        """Sync uploaded image url into Evernote Note content, replace en-media to img

        Args:
            curNoteDetail (Note): evernote Note
            curResource (Resource): evernote Note Resource
            uploadedImgUrl (str): uploaded imge url, previously is Evernote Resource
            curResList (list): evernote Note Resource list
        Returns:
            updated note detail
        Raises:
        """
        if not curResList:
            curResList = curNoteDetail.resources

        soup = crifanEvernote.noteContentToSoup(curNoteDetail)
        curEnMediaSoup = crifanEvernote.findResourceSoup(curResource,
                                                         soup=soup)
        logging.debug("curEnMediaSoup=%s", curEnMediaSoup)
        # curEnMediaSoup=<en-media hash="0bbf1712d4e9afe725dd51e701c7fae6" style="width: 788px; height: auto;" type="image/jpeg"></en-media>

        if curEnMediaSoup:
            curImgSoup = curEnMediaSoup
            curImgSoup.name = "img"
            curImgSoup.attrs = {"src": uploadedImgUrl}
            logging.debug("curImgSoup=%s", curImgSoup)
            # curImgSoup=<img src="https://www.crifan.com/files/pic/uploads/2020/11/c8b16cafe6484131943d80267d390485.jpg"></img>
            # new content string
            updatedContent = crifanEvernote.soupToNoteContent(soup)
            logging.debug("updatedContent=%s", updatedContent)
            curNoteDetail.content = updatedContent
        else:
            logging.warning(
                "Not found en-media node for guid=%s, mime=%s, fileName=%s",
                curResource.guid, curResource.mime,
                curResource.attributes.fileName)
            # here even not found, still consider as processed, later will remove it

        # remove resource from resource list
        # oldResList = curNoteDetail.resources
        # Note: avoid side-effect: alter pass in curNoteDetail object's resources list
        # which will cause caller curNoteDetail.resources loop terminated earlier than expected !
        # oldResList = copy.deepcopy(curNoteDetail.resources)
        # oldResList.remove(curResource) # workable
        # newResList = oldResList
        # Note 20201206: has update above loop, so should directly update curNoteDetail.resources
        # curNoteDetail.resources.remove(curResource)
        # newResList = curNoteDetail.resources

        curResList.remove(curResource)
        newResList = curResList

        # # for debug
        # if not newResList:
        #     logging.info("empty resources list")

        syncParamDict = {
            # mandatory
            "noteGuid": curNoteDetail.guid,
            "noteTitle": curNoteDetail.title,
            # optional
            "newContent": curNoteDetail.content,
            "newResList": newResList,
        }
        respNote = self.evernote.syncNote(**syncParamDict)
        # logging.info("Completed sync image %s to evernote note %s", uploadedImgUrl, curNoteDetail.title)
        logging.info("Completed sync image %s", uploadedImgUrl)

        return respNote
    def batchUploadAndSyncImage(self, curNoteDetail, isCheckExisted):
        """Batch upload image to wordpress and sync to note (replace en-media to img) 

        Args:
            curNoteDetail (Note): evernote Note
            isCheckExisted (bool): whether check image is uploaded or not
        Returns:
            resp Note
        Raises:
        """
        originResList = copy.deepcopy(curNoteDetail.resources)
        totalResNum = len(originResList)
        logging.info("Total resources: %d", totalResNum)
        latestResList = copy.deepcopy(curNoteDetail.resources)

        soup = crifanEvernote.noteContentToSoup(curNoteDetail)

        for curResIdx, eachResource in enumerate(originResList):
            curResNum = curResIdx + 1
            logging.info("%s resource %d/%d %s", "-" * 20, curResNum,
                         totalResNum, "-" * 20)
            curResInfoStr = crifanEvernote.genResourceInfoStr(eachResource)
            if self.evernote.isValidImageResource(eachResource):
                curEnMediaSoup = crifanEvernote.findResourceSoup(eachResource,
                                                                 soup=soup)
                if not curEnMediaSoup:
                    logging.warning(
                        "NOT upload for not found related <en-media> node for %s",
                        curResInfoStr)
                    continue

                isUploadOk, respInfo = self.uploadImageToWordpress(
                    eachResource, isCheckExisted)
                if isUploadOk:
                    uploadedImgUrl = respInfo["url"]
                    logging.info("Uploaded image url %s", uploadedImgUrl)

                    curImgSoup = curEnMediaSoup
                    curImgSoup.name = "img"
                    curImgSoup.attrs = {"src": uploadedImgUrl}
                    latestResList.remove(eachResource)
                else:
                    logging.error(
                        "Failed to upload image resource=%s, respInfo=%s",
                        curResInfoStr, respInfo)
            else:
                logging.warning("NOT upload for non-image resource: %s",
                                curResInfoStr)

        updatedContent = crifanEvernote.soupToNoteContent(soup)

        newResList = latestResList

        syncParamDict = {
            # mandatory
            "noteGuid": curNoteDetail.guid,
            "noteTitle": curNoteDetail.title,
            # optional
            "newContent": updatedContent,
            "newResList": newResList,
        }
        respNote = self.evernote.syncNote(**syncParamDict)
        return respNote