@Override public int onStartCommand(Intent intent, int flags, int startid) { SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplication()); int backupIntervalMinutes = Integer.parseInt( pref.getString( SettingsActivity.KEY_PREF_BACKUP_INTERVAL, getResources().getString(R.string.pref_default_backup_interval))); if (backupIntervalMinutes > 0) { int backupInterval = backupIntervalMinutes * 60 * 1000; Logger.i( this.getClass().getName(), "Backups running every " + backupIntervalMinutes + " minute/s"); sRunning = true; sTimer.scheduleAtFixedRate( new TimerTask() { @Override public void run() { if (!mFirstRun) { runBackup(); } mFirstRun = false; } }, 0, backupInterval); return START_STICKY; } else { Logger.i(this.getClass().getName(), "Backups are disabled"); sRunning = true; return START_STICKY; } }
@Override public void start() { File logFile = Logger.getLogFile(); // TRICKY: make sure the github_oauth2 token has been set int githubTokenIdentifier = AppContext.context() .getResources() .getIdentifier("github_oauth2", "string", AppContext.context().getPackageName()); String githubUrl = AppContext.context().getResources().getString(R.string.github_bug_report_repo); if (githubTokenIdentifier != 0) { GithubReporter reporter = new GithubReporter( AppContext.context(), githubUrl, AppContext.context().getResources().getString(githubTokenIdentifier)); reporter.reportBug(mNotes, logFile); // empty the log try { FileUtils.write(logFile, ""); } catch (IOException e) { e.printStackTrace(); } Logger.i(this.getClass().getName(), "Submitted bug report"); } else if (githubTokenIdentifier == 0) { Logger.w(this.getClass().getName(), "the github oauth2 token is missing"); } }
/** Stops the service */ private void stopService() { if (sTimer != null) { sTimer.cancel(); } sRunning = false; Logger.i(this.getClass().getName(), "stopping backup service"); }
/** * Merges chunks found in a target translation Project that do not exist in the source translation * to a sibling chunk so that no data is lost. * * @param library * @param targetTranslation target translation to merge * @return */ public static boolean migrateChunkChanges( final Library library, final TargetTranslation targetTranslation) { try { Logger.i( TargetTranslationMigrator.class.getName(), "Migrating chunks in target translation " + targetTranslation.getProjectId()); final SourceTranslation sourceTranslation = library.getDefaultSourceTranslation(targetTranslation.getProjectId(), "en"); if (sourceTranslation == null) { Logger.w( TargetTranslationMigrator.class.getName(), "Could not find a source translation for the target translation " + targetTranslation.getId()); return false; } if (targetTranslation.getPath().exists()) { boolean migrationSuccess = true; // perform the chunk migration on each chapter of the target translation for (ChapterTranslation chapterTranslation : targetTranslation.getChapterTranslations()) { Chapter chapter = library.getChapter(sourceTranslation, chapterTranslation.getId()); if (chapter != null) { boolean success = mergeInvalidChunksInChapter(library, sourceTranslation, targetTranslation, chapter); migrationSuccess = migrationSuccess && success; } } return migrationSuccess; } } catch (Exception e) { Logger.e( TargetTranslationMigrator.class.getName(), "Failed to merge the chunks in the target translation " + targetTranslation.getProjectId()); } return false; }
/** * Detects the format of the file and imports it * * @param file */ public void importTranslation(final File file) { try { if (file.exists() && file.isFile()) { String[] name = file.getName().split("\\."); if (name[name.length - 1].toLowerCase().equals(Project.PROJECT_EXTENSION)) { // import translationStudio project final ProgressDialog dialog = new ProgressDialog(this); dialog.setMessage(getResources().getString(R.string.import_project)); dialog.setCancelable(false); dialog.setCanceledOnTouchOutside(false); dialog.show(); final Handler handle = new Handler(Looper.getMainLooper()); new ThreadableUI(this) { @Override public void onStop() {} @Override public void run() { ProjectImport[] importRequests = Sharing.prepareArchiveImport(file); if (importRequests.length > 0) { boolean importWarnings = false; for (ProjectImport s : importRequests) { if (!s.isApproved()) { importWarnings = true; } } if (importWarnings) { // review the import status in a dialog FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment prev = getFragmentManager().findFragmentByTag("dialog"); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); app().closeToastMessage(); ProjectTranslationImportApprovalDialog newFragment = new ProjectTranslationImportApprovalDialog(); newFragment.setImportRequests(importRequests); newFragment.setOnClickListener( new ProjectTranslationImportApprovalDialog.OnClickListener() { @Override public void onOk(ProjectImport[] requests) { handle.post( new Runnable() { @Override public void run() { dialog.setMessage(getResources().getString(R.string.loading)); dialog.show(); } }); for (ProjectImport r : requests) { Sharing.importProject(r); } Sharing.cleanImport(requests); AppContext.context().showToastMessage(R.string.success); handle.post( new Runnable() { @Override public void run() { dialog.dismiss(); } }); } @Override public void onCancel(ProjectImport[] requests) {} }); newFragment.show(ft, "dialog"); } else { // TODO: we should update the status with the results of the import and let the // user see an overview of the import process. for (ProjectImport r : importRequests) { Sharing.importProject(r); } Sharing.cleanImport(importRequests); app().showToastMessage(R.string.success); } } else { Sharing.cleanImport(importRequests); app().showToastMessage(R.string.translation_import_failed); } } @Override public void onPostExecute() { dialog.dismiss(); } }.start(); } else if (name[name.length - 1].toLowerCase().equals("zip")) { // import DokuWiki files final ProgressDialog dialog = new ProgressDialog(SharingActivity.this); dialog.setMessage(getResources().getString(R.string.import_project)); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(false); dialog.show(); new ThreadableUI(this) { @Override public void onStop() {} @Override public void run() { if (Sharing.importDokuWikiArchive(file)) { app().showToastMessage(R.string.success); } else { app().showToastMessage(R.string.translation_import_failed); } } @Override public void onPostExecute() { dialog.dismiss(); } }.start(); } else if (name[name.length - 1].toLowerCase().equals("txt")) { // import legacy 1.x DokuWiki files final ProgressDialog dialog = new ProgressDialog(SharingActivity.this); dialog.setMessage(getResources().getString(R.string.import_project)); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(false); dialog.show(); new ThreadableUI(this) { @Override public void onStop() {} @Override public void run() { if (Sharing.importDokuWiki(file)) { app().showToastMessage(R.string.success); } else { app().showToastMessage(R.string.translation_import_failed); } } @Override public void onPostExecute() { dialog.dismiss(); } }.start(); } } else { app().showToastMessage(R.string.missing_file); } } catch (Exception e) { Logger.e(this.getClass().getName(), "Failed to read file", e); } }
/** * Merges invalid chunks found in the target translation with a valid sibling chunk in order to * preserve translation data. Merged chunks are marked as not finished to force translators to * review the changes. * * @param library * @param sourceTranslation * @param targetTranslation * @param chapter * @return */ private static boolean mergeInvalidChunksInChapter( final Library library, final SourceTranslation sourceTranslation, final TargetTranslation targetTranslation, final Chapter chapter) { boolean success = true; Logger.i( TargetTranslationMigrator.class.getName(), "Searching chapter " + chapter.getId() + " for invalid chunks "); // TRICKY: the translation format doesn't matter for migrating FrameTranslation[] frameTranslations = targetTranslation.getFrameTranslations(chapter.getId(), TranslationFormat.DEFAULT); String invalidChunks = ""; Frame lastValidFrame = null; for (FrameTranslation frameTranslation : frameTranslations) { Frame frame = library.getFrame(sourceTranslation, chapter.getId(), frameTranslation.getId()); if (frame != null) { lastValidFrame = frame; // merge invalid frames into the existing frame if (!invalidChunks.isEmpty()) { targetTranslation.applyFrameTranslation( frameTranslation, invalidChunks + frameTranslation.body); invalidChunks = ""; targetTranslation.reopenFrame(frame); } } else if (!frameTranslation.body.trim().isEmpty()) { if (lastValidFrame == null) { // collect invalid frame invalidChunks += frameTranslation.body + CHUNK_MERGE_MARKER; } else { // if last frame is not null, then append invalid chunk to it FrameTranslation lastFrameTranslation = targetTranslation.getFrameTranslation(lastValidFrame); targetTranslation.applyFrameTranslation( lastFrameTranslation, lastFrameTranslation.body + CHUNK_MERGE_MARKER + frameTranslation.body); targetTranslation.reopenFrame(lastValidFrame); } targetTranslation.applyFrameTranslation(frameTranslation, ""); // clear out old data } } // clean up remaining invalid chunks if (!invalidChunks.isEmpty()) { if (lastValidFrame == null) { // push remaining invalid chunks onto the first available frame String[] frameslugs = library.getFrameSlugs(sourceTranslation, chapter.getId()); if (frameslugs.length > 0) { lastValidFrame = library.getFrame(sourceTranslation, chapter.getId(), frameslugs[0]); } else { Logger.w( TargetTranslationMigrator.class.getName(), "No frames were found for chapter " + chapter.getId()); } } if (lastValidFrame != null) { FrameTranslation frameTranslation = targetTranslation.getFrameTranslation(lastValidFrame); targetTranslation.applyFrameTranslation( frameTranslation, invalidChunks + CHUNK_MERGE_MARKER + frameTranslation.body); targetTranslation.reopenFrame(lastValidFrame); } } return success; }
@Override public void onCreate() { Logger.i(this.getClass().getName(), "starting backup service"); }
/** Performs the backup if nessesary */ private void runBackup() { boolean backupPerformed = false; Translator translator = AppContext.getTranslator(); TargetTranslation[] targetTranslations = translator.getTargetTranslations(); for (TargetTranslation t : targetTranslations) { // commit pending changes try { t.commit(); } catch (Exception e) { Logger.e(this.getClass().getName(), "Failed to commit changes before backing up", e); continue; } // run backup if there are translations if (t.numTranslated() > 0) { // retreive commit hash String tag; try { tag = t.getCommitHash(); } catch (Exception e) { Logger.w(this.getClass().getName(), "Failed to read commit hash", e); continue; } // check if backup is required if (tag != null) { File primaryBackupDir = new File(AppContext.getPublicDirectory(), "backups/" + t.getId() + "/"); File primaryBackupFile = new File(primaryBackupDir, tag + "." + Translator.ARCHIVE_EXTENSION); File downloadBackupDir = new File(AppContext.getPublicDownloadsDirectory(), "backups/" + t.getId() + "/"); File downloadBackupFile = new File(downloadBackupDir, tag + "." + Translator.ARCHIVE_EXTENSION); // e.g. ../../backups/uw-obs-de/[commit hash].tstudio if (!downloadBackupFile.exists()) { // peform backup File archive = new File( AppContext.getPublicDownloadsDirectory(), t.getId() + ".temp." + Translator.ARCHIVE_EXTENSION); try { translator.exportArchive(t, archive); } catch (Exception e) { Logger.e( this.getClass().getName(), "Failed to export the target translation " + t.getId(), e); continue; } if (archive.exists() && archive.isFile()) { // move into backup FileUtils.deleteQuietly(downloadBackupDir); FileUtils.deleteQuietly(primaryBackupDir); downloadBackupDir.mkdirs(); primaryBackupDir.mkdirs(); try { // backup to downloads directory FileUtils.copyFile(archive, downloadBackupFile); // backup to a slightly less public area (used for auto restore) FileUtils.copyFile(archive, primaryBackupFile); backupPerformed = true; } catch (IOException e) { Logger.e( this.getClass().getName(), "Failed to copy the backup archive for target translation: " + t.getId(), e); } archive.delete(); } else { Logger.w( this.getClass().getName(), "Failed to export the target translation: " + t.getId()); } } } else { Logger.w(this.getClass().getName(), "Could not find the commit hash"); } } } if (backupPerformed) { onBackupComplete(); } }