package sys; import java.awt.Desktop; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.net.URL; import java.net.URLConnection; import java.net.HttpURLConnection; import java.net.URI; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Properties; import org.jsoup.Jsoup; import org.jsoup.select.Elements; import org.jsoup.nodes.Document; import common.ErrorHandling; /** * <p>시스템과 관련된 클래스 * <p>폴더생성, 브라우저 오픈등의 기능이 static으로 내장 * <p>Instantiation 불가(private 생성자) * @author occidere */ @SuppressWarnings("unused") public class SystemInfo { private SystemInfo(){} //운영체제별 파일 구분자 static final String fileSeparator = File.separator; //윈도우는 \, 나머지는 / //운영체제별 줄바꿈 문자 static final String lineSeparator = System.getProperty("line.separator"); //윈도우는 \r\n, 유닉스 계열은 \n, 맥은 \r //OS이름 ex) Windows 10, Linux 등.. static transient final String OS_NAME = System.getProperty("os.name"); //디폴트 저장 디렉토리 ex) C:\Users\occid\Marumaru 또는 home/occidere/marumaru public static transient final String DEFAULT_PATH = System.getProperty("user.home")+ fileSeparator + "Marumaru"; //다운로드 저장할 디렉토리. public static transient String PATH = DEFAULT_PATH; //에러 로그 저장 경로(무조건 디폴트 경로에 위치) public static final String ERROR_LOG_PATH = DEFAULT_PATH + fileSeparator + "log"; //마루마루 브라우저 주소 public static final String MARU_ADDR = "http://marumaru.in/"; //최신 버전 공지할 페이지 주소 private transient static final String LATEST_VERSION_URL = "https://github.com/occidere/MMDownloader/blob/master/VERSION_INFO"; /* <수정 금지> 프로그램 정보 */ private static final String VERSION = "0.5.2.2"; //프로그램 버전 private static final String UPDATED_DATE = "2018.07.30"; //업데이트 날짜 private static final String DEVELOPER = "제작자: occidere"; //제작자 정보 private static final String VERSION_INFO = String.format("현재버전: %s (%s)", VERSION, UPDATED_DATE); /* 최신 버전 버전. 서버 연결해서 정보 받아오기 전까진 null */ private static String LATEST_VERSION = null; private static String LATEST_UPDATED_DATE = null; private static String LATEST_VERSION_INFO = null; private static String LATEST_WINDOWS = null; //윈도우용 최신버전 링크 private static String LATEST_OTHERS = null; //다른OS 최신버전 링크 /** * <p>제작자 정보와 현재 버전, 저장경로 등 프로그램 정보 출력. * <p>프로그램 첫 시작시 보여줄 정보. */ public static void printProgramInfo(){ try { Configuration.refresh(); } catch (Exception e) {} System.out.printf("%s\t%s\n", DEVELOPER, VERSION_INFO); System.out.printf("저장경로: %s\n", Configuration.getString("PATH", DEFAULT_PATH)); System.out.printf("(이미지 병합: %s, 디버깅 모드: %s, 멀티스레딩: %d, zip 압축: %s)\n", Configuration.getBoolean("MERGE", false), Configuration.getBoolean("DEBUG", false), Configuration.getInt("MULTI", 2), Configuration.getBoolean("ZIP", false)); } /** * <p>최신 버전 출력(현재 버전과 최신 버전을 같이 보여준다) * <p>깃허브에 등록한 최신버전 명시해놓은 파일을 불러와서 파싱해 출력. * <p>최초 1회만 연결 작업 수행하며, 이후엔 변수에 저장된 내용 계속 사용. * @param in 입력받을 버퍼리더 객체 */ public static void printLatestVersionInfo(BufferedReader in){ //최신 버전 정보가 null 인 경우에만 실행 == 최초 1회만 실행 if(LATEST_VERSION_INFO == null){ System.out.println("최신 버전 확인중..."); try{ Document doc = Jsoup.connect(LATEST_VERSION_URL).get(); Elements e = doc.getElementsByClass("highlight tab-size js-file-line-container"); LATEST_VERSION = e.select("#LC1").text().split("=")[1]; LATEST_UPDATED_DATE = e.select("#LC2").text().split("=")[1]; LATEST_WINDOWS = e.select("#LC3").text().split("=")[1]; LATEST_OTHERS = e.select("#LC4").text().split("=")[1]; LATEST_VERSION_INFO = String.format("최신버전: %s (%s)", LATEST_VERSION, LATEST_UPDATED_DATE); } catch(Exception e){ ErrorHandling.saveErrLog("업데이트 서버 연결 실패", "", e); return; //업데이트 실패 시 버전 체크 작업 종료 } } System.out.printf("%s\n%s\n", VERSION_INFO, LATEST_VERSION_INFO); //현재 버전 & 서버의 최신 버전 출력 //최신버전 체크가 제대로 됬을 때, 현재 버전이 최신 버전보다 낮으면 업데이트 여부 물어봄. if(LATEST_VERSION_INFO != null && LATEST_VERSION_INFO.length() > 0){ int curVersion = Integer.parseInt(VERSION.replace(".", "")); int latestVersion = Integer.parseInt(LATEST_VERSION.replace(".", "")); if(curVersion < latestVersion) { downloadLatestVersion(in); } else if(curVersion == latestVersion) { System.out.println("현재 최신버전입니다!"); } else { System.out.println("버전이 이상합니다! ᕙ(•̀‸•́‶)ᕗ"); } } } /** * <p>최신버전 다운로드 메서드. * <p>최신버전 확인 메서드 내부에서 불러지는 용도로만 사용하게 제한. * <p>사용자의 OS 값에 따라서 윈도우 / 그 이외(맥, 리눅스) 버전을 다운로드 * @param in 키보드 입력용 버퍼리더 객체 */ private static void downloadLatestVersion(final BufferedReader in){ try{ final int MB = 1048576; final int BUF_SIZE = MB * 10; String select, fileName = null, fileURL = null; boolean isCorrectlySelected = false; while(!isCorrectlySelected){ //다운로드 받거나(y) 취소(n)를 제대로 선택할 때 까지 반복. System.out.printf("최신 버전(%s)을 다운받으시겠습니까? (Y/n): ", LATEST_VERSION); select = in.readLine(); if(select.equalsIgnoreCase("y")){ isCorrectlySelected = true; makeDir(); //Marumaru폴더 생성 /* OS가 윈도우면, 파일 이름 = MMdownloader_0.5.0.0_Windows.zip */ if(OS_NAME.contains("Windows")){ fileName = LATEST_WINDOWS.substring(LATEST_WINDOWS.lastIndexOf("/")+1); fileURL = LATEST_WINDOWS; } /* OS가 윈도우 이외면 파일 이름 = MMdownloader_0.5.0.0_Mac,Linux.zip */ else{ fileName = LATEST_OTHERS.substring(LATEST_OTHERS.lastIndexOf("/")+1); fileURL = LATEST_OTHERS; } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(DEFAULT_PATH+fileSeparator+fileName), BUF_SIZE); HttpURLConnection conn = (HttpURLConnection)new URL(fileURL).openConnection(); conn.setConnectTimeout(30000); // 타임아웃 30초 conn.setReadTimeout(60000); // 타임아웃 1분 BufferedInputStream bis = new BufferedInputStream(conn.getInputStream(), BUF_SIZE); final double MB_SIZE = (double) conn.getContentLength() / MB; // 전체 파일 사이즈의 MB 값 int readByte = 0; // 읽은 바이트 값 int accum = 0; // 누적 바이트 길이 int count = 0; // MB 를 넘기 전 까지 읽어들인 바이트 길이 System.out.println("다운로드중 ..."); while((readByte = bis.read()) != -1){ bos.write(readByte); /* MB 별로 다운로드 진행 상황을 출력함 */ if(++count >= MB) { accum += count; count = 0; System.out.printf("%,3.2f MB / %,3.2f MB 완료!\n", (double) accum / MB, MB_SIZE); } } bos.close(); bis.close(); System.out.printf("%,3.2f MB / %,3.2f MB 완료!\n", (double) (accum + count) / MB, MB_SIZE); System.out.println("완료! (위치: "+DEFAULT_PATH+fileSeparator+fileName+")"); } else if(select.equalsIgnoreCase("n")){ isCorrectlySelected = true; System.out.println("업데이트가 취소되었습니다."); } } } catch(Exception e){ ErrorHandling.saveErrLog("최신버전 다운로드 에러", "", e); } } /** * <p>마루마루 인터넷창 열기 * <p>매개변수 void면 MARU_ADDR(http://marumaru.in/)을 호출 */ public static void openBrowser(){ openBrowser(MARU_ADDR); } /** * <p>마루마루 인터넷창 열기 * <p>매개변수로 들어온 스트링 주소를 new URI(uri)를 통해 오픈 * <p>확장성을 위해 만들어 놓은 것 * @param uri String 형식 커스텀 uri주소 */ public static void openBrowser(String uri){ try{ Desktop.getDesktop().browse(new URI(uri)); } catch(Exception e){ ErrorHandling.saveErrLog(uri+" 브라우저 접속 실패", "", e); } } /** * <p>폴더 열기 기능 * <p>매개변수 void이면 DEFAULT_PATH(System.getProperty("user.home")) 자동 호출 */ public static void openDir(){ openDir(DEFAULT_PATH); } /** * <p>폴더 열기 기능 * <p>매개변수로 들어온 스트링 경로를 오픈 * <p>확장성을 위해 만들어 놓은 것 * @param path String 형식 커스텀 path */ public static void openDir(String path){ try{ Desktop.getDesktop().open(new File(path)); } catch(Exception e){ ErrorHandling.saveErrLog("폴더 오픈 실패", "", e); } } /** * <p>폴더 생성 메서드 * <p>매개변수가 없으면 DEFAULT_PATH를 자동으로 생성 */ public static void makeDir(){ makeDir(DEFAULT_PATH); } /** * <p>폴더 생성 메서드 * <p>매개변수로 경로를 받아 생성 * @param path String 타입의 경로 */ public static void makeDir(String path){ new File(path).mkdirs(); //존재하지 않는 경우만 새로 생성함 } /** * <p>도움말 출력 메서드 */ public static void help(){ System.out.println(MESSAGE); } /** * 개발 참고용 시스템 사양 출력 메서드 */ private static void printSystemProperties(){ System.getProperties().list(System.out); } /* 도움말 */ private final static String MESSAGE = "## 도움말 ##\n" + "1. 만화 다운로드\n" + " - 다운받을 만화 주소를 입력합니다. 인식가능한 주소는 크게 3가지가 있습니다.\n" + " -- wasabisyrup과 같은 아카이브 주소: 입력한 1편만 다운로드 합니다.\n" + " ex) http://wasabisyrup.com/archives/807EZuSyjwA\n" + " -- mangaup 등이 포함된 만화 업데이트 주소: 해당 페이지에 있는 모든 아카이브 주소를 찾아 다운로드합니다.\n" + " ex) http://marumaru.in/b/mangaup/204237\n" + " -- 전편 보러가기 주소: 해당 페이지에 있는 모든 아카이브 주소를 찾아 다운로드 합니다.\n" + " ex) http://marumaru.in/b/manga/198822\n" + "\n2. 선택적 다운로드\n" + " - 받고 싶은 만화만 골라서 다운로드 합니다.\n" + " - 주소 입력창에 '전편 보러가기'주소를 입력하면 다운로드 가능한 목록이 출력됩니다.\n" + " - 이후 다운받을 페이지들을 정규식 형태로 입력하여 선택적 다운로드를 진행합니다.\n" + " - 사용가능한 정규식은 다음과 같습니다.\n" + " -- 페이지 번호는 1번부터 시작합니다.\n" + " -- 각 만화의 번호 구분은 , 으로 합니다.\n" + " ex) 5, 3, 1, 7\n" + " -- 여러편을 이어서 받고 싶으면 각 회차 사이에 ~ 나 - 를 입력합니다.\n" + " ex) 4 ~ 10, 9-1\n" + " -- 각 번호 사이에 띄어쓰기는 해도 되고 안해도 됩니다.\n" + " ex) 1, 2, 3 이나 1, 2,3 이나 전부 같습니다.\n" + " -- 페이지 번호 입력 순서도 딱히 상관 없습니다(알아서 자동 오름차순 정렬이 됩니다)\n" + " ex) 17 ~ 15, 1-3 과 같이 입력하면 1,2,3,15,16,17로 최종 변환이 됩니다.\n" + " -- 중복된 페이지는 알아서 제거됩니다.\n" + " ex) 1, 3, 3, 2~4와 같은 경우 최종적으로 1,2,3,4로 변환이 됩니다.\n" + " -- 잘못된 페이지 번호는 걸러낸 뒤 다운로드 할 수 있는 최대한 다운로드를 시도합니다.\n" + " ex) 5번까지만 있는 만화에서, 4~6을 입력시 4~5까지만 다운로드.\n" + " 단, 6,8 처럼 아예 정상 페이지가 하나도 없을시엔 다운로드 안함\n" + "\n3. 다운로드 폴더 열기\n" + " - 기본적으로 GUI가 지원되야 합니다. 만일 폴더가 없더라도 자동으로 생성되고 열리게 됩니다.\n" + " -- Windows의 경우 C:\\Users\\사용자\\Marumaru\\ 폴더가 열립니다.\n" + " -- Mac과 Linux의 경우 /home/사용자/marumaru/ 폴더가 열립니다.\n" + "\n4. 마루마루 접속\n" + " - 기본적으로 GUI가 지원되야 합니다. 사용자 PC의 기본 브라우저를 이용하여 마루마루 페이지에 접속합니다.\n" + "\n8. 설정\n" + " 1) 업데이트 확인\n" + " - 서버에 등록된 최신 버전을 확인하고 보여줍니다.\n" + " - 현재 프로그램 보다 최신 버전이 있다면 다운로드 여부를 물어봅니다(Y/n)\n" + " - 다운로드를 선택하면 사용자의 OS를 바탕으로 Windows / 그 외(Mac, Linux) 버전을 자동 선택하여 다운로드합니다.\n" + " - 다운로드 경로는 만화 다운로드 경로(Marumaru 폴더)와 동일합니다\n" + " 2) 저장경로 변경\n" + " - 입력한 경로로 저장 경로를 변경합니다. 잘못된 경로 입력 시 기존 경로를 유지합니다.\n" + " - 단, 만화가 저장되는 경로만 변경되는 것이며, 로그 폴더, 업데이트 다운로드 폴더 등은 기존 기본 경로로 유지됩니다.\n" + " - 기본값: C\\Users\\사용자\\Marumaru 또는 /home/사용자/Marumaru\n" + " 3) 이미지 병합\n" + " - 다운받은 만화 폴더에 이미지들을 세로로 이어붙인 긴 이미지를 추가로 생성합니다.\n" + " 기존의 좌, 우로 넘겨보던 방식 대신, 하나의 긴 이미지를 확대하여 스크롤 해서 볼 수 있습니다.\n" + " - 기본값: false\n" + " 4) 디버깅 모드\n" + " - 만화 다운로드 시 파일의 용량과 메모리 사용량을 같이 출력합니다.\n" + " - 기본값: false\n" + " 5) 멀티스레딩 다운로드\n" + " - 다운로드 시 멀티스레딩의 정도를 설정합니다.\n" + " - 대체로 값이 커질수록 성능은 좋아지나 메모리 사용량이 증가합니다.\n" + " -- 0: 멀티스레딩을 하지 않습니다 (초저성능)\n" + " -- 1: 코어 개수의 절반 만큼을 할당합니다 (저성능)\n" + " -- 2: 코어 개수 만큼을 할당합니다 (기본값, 권장)\n" + " -- 3: 코어 개수의 2배 만큼을 할당합니다 (고성능)\n" + " -- 4: 사용할 수 있는 최대한 할당합니다 (초고성능)\n" + " - 기본값: 2\n" + " 6) 만화 압축하기\n" + " - 다운받은 만화를 zip으로 압축합니다.\n" + " - 기본값: false\n" + "\n0. 종료\n" + " - 모든 작업을 중단하고 프로그램을 종료합니다.\n" + "\n작성자: occidere\t작성일: 2018.07.30\n\n"; }