728x90

Qt로 Database에 접속하려고 하면 QSqlDatabase를 사용해야 합니다. 그 중 사용할 데이터베이스가 PostgreSQL일 경우 addDatabase("QPSQL")을 사용합니다.

 

간단히 코드를 보면 아래와 같습니다.

m_serverDB = QSqlDatabase::addDatabase("QPSQL");      // PostgreSQL Driver

m_serverDB.setHostName(m_connectionInfo.host);
m_serverDB.setPort(m_connectionInfo.port);
m_serverDB.setDatabaseName(m_connectionInfo.dbName);
m_serverDB.setUserName(m_connectionInfo.user);
m_serverDB.setPassword(m_connectionInfo.pass);

if ( !m_serverDB.open() ) {
    sErrMsg = m_serverDB.lastError().text();
    sErrMsg.prepend(SERVERDB_ERROR_MSG);
    return bResult;
}

만약 이 때 에러 메시지로 "Driver not loaded"가 나오는 경우가 있는데 이때는 아래의 내용을 확인해봐야 합니다.

 

1. exe가 있는 폴더 혹은 library path에 아래의 dll이 있는지

  • libcrypto-3-x64.dll
  • libiconv-2.dll
  • libintl-9.dll
  • libpq.dll
  • libssl-3-x64.dll
  • libwinpthread-1.dll

여기서 중요한건 libwinpthread-1.dll 입니다. GPT나 Gemini한테 물어보면 libwinpthread-1.dll를 제외한 나머지 파일들의 유무를 많이 얘기하는데 PostgreSQL은 mingw/gcc로 빌드 하였고 만약 Qt build를 msvc로 했을 경우 문제가 발생할 수 있습니다.

 

2. exe가 있는 폴더에 sqldriver 폴더 생성 후 qsqlpsql.dll, qsqlodbc.dll 있는지 

 

저도 이 문제 때문에 꽤 애를 먹었는데 [libwinpthread-1.dll] 파일을 exe에 추가하여 배포하였더니 사용자 PC에서도 문제가 없었습니다. 

728x90
728x90

요즘에 QXlsx를 이용해서 프로그램의 실행 결과를 자동으로 엑셀로 만들어주는 기능을 작업하고 있습니다.

 

이때 Summary 시트의 결과 셀을 누르면 Log 시트의 특정 Cell로 이동하게 만들고 싶어서 Hyperlink 코드 추가했습니다.

Url을 "#시트이름!셀"로 넣고 마지막 "text" 부분에 실제 표시할 텍스트를 넣어주시면 됩니다.

#include <xlsxdocument.h>
#include <xlsxworksheet.h>

{
    ...;
    
    auto currentSheet = reportFile.currentWorksheet();
    
    QXlsx::Format linkFormat;
    linkFormat.setFontColor(Qt::blue);
    linkFormat.setFontUnderline(QXlsx::Format::FontUnderlineSingle);
    
    QString sLogCellUrl = QString("#%1!A%2").arg("Log", QString::number(Row));
    currentSheet->writeHyperlink(nRow, nColumn++, QUrl(sLogCellUrl), linkFormat, "text");
    ...;
    
}

 

728x90
728x90

Confluence에서 페이지를 업데이트할 때 두 가지 옵션이 있습니다. 그냥 [업데이트]와 [업데이트 설정 조정을 통해 버전 댓글을 남겨 업데이트]가 있습니다.
 


버전 댓글을 남기게 된다면 Git에서 Commit 같이 어떤 내용이 변경되었는지 버전 별로 간단하게 메시지를 남길 수 있어 필요한 기능이라고 생각이 되는데 이를 강제할 수 있는 방법은 현재 없는 것으로 보입니다. 



그래서 사용자가 버전 댓글을 남기지 않고 그냥 업데이트 했을 때 관리자에게 메일을 통해 알려서 버전 댓글을 달 수 있게 하는 방법에 대해 소개하려고 합니다.
 
먼저 gmail의 smtp를 이용하기 위해서 Google [앱 비밀번호]를 먼저 발급받아야 합니다. 


발급된 [기기용 앱 비밀번호]를 따로 복사 해둡니다.



[ScriptRunner] > [Script Listeners]에서 [Add Listener]를 눌러줍니다. Called에 간단히 제목이나 설명을 추가하고 [On these events]에 우리는 버전 댓글 없이 업데이트 됐을 때니 [Page updated]를 선택합니다.


그리고 Code to Run에 아래 코드를 입력 후 [Save] 합니다.

import javax.mail.internet.*
import javax.mail.*

def pageID = page["id"]             // 현재 업데이트 된 페이지 ID
def curVerID = page["version"]      // 업데이트 된 페이지 현재 버전

def res = get("/wiki/api/v2/pages/${pageID}/versions").asObject(Map).body
def value = get("/wiki/api/v2/pages/${pageID}?expand=body.storage").asObject(Map).body

def lastVersionRes = res["results"].find { ver ->
    ver["number"] == curVerID
} // 마지막으로 update 된 버전의 정보

if ( lastVersionRes["message"] == "" ) {

    Properties prop = new Properties()
    //Enter the details of your SMTP server
    prop.put("mail.smtp.auth", true)
    prop.put("mail.smtp.host", "smtp.gmail.com")
    prop.put("mail.smtp.port", "587")
    prop.put("mail.smtp.starttls.enable", "true")

    String emailId = "Gmail ID"
    String password = "App Password"

    Session session = Session.getInstance(prop, new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            new PasswordAuthentication(emailId, password)
        }
    })

    MimeMessage msg = new MimeMessage(session)
    msg.setFrom(new InternetAddress(emailId, "Confluence Alert"))
    msg.setSubject("[Confluence] 페이지 업데이트 버전 댓글이 제대로 기록되지 않았습니다.", "UTF-8")
	
    String htmlBody = """
    <html>
        <body>
            <p><strong> 업데이트 한 ${page["title"]}에 버전 댓글이 입력되지 않았습니다.</strong></p>
            <p><strong> Url = <a href="${page["self"]}">${page["self"]}</a></strong></p>
        </body>
    </html>
    """   
    msg.setContent(htmlBody, "text/html; charset=UTF-8")
    msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("Admin Email", false))

    Transport.send(msg)
    logger.info("Email Sent Successfully!!")
} // 버전 정보가 비어져 있으면 버전 메시지를 추가해서 다시 업데이트


이제 Listener를 등록해 놨으니 버전 댓글 없이 업데이트 시 아래처럼 메일이 오게 됩니다.



그리고 만약에 버전 댓글을 기록하지 않은 버전을 지우고 싶다면 먼저 버전 댓글이 입력되지 않은 상태에서 업데이트 버튼을 누른 후 글의 내용은 일절 수정하지 않고 버전 기록만 남긴 후 업데이트 합니다.

 
다음으로 [버전 이력]으로 들어가 버전 이력을 남기지 않은 버전을 삭제합니다.(관리자 계정으로만 가능)


 
그렇게 되면 버전 이력이 추가된 버전과 그 이전 버전만 남게 되게 됩니다.


 

728x90
728x90

이전 ScriptRunner 게시글에서 사용했던것처럼 ScriptRunner를 이용하여 Jira의 상태를 자동으로 변경하고 버전 정보를 넣는 간단한 코드입니다.

 

ScriptRunner > Script Console에서 사용하였습니다.

// 이슈 상태 바꾸고 version 픽스하기
def trans = '이슈 다시 열기'     // 이슈 닫기 or 이슈 다시 열기
def type = 'issue'

Issues.search("type = " + type).each { issue->

    if ( issue.getTransitions()['name'].contains(trans) ) {
        logger.warn('Issue = ' + issue.getKey())
        issue.transition(trans)

    }

    if ( issue.getStatus()['name'] == '다시 열림' ) {
        issue.update { 
            setFixVersions {
                add('ver2')
            }
        }
    }
}
728x90
728x90

Confluence 페이지에 특정 상태를 지정해주는 방법입니다.
 
[스페이스 설정]으로 들어간 후 [콘텐츠 상태]로 들어갑니다. 
여기서 콘텐츠 상태를 활성화 하고 상태를 지정해줍니다.(최대 5개)
 


상태를 지정한 후에는 페이지 편집 화면에서 제목 영역부분으로 커서를 올린 후 [상태]를 통해 지정을 해줄 수 있습니다.
 


여기서 만약에 페이지를 만든 후 자동으로 상태를 지정하게 하고 싶다면 [자동화]를 통해 작업이 가능합니다.
 
[자동화] > [규칙 만들기]를 통해 규칙을 생성합니다.
 
여기서 페이지 게시할 때 만약 페이지 상태가 지정되어 있지 않다면 자동으로 기본 상태를 지정하게 됩니다.


 

728x90
728x90

Confluence에 기본적으로 매크로들이 많이 제공되지만 ScriptRunner라는 App을 사용하면 REST API를 통해 Jira의 정보들을 사용할 수 있습니다.
 
Confluence에서 [앱] > [새 앱 찾기]를 들어가 [ScriptRunner for Confluence]를 설치합니다. 
 
설치가 완료됐으면 [Get Started] 옆에 있는 점 세개 버튼을 눌러 [Configure]로 들어갑니다.


초기 화면으로 들어오면 여러 탭이 있지만 일단 여기서는 [Script Console]과 [Macros]를 사용해보려고 합니다.
 
[Script Console]을 통해 Jira의 정보를 가져올 REST API 호출문을 먼저 실행해볼 수 있고 원하는 결과가 나왔으면 이제 그 코드로 [Macro]로 만들어줄 예정입니다.
 
[Script Console]에서 아래 코드 입력 화면에 먼저 특정 이슈에 대한 정보를 가져올 수 있는 코드를 실행해보겠습니다.
여기서 우리가 필요한것은 Jira의 API Token인데 파싱할 Jira 페이지로 이동합니다.
 
Jira 프로필 사진을 누른 후 [계정 관리]로 들어갑니다.
 
상단 메뉴에서 [보안]으로 이동한 후 하단 [API 토큰]에서 [API 토큰 만들기 및 관리]로 들어갑니다.
 


[API 토큰 만들기]를 눌러 만들기 후 생성된 토큰 값을 복사해줍니다.
 


이제 다시 [ScriptRunner for Confluence]의 [Script Console]로 넘어와서 아래 코드 입력 창에 아래와 같이 입력합니다.

def issue = get("https://[도메인].atlassian.net/rest/api/3/issue/[Issue Key]")
            .basicAuth("[아이디]", "[API Token]")
            .header("Accept", "application/json")
            .asObject(Map).body

return issue.key

이렇게 코드를 각자에 맞게 넣어주신 후 [Run]을 누르면 하단에 해당 Issue의 Key가 출력되게 됩니다.
 
이를 이용해서 매크로를 만들어주려 합니다. [Macros] 탭으로 이동합니다. [Create Custom Macro]를 눌러줍니다.


매크로 이름과 설명을 간단히 작성 후 아래 [parameters]로 이동하여 [Add Parameter]를 선택합니다. 그리고 파라미터로 받을 issueID를 생성 후 [Add]를 눌러줍니다.
 


이제 우리는 아까 [Issue Key] 부분을 우리가 전달한 parameter로 대체하게 되는데 [Script to execute]에 아래 코드를 넣어줍니다.

def issue = get("https://[도메인].atlassian.net/rest/api/3/issue/${parameters.issueID}")
            .basicAuth("[아이디]", "[API Token]")
            .header("Accept", "application/json")
            .asObject(Map).body

return issue.key

이제 [Save]로 매크로를 저장해주고 Confluence Page를 하나 생성한 후 "/만든 매크로 이름"을 선택 하고 설정한 파라미터에 원하는 키를 입력하면 됩니다.
 


 
 

728x90
728x90

enum 값을 사용할 때 for 문으로 enum 값을 증가시키면서 사용하는 방식을 사용할 수도 있습니다. 이럴 때 enum을 정의한 후 바로 증가 연산자를 사용할 수 없고 operator++를 재정의 해주어야 합니다.

enum fruit {
	frApple,
    frOrange,
    
    num_of_fruit
};

fruit& operator++(fruit& fr) {
    fr = static_cast<fruit>(static_cast<int>(fr) + 1);
    return fr;
}

fruit operator++(fruit& fr, int) {
    fruit temp = fr;
    fr = static_cast<fruit>(static_cast<int>(fr) + 1);
    if (fr > num_of_fruit) {
        fr = num_of_fruit;  
    }
    return temp;
}

첫 번째 operator++는 전위 증가 연산자를 위한 재정의, 두 번째 operator++는 후위 증가 연산자를 위한 재정의 함수입니다.

 

728x90

'Programming > C++' 카테고리의 다른 글

[C++] boost windows 비동기 처리하기  (2) 2023.10.23
[C++] Template(2)  (0) 2023.09.22
[C++] Template(1)  (2) 2023.09.19
[C++] std::forward_list, std::list  (0) 2023.07.18
[C++] std::array, std::vector  (0) 2023.07.12
728x90

보통 QTreeWidget에서 아이템을 싹 비우려고 할 때 Clear() 함수를 많이 사용합니다. 하지만 이 함수의 경우 QTreeWidget에 가지고 있는 아이템들의 메모리를 해제시키기 때문에 아래와 같이 아이템을 다시 불러오려고 할 때 문제가 생기게 됩니다.

class Test {
    Test(uchar nItemCount) {
    	for ( int i=0; i < nItemCount; i++ ) {
    		QTreeWidgetItem* it = new QTreeWidgetItem;
        	items.append(it);
        }
    }
    QList<QTreeWidgetItem*> getItems() { return Items; }
    
	QList<QTreeWidgetItem*> Items;
};

int main() {
    Test* t1 = new Test(3);
    Test* t2 = new Test(10);
    
    auto items = t1->getItems();
    for ( auto i : items ) {
    	ui->tree->addTopLevelCount(i);
    }
    
    ui->tree->clear();		// t1이 갖고 있는 item들 delete 됨
    
    auto items = t2->getItems();
    for ( auto i : items ) {
    	ui->tree->addTopLevelCount(i);
    }
    
    ui->tree->clear();		// t2이 갖고 있는 item들 delete 됨
    
    auto items = t1->getItems();
    for ( auto i : items ) {
    	ui->tree->addTopLevelCount(i);		// i는 쓰레기 값
    }
    
    ...
}

이럴때는 clear를 사용하는 것이 아닌 takeTopLevelItem을 통해 단순히 트리에서 제거해주면 됩니다.

while (topLevelItemCount() > 0) {
    takeTopLevelItem(0);
} // clear but not delete items
728x90

+ Recent posts