I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • 0.6.1.30-20
  • 0.6.1.30-20-cvs-suck-import
  • 2.8.1-2-rc
  • i2p-0.6.1.31
  • i2p-0.6.1.32
  • i2p-0.6.1.33
  • i2p-0.6.2
  • i2p-0.6.3
  • i2p-0.6.4
  • i2p-0.6.5
  • i2p-0.7
  • i2p-0.7.1
  • i2p-0.7.10
  • i2p-0.7.11
  • i2p-0.7.12
  • i2p-0.7.13
  • i2p-0.7.14
  • i2p-0.7.2
  • i2p-0.7.3
  • i2p-0.7.4
  • i2p-0.7.5
  • i2p-0.7.6
  • i2p-0.7.7
  • i2p-0.7.8
  • i2p-0.7.9
  • i2p-0.8
  • i2p-0.8.1
  • i2p-0.8.10
  • i2p-0.8.11
  • i2p-0.8.12
  • i2p-0.8.13
  • i2p-0.8.2
  • i2p-0.8.3
  • i2p-0.8.4
  • i2p-0.8.5
  • i2p-0.8.6
  • i2p-0.8.7
  • i2p-0.8.8
  • i2p-0.8.9
  • i2p-0.9
  • i2p-0.9.1
  • i2p-0.9.10
  • i2p-0.9.11
  • i2p-0.9.12
  • i2p-0.9.13
  • i2p-0.9.14
  • i2p-0.9.14.1
  • i2p-0.9.15
  • i2p-0.9.16
  • i2p-0.9.17
  • i2p-0.9.18
  • i2p-0.9.19
  • i2p-0.9.2
  • i2p-0.9.20
  • i2p-0.9.21
  • i2p-0.9.22
  • i2p-0.9.23
  • i2p-0.9.24
  • i2p-0.9.25
  • i2p-0.9.26
  • i2p-0.9.27
  • i2p-0.9.28
  • i2p-0.9.29
  • i2p-0.9.29-win1
  • i2p-0.9.3
  • i2p-0.9.30
  • i2p-0.9.31
  • i2p-0.9.32
  • i2p-0.9.33
  • i2p-0.9.34
  • i2p-0.9.35
  • i2p-0.9.36
  • i2p-0.9.37
  • i2p-0.9.38
  • i2p-0.9.39
  • i2p-0.9.4
  • i2p-0.9.40
  • i2p-0.9.41
  • i2p-0.9.42
  • i2p-0.9.43
  • i2p-0.9.44
  • i2p-0.9.45
  • i2p-0.9.46
  • i2p-0.9.47
  • i2p-0.9.48
  • i2p-0.9.49
  • i2p-0.9.5
  • i2p-0.9.5-win1
  • i2p-0.9.50
  • i2p-0.9.6
  • i2p-0.9.7
  • i2p-0.9.7.1
  • i2p-0.9.8
  • i2p-0.9.8.1
  • i2p-0.9.9
  • i2p-1.5.0
  • i2p-1.6.0
  • i2p-1.6.1
  • i2p-1.7.0
  • i2p-1.8.0
101 results

Target

Select target project
  • i2p-hackers/i2p.i2p
  • idk/i2p.i2p
  • welshlyluvah1967/i2p.i2p
  • echelon/i2p.i2p
  • elde/i2p.i2p
  • thebland/i2p.i2p
  • zzz/i2p.i2p
  • loveisgrief/i2p.i2p
  • sadie/i2p.i2p
  • longyap/i2p.i2p
  • pVT0/i2p.i2p
  • Lfrr/i2p.i2p
  • aargh/i2p.i2p
  • zlatinb/i2p.i2p
  • mesh/i2p.i2p
  • anonymousmaybe/i2p.i2p
  • DuncanIdaho/i2p.i2p
  • Kalhintz/i2p.i2p
  • 31337/i2p.i2p
  • equincey/i2p.i2p
  • lbt/i2p.i2p
  • y2kboy23/i2p.i2p
  • obscuratus/i2p.i2p
  • apsoyka/i2p.i2p
  • ashtod/i2p.i2p
  • agentoocat/i2p.i2p
  • kelare/i2p.i2p
  • kytv/i2p.i2p
  • marek/i2p.i2p
29 results
Select Git revision
  • i2p-android-1.8.1
  • i2p.i2p-docker_updates
  • master
  • programdata-path-fix
  • 0.6.1.30-20
  • 0.6.1.30-20-cvs-suck-import
  • i2p-0.6.1.31
  • i2p-0.6.1.32
  • i2p-0.6.1.33
  • i2p-0.6.2
  • i2p-0.6.3
  • i2p-0.6.4
  • i2p-0.6.5
  • i2p-0.7
  • i2p-0.7.1
  • i2p-0.7.10
  • i2p-0.7.11
  • i2p-0.7.12
  • i2p-0.7.13
  • i2p-0.7.14
  • i2p-0.7.2
  • i2p-0.7.3
  • i2p-0.7.4
  • i2p-0.7.5
  • i2p-0.7.6
  • i2p-0.7.7
  • i2p-0.7.8
  • i2p-0.7.9
  • i2p-0.8
  • i2p-0.8.1
  • i2p-0.8.10
  • i2p-0.8.11
  • i2p-0.8.12
  • i2p-0.8.13
  • i2p-0.8.2
  • i2p-0.8.3
  • i2p-0.8.4
  • i2p-0.8.5
  • i2p-0.8.6
  • i2p-0.8.7
  • i2p-0.8.8
  • i2p-0.8.9
  • i2p-0.9
  • i2p-0.9.1
  • i2p-0.9.10
  • i2p-0.9.11
  • i2p-0.9.12
  • i2p-0.9.13
  • i2p-0.9.14
  • i2p-0.9.14.1
  • i2p-0.9.15
  • i2p-0.9.16
  • i2p-0.9.17
  • i2p-0.9.18
  • i2p-0.9.19
  • i2p-0.9.2
  • i2p-0.9.20
  • i2p-0.9.21
  • i2p-0.9.22
  • i2p-0.9.23
  • i2p-0.9.24
  • i2p-0.9.25
  • i2p-0.9.26
  • i2p-0.9.27
  • i2p-0.9.28
  • i2p-0.9.29
  • i2p-0.9.29-win1
  • i2p-0.9.3
  • i2p-0.9.30
  • i2p-0.9.31
  • i2p-0.9.32
  • i2p-0.9.33
  • i2p-0.9.34
  • i2p-0.9.35
  • i2p-0.9.36
  • i2p-0.9.37
  • i2p-0.9.38
  • i2p-0.9.39
  • i2p-0.9.4
  • i2p-0.9.40
  • i2p-0.9.41
  • i2p-0.9.42
  • i2p-0.9.43
  • i2p-0.9.44
  • i2p-0.9.45
  • i2p-0.9.46
  • i2p-0.9.47
  • i2p-0.9.48
  • i2p-0.9.49
  • i2p-0.9.5
  • i2p-0.9.5-win1
  • i2p-0.9.50
  • i2p-0.9.6
  • i2p-0.9.7
  • i2p-0.9.7.1
  • i2p-0.9.8
  • i2p-0.9.8.1
  • i2p-0.9.9
  • i2p-1.5.0
  • i2p-1.6.0
  • i2p-1.6.1
  • i2p-1.7.0
  • i2p-1.8.0
  • i2p-1.9.0
104 results
Show changes
Showing
with 923 additions and 145 deletions
......@@ -51,11 +51,15 @@ class ExternalTrayManager extends TrayManager {
}
});
popup.add(startItem);
initializeNotificationItems();
popup.add(_notificationItem2);
popup.add(_notificationItem1);
return popup;
}
public JPopupMenu getSwingMainMenu() {
JPopupMenu popup = new JPopupMenu();
/*
JMenuItem startItem = new JMenuItem(_t("Start I2P"));
startItem.addActionListener(new ActionListener() {
@Override
......@@ -79,6 +83,10 @@ class ExternalTrayManager extends TrayManager {
}
});
popup.add(startItem);
*/
initializeJNotificationItems();
popup.add(_jnotificationItem2);
popup.add(_jnotificationItem1);
return popup;
}
......@@ -86,5 +94,14 @@ class ExternalTrayManager extends TrayManager {
* Update the menu
* @since 0.9.26
*/
protected void updateMenu() {}
protected void updateMenu() {
if (_notificationItem1 != null)
_notificationItem1.setEnabled(_showNotifications);
if (_notificationItem2 != null)
_notificationItem2.setEnabled(!_showNotifications);
if (_jnotificationItem1 != null)
_jnotificationItem1.setVisible(_showNotifications);
if (_jnotificationItem2 != null)
_jnotificationItem2.setVisible(!_showNotifications);
}
}
......@@ -32,11 +32,9 @@ class InternalTrayManager extends TrayManager {
private final Log log;
private final Main _main;
private MenuItem _statusItem, _browserItem, _configItem, _restartItem, _stopItem,
_restartHardItem, _stopHardItem, _cancelItem,
_notificationItem1, _notificationItem2;
_restartHardItem, _stopHardItem, _cancelItem;
private JMenuItem _jstatusItem, _jbrowserItem, _jconfigItem, _jrestartItem, _jstopItem,
_jrestartHardItem, _jstopHardItem, _jcancelItem,
_jnotificationItem1, _jnotificationItem2;
_jrestartHardItem, _jstopHardItem, _jcancelItem;
private static final boolean CONSOLE_ENABLED = Desktop.isDesktopSupported() &&
Desktop.getDesktop().isSupported(Action.BROWSE);
......@@ -86,33 +84,6 @@ class InternalTrayManager extends TrayManager {
}
PopupMenu desktopguiConfigurationLauncher = new PopupMenu(_t("Configure I2P System Tray"));
final MenuItem notificationItem2 = new MenuItem(_t("Enable notifications"));
notificationItem2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(true);
return null;
}
}.execute();
}
});
final MenuItem notificationItem1 = new MenuItem(_t("Disable notifications"));
notificationItem1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(false);
return null;
}
}.execute();
}
});
MenuItem configSubmenu = new MenuItem(_t("Disable system tray"));
configSubmenu.addActionListener(new ActionListener() {
......@@ -214,8 +185,9 @@ class InternalTrayManager extends TrayManager {
popup.add(browserLauncher);
popup.addSeparator();
}
desktopguiConfigurationLauncher.add(notificationItem2);
desktopguiConfigurationLauncher.add(notificationItem1);
initializeNotificationItems();
desktopguiConfigurationLauncher.add(_notificationItem2);
desktopguiConfigurationLauncher.add(_notificationItem1);
desktopguiConfigurationLauncher.add(configSubmenu);
popup.add(desktopguiConfigurationLauncher);
popup.addSeparator();
......@@ -230,8 +202,6 @@ class InternalTrayManager extends TrayManager {
_statusItem = statusItem;
_browserItem = browserLauncher;
_configItem = desktopguiConfigurationLauncher;
_notificationItem1 = notificationItem1;
_notificationItem2 = notificationItem2;
_restartItem = restartItem;
_stopItem = stopItem;
_restartHardItem = restartItem2;
......@@ -270,33 +240,6 @@ class InternalTrayManager extends TrayManager {
}
JMenu desktopguiConfigurationLauncher = new JMenu(_t("Configure I2P System Tray"));
final JMenuItem notificationItem2 = new JMenuItem(_t("Enable notifications"));
notificationItem2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(true);
return null;
}
}.execute();
}
});
final JMenuItem notificationItem1 = new JMenuItem(_t("Disable notifications"));
notificationItem1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(false);
return null;
}
}.execute();
}
});
JMenuItem configSubmenu = new JMenuItem(_t("Disable system tray"));
configSubmenu.addActionListener(new ActionListener() {
......@@ -398,8 +341,9 @@ class InternalTrayManager extends TrayManager {
popup.add(browserLauncher);
popup.addSeparator();
}
desktopguiConfigurationLauncher.add(notificationItem2);
desktopguiConfigurationLauncher.add(notificationItem1);
initializeJNotificationItems();
desktopguiConfigurationLauncher.add(_jnotificationItem2);
desktopguiConfigurationLauncher.add(_jnotificationItem1);
desktopguiConfigurationLauncher.add(configSubmenu);
popup.add(desktopguiConfigurationLauncher);
popup.addSeparator();
......@@ -414,8 +358,6 @@ class InternalTrayManager extends TrayManager {
_jstatusItem = statusItem;
_jbrowserItem = browserLauncher;
_jconfigItem = desktopguiConfigurationLauncher;
_jnotificationItem1 = notificationItem1;
_jnotificationItem2 = notificationItem2;
_jrestartItem = restartItem;
_jstopItem = stopItem;
_jrestartHardItem = restartItem2;
......@@ -511,7 +453,8 @@ class InternalTrayManager extends TrayManager {
/**
* @since 0.9.53
*/
private void configureNotifications(boolean enable) {
@Override
protected void configureNotifications(boolean enable) {
_showNotifications = enable;
String value = Boolean.toString(enable);
if (!_context.router().saveConfig(PROP_NOTIFICATIONS, value))
......
......@@ -17,6 +17,9 @@ import net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.app.MenuCallback;
import net.i2p.app.MenuHandle;
import net.i2p.app.MenuService;
import net.i2p.app.NotificationService;
import net.i2p.desktopgui.router.RouterManager;
import net.i2p.router.RouterContext;
......@@ -29,7 +32,7 @@ import net.i2p.util.I2PProperties.I2PPropertyCallback;
/**
* The main class of the application.
*/
public class Main implements RouterApp, NotificationService {
public class Main implements RouterApp, NotificationService, MenuService {
// non-null
private final I2PAppContext _appContext;
......@@ -245,6 +248,89 @@ public class Main implements RouterApp, NotificationService {
return false;
}
/////// MenuService methods
/**
* Menu will start out shown and enabled, in the root menu
*
* @param message for the menu, translated
* @param callback fired on click
* @return null on error
* @since 0.9.59
*/
public MenuHandle addMenu(String message, MenuCallback callback) {
return addMenu(message, callback, null);
}
/**
* Menu will start out enabled, as a submenu
*
* @param message for the menu, translated
* @param callback fired on click
* @param parent the parent menu this will be a submenu of, or null for top level
* @return null on error
* @since 0.9.59
*/
public MenuHandle addMenu(String message, MenuCallback callback, MenuHandle parent) {
if (_trayManager == null)
return null;
return _trayManager.addMenu(message, callback, parent);
}
/**
* @since 0.9.59
*/
public void removeMenu(MenuHandle item) {
if (_trayManager == null)
return;
_trayManager.removeMenu(item);
}
/**
* @since 0.9.59
*/
public void showMenu(MenuHandle item) {
if (_trayManager == null)
return;
_trayManager.showMenu(item);
}
/**
* @since 0.9.59
*/
public void hideMenu(MenuHandle item) {
if (_trayManager == null)
return;
_trayManager.hideMenu(item);
}
/**
* @since 0.9.59
*/
public void enableMenu(MenuHandle item) {
if (_trayManager == null)
return;
_trayManager.enableMenu(item);
}
/**
* @since 0.9.59
*/
public void disableMenu(MenuHandle item) {
if (_trayManager == null)
return;
_trayManager.disableMenu(item);
}
/**
* @since 0.9.59
*/
public void updateMenu(String message, MenuHandle item) {
if (_trayManager == null)
return;
_trayManager.updateMenu(message, item);
}
/////// ClientApp methods
/** @since 0.9.26 */
......
......@@ -4,6 +4,7 @@ import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
......@@ -16,8 +17,12 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingWorker;
import javax.swing.event.MenuKeyEvent;
......@@ -26,6 +31,8 @@ import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import net.i2p.I2PAppContext;
import net.i2p.app.MenuCallback;
import net.i2p.app.MenuHandle;
import net.i2p.apps.systray.UrlLauncher;
import net.i2p.desktopgui.i18n.DesktopguiTranslator;
import net.i2p.util.Log;
......@@ -43,6 +50,11 @@ abstract class TrayManager {
///Our tray icon, or null if unsupported
protected TrayIcon trayIcon;
protected volatile boolean _showNotifications;
protected MenuItem _notificationItem1, _notificationItem2;
protected JMenuItem _jnotificationItem1, _jnotificationItem2;
private final AtomicInteger _id = new AtomicInteger();
private final List<MenuInternal> _menus;
private JPopupMenu _jPopupMenu;
private static final String PNG_DIR = "/desktopgui/resources/images/";
private static final String MAC_ICON = "itoopie_black_24.png";
......@@ -57,6 +69,7 @@ abstract class TrayManager {
protected TrayManager(I2PAppContext ctx, boolean useSwing) {
_appContext = ctx;
_useSwing = useSwing;
_menus = new ArrayList<MenuInternal>();
}
/**
......@@ -105,6 +118,7 @@ abstract class TrayManager {
frame.setMinimumSize(new Dimension(0, 0));
frame.setSize(0, 0);
final JPopupMenu menu = getSwingMainMenu();
_jPopupMenu = menu;
menu.setFocusable(true);
frame.add(menu);
TrayIcon ti = new TrayIcon(getTrayImage(), tooltip, null);
......@@ -288,6 +302,248 @@ abstract class TrayManager {
return 0;
}
/**
* Does not save. See InternalTrayManager.
*
* @since 0.9.58 moved up from InternalTrayManager
*/
protected void configureNotifications(boolean enable) {
_showNotifications = enable;
}
/**
* Initializes _notificationItem 1 and 2
*
* @since 0.9.58 pulled out of InternalTrayManager
*/
protected void initializeNotificationItems() {
final MenuItem notificationItem2 = new MenuItem(_t("Enable notifications"));
notificationItem2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(true);
return null;
}
}.execute();
}
});
_notificationItem2 = notificationItem2;
final MenuItem notificationItem1 = new MenuItem(_t("Disable notifications"));
notificationItem1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(false);
return null;
}
}.execute();
}
});
_notificationItem1 = notificationItem1;
}
/**
* Initializes _jnotificationItem 1 and 2
*
* @since 0.9.58 pulled out of InternalTrayManager
*/
protected void initializeJNotificationItems() {
final JMenuItem notificationItem2 = new JMenuItem(_t("Enable notifications"));
notificationItem2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(true);
return null;
}
}.execute();
}
});
_jnotificationItem2 = notificationItem2;
final JMenuItem notificationItem1 = new JMenuItem(_t("Disable notifications"));
notificationItem1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
configureNotifications(false);
return null;
}
}.execute();
}
});
_jnotificationItem1 = notificationItem1;
}
/////// MenuService delegation methods
/**
* @since 0.9.59
*/
public MenuHandle addMenu(String message, final MenuCallback callback, MenuHandle p) {
MenuInternal parent = p != null ? (MenuInternal) p : null;
final int id = _id.incrementAndGet();
final MenuInternal rv;
if (_useSwing) {
final JMenuItem m = new JMenuItem(message);
rv = new MenuInternal(null, m, callback, id);
m.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
rv.cb.clicked(rv);
return null;
}
}.execute();
}
});
_jPopupMenu.add(m);
} else {
final MenuItem m = new MenuItem(message);
rv = new MenuInternal(m, null, callback, id);
m.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
rv.cb.clicked(rv);
return null;
}
}.execute();
}
});
trayIcon.getPopupMenu().add(m);
}
synchronized(_menus) {
_menus.add(rv);
}
updateMenu();
return rv;
}
/**
* @since 0.9.59
*/
public void removeMenu(MenuHandle item) {
MenuInternal mi = (MenuInternal) item;
if (_useSwing) {
_jPopupMenu.remove(mi.jm);
} else {
trayIcon.getPopupMenu().remove(mi.m);
}
updateMenu();
}
/**
* @since 0.9.59
*/
public void showMenu(MenuHandle item) {
MenuInternal mi = (MenuInternal) item;
mi.setVisible(true);
updateMenu();
}
/**
* @since 0.9.59
*/
public void hideMenu(MenuHandle item) {
MenuInternal mi = (MenuInternal) item;
mi.setVisible(false);
updateMenu();
}
/**
* @since 0.9.59
*/
public void enableMenu(MenuHandle item) {
MenuInternal mi = (MenuInternal) item;
mi.setEnabled(true);
updateMenu();
}
/**
* @since 0.9.59
*/
public void disableMenu(MenuHandle item) {
MenuInternal mi = (MenuInternal) item;
mi.setEnabled(false);
updateMenu();
}
/**
* @since 0.9.59
*/
public void updateMenu(String message, MenuHandle item) {
MenuInternal mi = (MenuInternal) item;
mi.setText(message);
updateMenu();
}
/////// MenuService internals
/**
* @since 0.9.59
*/
private MenuInternal getMenu(int id) {
synchronized(_menus) {
for (MenuInternal mi : _menus) {
if (mi.getID() == id)
return mi;
}
}
return null;
}
/**
* @since 0.9.59
*/
private static class MenuInternal implements MenuHandle {
private final MenuItem m;
private final JMenuItem jm;
private final MenuCallback cb;
private final int id;
public MenuInternal(MenuItem mm, JMenuItem jmm, MenuCallback cbb, int idd) {
m = mm; jm = jmm; cb = cbb; id = idd;
}
public int getID() { return id; }
private void setEnabled(boolean yes) {
if (m != null)
m.setEnabled(yes);
else
jm.setEnabled(yes);
}
private void setVisible(boolean yes) {
if (m != null)
m.setEnabled(yes);
else
jm.setVisible(yes);
}
private void setText(String text) {
if (m != null)
m.setLabel(text);
else
jm.setText(text);
}
}
protected String _t(String s) {
return DesktopguiTranslator._t(_appContext, s);
}
......
plugins {
id 'java-library'
id 'war'
}
......@@ -11,13 +12,15 @@ sourceSets {
}
dependencies {
providedCompile project(':router')
providedCompile project(':apps:jetty')
providedCompile files('../../installer/lib/wrapper/all/wrapper.jar')
api project(':router')
api project(':apps:jetty')
api files('../../installer/lib/wrapper/all/wrapper.jar')
api fileTree("../jetty/apache-tomcat-${tomcatVersion}")
api fileTree("../jetty/jetty-distribution-${jettyVersion}")
}
war {
archiveName 'jsonrpc.war'
archiveBaseName.set('jsonrpc')
webXml = file('web.xml')
}
......
......@@ -92,7 +92,29 @@
</javac>
</target>
<target name="jar" depends="compile">
<target name="listChangedFiles" if="git.available" >
<exec executable="git" outputproperty="workspace.changes" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="status" />
<arg value="-s" />
<arg value="--porcelain" />
<arg value="-uno" />
<arg value="." />
<arg value="../resources" />
</exec>
<!-- trim flags -->
<exec executable="sed" inputstring="${workspace.changes}" outputproperty="workspace.changes.sed" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="-e" />
<arg value="s/^[MTADRCU ]*//" />
</exec>
<!-- \n in an attribute value generates an invalid manifest -->
<exec executable="tr" inputstring="${workspace.changes.sed}" outputproperty="workspace.changes.tr" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="-s" />
<arg value="[:space:]" />
<arg value="," />
</exec>
</target>
<target name="jar" depends="compile, listChangedFiles">
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<jar destfile="build/i2pcontrol.jar" basedir="./build/obj" includes="**/*.class" >
......@@ -108,7 +130,7 @@
</jar>
</target>
<target name="socketJar" depends="compileSocketJar">
<target name="socketJar" depends="compileSocketJar, listChangedFiles">
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<jar destfile="build/i2pcontrol.jar" basedir="./build/obj" includes="**/*.class" >
......@@ -124,7 +146,7 @@
</jar>
</target>
<target name="war" depends="compile" >
<target name="war" depends="compile, listChangedFiles" >
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<war destfile="build/jsonrpc.war" webxml="web.xml" >
......
......@@ -162,6 +162,7 @@ public class Dispatcher implements RequestHandler, NotificationHandler {
/**
* @deprecated
*/
@Deprecated
public JSONRPC2Response dispatch(final JSONRPC2Request request, final MessageContext requestCtx) {
return process(request, requestCtx);
......@@ -209,6 +210,7 @@ public class Dispatcher implements RequestHandler, NotificationHandler {
/**
* @deprecated
*/
@Deprecated
public void dispatch(final JSONRPC2Notification notification, final MessageContext notificationCtx) {
process(notification, notificationCtx);
......
......@@ -170,7 +170,7 @@ public class RouterInfoHandler implements RequestHandler {
&& (!_context.router().gracefulShutdownInProgress())
&& !_context.clientManager().isAlive())
return (NETWORK_STATUS.ERROR_I2CP);
long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
long skew = _context.commSystem().getFramedAveragePeerClockSkew(10);
// Display the actual skew, not the offset
if (Math.abs(skew) > 60 * 1000)
return NETWORK_STATUS.ERROR_CLOCK_SKEW;
......@@ -200,7 +200,7 @@ public class RouterInfoHandler implements RequestHandler {
case CommSystemFacade.STATUS_IPV4_DISABLED_IPV6_FIREWALLED:
if (_context.router().getRouterInfo().getTargetAddress("NTCP2") != null)
return NETWORK_STATUS.WARN_FIREWALLED_WITH_INBOUND_TCP;
if (((FloodfillNetworkDatabaseFacade) _context.netDb()).floodfillEnabled())
if (_context.netDb().floodfillEnabled())
return NETWORK_STATUS.WARN_FIREWALLED_AND_FLOODFILL;
if (_context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
return NETWORK_STATUS.WARN_FIREWALLED_AND_FAST;
......
plugins {
id 'java-library'
id 'war'
}
......@@ -12,11 +13,14 @@ sourceSets {
}
dependencies {
compile project(':core')
providedCompile project(':apps:systray')
compile 'gnu.getopt:java-getopt:1.0.13'
providedCompile project(':apps:ministreaming')
providedCompile project(':apps:jetty')
api project(':core')
api project(':apps:systray')
api 'gnu.getopt:java-getopt:1.0.13'
api project(':apps:ministreaming')
api project(':apps:jetty')
// this is not needed except for standalone,
// but we build the standalone classes even for non-standalone
api project(':apps:desktopgui')
}
task i2psnarkJar(type: Jar) {
......
......@@ -10,3 +10,5 @@
#routerconsole.browser=firefox
# disable system tray
#desktopgui.enabled=false
# disable system tray notification popups
#desktopgui.showNotifications=false
......@@ -68,13 +68,20 @@
</target>
<target name="listChangedFiles" depends="jarUpToDate" if="shouldListChanges" >
<exec executable="mtn" outputproperty="workspace.changes" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="list" />
<arg value="changed" />
<exec executable="git" outputproperty="workspace.changes" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="status" />
<arg value="-s" />
<arg value="--porcelain" />
<arg value="-uno" />
<arg value=".." />
</exec>
<!-- trim flags -->
<exec executable="sed" inputstring="${workspace.changes}" outputproperty="workspace.changes.sed" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="-e" />
<arg value="s/^[MTADRCU ]*//" />
</exec>
<!-- \n in an attribute value generates an invalid manifest -->
<exec executable="tr" inputstring="${workspace.changes}" outputproperty="workspace.changes.tr" errorproperty="mtn.error2" failifexecutionfails="false" >
<exec executable="tr" inputstring="${workspace.changes.sed}" outputproperty="workspace.changes.tr" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="-s" />
<arg value="[:space:]" />
<arg value="," />
......@@ -111,7 +118,7 @@
<not>
<isset property="war.uptodate" />
</not>
<isset property="mtn.available" />
<isset property="git.available" />
</and>
</condition>
</target>
......@@ -201,11 +208,11 @@
<target name="standalone" depends="standalone_prep">
<!-- doesn't support file permissions
<zip destfile="i2psnark-standalone.zip">
<zipfileset dir="./i2psnark/" />
<zip destfile="build/i2psnark-standalone.zip">
<zipfileset dir="./build/i2psnark/" fullpath="i2psnark" />
</zip>
-->
<exec executable="zip" failifexecutionfails="true" failonerror="true" >
<exec executable="zip" dir="build" failifexecutionfails="true" failonerror="true" >
<arg value="-r" />
<arg value="i2psnark-standalone.zip" />
<arg value="i2psnark" />
......@@ -252,10 +259,17 @@
<attribute name="Build-Date" value="${build.timestamp}" />
<attribute name="Base-Revision" value="${workspace.version}" />
<attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
<attribute name="X-Compile-Source-JDK" value="${javac.version}" />
<attribute name="X-Compile-Target-JDK" value="${javac.version}" />
<!--
Suppress JNI warning in JRE 24+, and eventual restriction
See https://openjdk.org/jeps/472
-->
<attribute name="Enable-Native-Access" value="ALL-UNNAMED" />
<!-- this is so Jetty will report its version correctly -->
<section name="org/eclipse/jetty/server/" >
<attribute name="Implementation-Vendor" value="Eclipse.org - Jetty" />
<attribute name="Implementation-Version" value="8.1.17.v20150415" />
<attribute name="Implementation-Version" value="${jetty.ver}" />
</section>
</manifest>
</jar>
......@@ -298,6 +312,12 @@
value="url(/i2psnark/.resources/themes/ubergine/images/" >
<include name="**/*.css" />
</replace>
<replace dir="build/standalone-resources/.resources/themes"
summary="true"
token="url(/themes/console/images/buttons/"
value="url(/i2psnark/.resources/icons/" >
<include name="**/*.css" />
</replace>
<!-- Rather than pulling in all the console theme images, let's just specify the ones we need -->
<copy file="../../routerconsole/jsp/themes/console/images/transparent.gif"
......@@ -308,6 +328,8 @@
todir="build/standalone-resources/.resources/themes/light/images" />
<copy file="../../routerconsole/jsp/themes/console/images/info/errortriangle.png"
todir="build/standalone-resources/.resources/themes/ubergine/images" />
<copy file="../../routerconsole/jsp/themes/console/images/buttons/search.png"
todir="build/standalone-resources/.resources/icons" />
<mkdir dir="build/standalone-resources/.resources/js" />
<copy file="../../routerconsole/jsp/js/ajax.js" todir="build/standalone-resources/.resources/js" />
......@@ -318,34 +340,32 @@
</target>
<target name="standalone_prep" depends="standalone_jar, standalone_war">
<delete dir="./i2psnark" />
<mkdir dir="./i2psnark" />
<copy file="../launch-i2psnark" todir="./i2psnark/" />
<chmod type="file" file="./i2psnark/launch-i2psnark" perm="+x" />
<copy file="../launch-i2psnark.bat" todir="./i2psnark/" />
<mkdir dir="./i2psnark/contexts" />
<copy file="../standalone-context.xml" tofile="./i2psnark/contexts/context.xml" />
<mkdir dir="./i2psnark/docroot" />
<copy file="../standalone-index.html" tofile="./i2psnark/docroot/index.html" />
<mkdir dir="./i2psnark/webapps" />
<copy file="../i2psnark.war" tofile="./i2psnark/webapps/i2psnark.war" />
<copy file="../jetty-i2psnark.xml" tofile="./i2psnark/jetty-i2psnark.xml" />
<copy file="../i2psnark-appctx.config" tofile="./i2psnark/i2psnark-appctx.config" />
<copy file="./build/i2psnark-standalone.jar" tofile="./i2psnark/i2psnark.jar" />
<copy file="../readme-standalone.txt" tofile="./i2psnark/readme.txt" />
<delete dir="./build/i2psnark" />
<mkdir dir="./build/i2psnark" />
<copy file="../launch-i2psnark" todir="./build/i2psnark/" />
<chmod type="file" file="./build/i2psnark/launch-i2psnark" perm="+x" />
<copy file="../launch-i2psnark.bat" todir="./build/i2psnark/" />
<mkdir dir="./build/i2psnark/contexts" />
<copy file="../standalone-context.xml" tofile="./build/i2psnark/contexts/context.xml" />
<mkdir dir="./build/i2psnark/docroot" />
<copy file="../standalone-index.html" tofile="./build/i2psnark/docroot/index.html" />
<mkdir dir="./build/i2psnark/webapps" />
<copy file="../i2psnark.war" tofile="./build/i2psnark/webapps/i2psnark.war" />
<copy file="../jetty-i2psnark.xml" tofile="./build/i2psnark/jetty-i2psnark.xml" />
<copy file="../i2psnark-appctx.config" tofile="./build/i2psnark/i2psnark-appctx.config" />
<copy file="./build/i2psnark-standalone.jar" tofile="./build/i2psnark/i2psnark.jar" />
<copy file="../readme-standalone.txt" tofile="./build/i2psnark/readme.txt" />
<!-- temp so announces work -->
<copy file="../../../installer/resources/hosts.txt" tofile="./i2psnark/hosts.txt" />
<copy todir="./i2psnark/licenses" >
<copy file="../../../installer/resources/hosts.txt" tofile="./build/i2psnark/hosts.txt" />
<copy todir="./build/i2psnark/licenses" >
<fileset dir="../../../licenses" includes="LICENSE-GPLv2.txt, ABOUT-Jetty.html" />
</copy>
<mkdir dir="./i2psnark/logs" />
<mkdir dir="./build/i2psnark/logs" />
</target>
<target name="clean">
<delete dir="./build" />
<delete file="../i2psnark.war" />
<delete file="./i2psnark-standalone.zip" />
<delete dir="./i2psnark" />
</target>
<target name="cleandep" depends="clean">
</target>
......
......@@ -6,6 +6,7 @@ package org.klomp.snark;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
......@@ -32,7 +33,9 @@ class BWLimits {
session.connect();
rv = session.bandwidthLimits();
session.destroySession();
} catch (I2PSessionException ise) {}
} catch (I2PSessionException ise) {
I2PAppContext.getGlobalContext().logManager().getLog(BWLimits.class).warn("BWL fail", ise);
}
return rv;
}
......
package org.klomp.snark;
/**
* Bandwidth and bandwidth limits
*
* Maintain three bandwidth estimators:
* Sent, received, and requested.
*
* @since 0.9.62
*/
public interface BandwidthListener {
/**
* The average rate in Bps
*/
public long getUploadRate();
/**
* The average rate in Bps
*/
public long getDownloadRate();
/**
* We unconditionally sent this many bytes
*/
public void uploaded(int size);
/**
* We unconditionally received this many bytes
*/
public void downloaded(int size);
/**
* Should we send this many bytes?
* Do NOT call uploaded() if this returns true.
*/
public boolean shouldSend(int size);
/**
* Should we request this many bytes?
*/
public boolean shouldRequest(Peer peer, int size);
/**
* Current limit in BPS
*/
public long getUpBWLimit();
/**
* Current limit in BPS
*/
public long getDownBWLimit();
/**
* Are we currently over the limit?
*/
public boolean overUpBWLimit();
/**
* Are we currently over the limit?
*/
public boolean overDownBWLimit();
}
package org.klomp.snark;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.SyntheticREDQueue;
/**
* Bandwidth and bandwidth limits
*
* Maintain three bandwidth estimators:
* Sent, received, and requested.
*
* There are three layers of BandwidthListeners:
*<pre>
* BandwidthManager (total)
* PeerCoordinator (per-torrent)
* Peer/WebPeer (per-connection)
*</pre>
*
* Here at the top, we use SyntheticRedQueues for accurate
* and current moving averages of up, down, and requested bandwidth.
*
* At the lower layers, simple weighted moving averages of
* three buckets of time PeerCoordinator.CHECK_PERIOD each are used
* for up and down, and requested is delegated here.
*
* The lower layers must report to the next-higher layer.
*
* At the Peer layer, we report inbound piece data per-read,
* not per-piece, to get a smoother inbound estimate.
*
* Only the following data are counted by the BandwidthListeners:
*<ul><li>Pieces (both Peer and WebPeer)
*<li>ut_metadata
*</ul>
*
* No overhead at any layer is accounted for.
*
* @since 0.9.62
*/
public class BandwidthManager implements BandwidthListener {
private final I2PAppContext _context;
private final Log _log;
private SyntheticREDQueue _up, _down, _req;
BandwidthManager(I2PAppContext ctx, int upLimit, int downLimit) {
_context = ctx;
_log = ctx.logManager().getLog(BandwidthManager.class);
_up = new SyntheticREDQueue(ctx, upLimit);
_down = new SyntheticREDQueue(ctx, downLimit);
// Allow down limit a little higher based on testing
// Allow req limit a little higher still because it uses RED
// so it actually kicks in sooner.
_req = new SyntheticREDQueue(ctx, downLimit * 110 / 100);
}
/**
* Current limit in Bps
*/
void setUpBWLimit(long upLimit) {
int limit = (int) Math.min(upLimit, Integer.MAX_VALUE);
if (limit != getUpBWLimit())
_up = new SyntheticREDQueue(_context, limit);
}
/**
* Current limit in Bps
*/
void setDownBWLimit(long downLimit) {
int limit = (int) Math.min(downLimit, Integer.MAX_VALUE);
if (limit != getDownBWLimit()) {
_down = new SyntheticREDQueue(_context, limit);
_req = new SyntheticREDQueue(_context, limit * 110 / 100);
}
}
/**
* The average rate in Bps
*/
long getRequestRate() {
return (long) (1000f * _req.getBandwidthEstimate());
}
// begin BandwidthListener interface
/**
* The average rate in Bps
*/
public long getUploadRate() {
return (long) (1000f * _up.getBandwidthEstimate());
}
/**
* The average rate in Bps
*/
public long getDownloadRate() {
return (long) (1000f * _down.getBandwidthEstimate());
}
/**
* We unconditionally sent this many bytes
*/
public void uploaded(int size) {
_up.addSample(size);
}
/**
* We received this many bytes
*/
public void downloaded(int size) {
_down.addSample(size);
}
/**
* Should we send this many bytes?
* Do NOT call uploaded() if this returns true.
*/
public boolean shouldSend(int size) {
boolean rv = _up.offer(size, 1.0f);
if (!rv && _log.shouldWarn())
_log.warn("Deny sending " + size + " bytes, upload rate " + DataHelper.formatSize(getUploadRate()) + "Bps");
return rv;
}
/**
* Should we request this many bytes?
*
* @param peer ignored
*/
public boolean shouldRequest(Peer peer, int size) {
boolean rv = !overDownBWLimit() && _req.offer(size, 1.0f);
if (!rv && _log.shouldWarn())
_log.warn("Deny requesting " + size + " bytes, download rate " + DataHelper.formatSize(getDownloadRate()) + "Bps" +
", request rate " + DataHelper.formatSize(getRequestRate()) + "Bps");
return rv;
}
/**
* Current limit in BPS
*/
public long getUpBWLimit() {
return _up.getMaxBandwidth();
}
/**
* Current limit in BPS
*/
public long getDownBWLimit() {
return _down.getMaxBandwidth();
}
/**
* Are we currently over the limit?
*/
public boolean overUpBWLimit() {
return getUploadRate() > getUpBWLimit();
}
/**
* Are we currently over the limit?
*/
public boolean overDownBWLimit() {
return getDownloadRate() > getDownBWLimit();
}
/**
* In HTML for debug page
*/
@Override
public String toString() {
return "<br><b>Bandwidth Limiters</b><br><b>Up:</b> " + _up +
"<br><b>Down:</b> " + _down +
"<br><b>Req:</b> " + _req +
"<br>";
}
}
......@@ -4,8 +4,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.i2p.CoreVersion;
/**
* Simple command line access to various utilities.
* Not a public API. Subject to change.
......@@ -38,7 +36,7 @@ public class CommandLine extends net.i2p.util.CommandLine {
}
private static void usage(List<String> classes) {
System.err.println("I2PSnark version " + CoreVersion.VERSION + '\n' +
System.err.println("I2PSnark version " + SnarkManager.FULL_VERSION + '\n' +
"USAGE: java -jar /path/to/i2psnark.jar command [args]");
printCommands(classes);
}
......
......@@ -83,4 +83,9 @@ public interface CompleteListener {
* @since 0.9.42
*/
public boolean shouldAutoStart();
/**
* @since 0.9.62
*/
public BandwidthListener getBandwidthListener();
}
......@@ -42,15 +42,5 @@ interface CoordinatorListener
*/
public boolean overUploadLimit(int uploaders);
/**
* Is i2psnark as a whole over its limit?
*/
public boolean overUpBWLimit();
/**
* Is a particular peer who has this recent download rate (in Bps) over our upstream bandwidth limit?
*/
public boolean overUpBWLimit(long total);
public void addMessage(String message);
}
......@@ -190,6 +190,8 @@ abstract class ExtensionHandler {
}
if (log.shouldLog(Log.INFO))
log.info("Request chunk " + chk + " from " + peer);
// ignore the rv, always request
peer.shouldRequest(state.chunkSize(chk));
sendRequest(peer, chk);
}
} catch (Exception e) {
......@@ -230,7 +232,6 @@ abstract class ExtensionHandler {
sendPiece(peer, piece, pc, totalSize);
// Do this here because PeerConnectionOut only reports for PIECE messages
peer.uploaded(pc.length);
listener.uploaded(peer, pc.length);
} else if (type == TYPE_DATA) {
// On close reading of BEP 9, this is the total metadata size.
// Prior to 0.9.21, we sent the piece size, so we can't count on it.
......@@ -245,7 +246,6 @@ abstract class ExtensionHandler {
return;
int len = is.available();
peer.downloaded(len);
listener.downloaded(peer, len);
// this checks the size
done = state.saveChunk(piece, bs, bs.length - len, len);
if (log.shouldLog(Log.INFO))
......@@ -264,6 +264,8 @@ abstract class ExtensionHandler {
// get the next chunk
if (log.shouldLog(Log.INFO))
log.info("Request chunk " + chk + " from " + peer);
// ignore the rv, always request
peer.shouldRequest(state.chunkSize(chk));
sendRequest(peer, chk);
}
} else if (type == TYPE_REJECT) {
......
package org.klomp.snark;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
......@@ -13,6 +20,7 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
......@@ -28,6 +36,7 @@ import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.ConcurrentHashSet;
......@@ -36,7 +45,9 @@ import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SimpleTimer;
import net.i2p.util.SystemVersion;
import net.i2p.util.Translate;
import org.klomp.snark.dht.DHT;
......@@ -77,6 +88,7 @@ public class I2PSnarkUtil implements DisconnectListener {
private DHT _dht;
private long _startedTime;
private final DisconnectListener _discon;
private int _maxFilesPerTorrent = SnarkManager.DEFAULT_MAX_FILES_PER_TORRENT;
private static final int EEPGET_CONNECT_TIMEOUT = 45*1000;
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
......@@ -242,6 +254,11 @@ public class I2PSnarkUtil implements DisconnectListener {
/** @since 0.9.1 */
public File getTempDir() { return _tmpDir; }
/** @since 0.9.58 */
public int getMaxFilesPerTorrent() { return _maxFilesPerTorrent; }
/** @since 0.9.58 */
public void setMaxFilesPerTorrent(int max) { _maxFilesPerTorrent = Math.max(max, 1); }
/**
* Connect to the router, if we aren't already
*/
......@@ -319,6 +336,8 @@ public class I2PSnarkUtil implements DisconnectListener {
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
if (opts.getProperty("i2p.streaming.answerPings") == null)
opts.setProperty("i2p.streaming.answerPings", "false");
if (opts.getProperty(I2PSocketOptions.PROP_PROFILE) == null)
opts.setProperty(I2PSocketOptions.PROP_PROFILE, Integer.toString(I2PSocketOptions.PROFILE_BULK));
if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null)
opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
if (opts.getProperty("i2cp.leaseSetEncType") == null)
......@@ -829,4 +848,90 @@ public class I2PSnarkUtil implements DisconnectListener {
public String getString(int n, String s, String p) {
return Translate.getString(n, s, p, _context, BUNDLE_NAME);
}
private static final boolean SHOULD_SYNC = !(SystemVersion.isAndroid() || SystemVersion.isARM());
private static final Pattern ILLEGAL_KEY = Pattern.compile("[#=\\r\\n;]");
private static final Pattern ILLEGAL_VALUE = Pattern.compile("[\\r\\n]");
/**
* Same as DataHelper.loadProps() but allows '#' in values,
* so we can have filenames with '#' in them in torrent config files.
* '#' must be in column 1 for a comment.
*
* @since 0.9.58
*/
static void loadProps(Properties props, File f) throws IOException {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"), 1024);
String line = null;
while ( (line = in.readLine()) != null) {
if (line.trim().length() <= 0) continue;
if (line.charAt(0) == '#') continue;
if (line.charAt(0) == ';') continue;
int split = line.indexOf('=');
if (split <= 0) continue;
String key = line.substring(0, split);
String val = line.substring(split+1).trim();
props.setProperty(key, val);
}
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
/**
* Same as DataHelper.loadProps() but allows '#' in values,
* so we can have filenames with '#' in them in torrent config files.
* '#' must be in column 1 for a comment.
*
* @since 0.9.58
*/
static void storeProps(Properties props, File file) throws IOException {
FileOutputStream fos = null;
PrintWriter out = null;
IOException ioe = null;
File tmpFile = new File(file.getPath() + ".tmp");
try {
fos = new SecureFileOutputStream(tmpFile);
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(fos, "UTF-8")));
out.println("# NOTE: This I2P config file must use UTF-8 encoding");
out.println("# Last saved: " + DataHelper.formatTime(System.currentTimeMillis()));
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String name = (String) entry.getKey();
String val = (String) entry.getValue();
if (ILLEGAL_KEY.matcher(name).find()) {
if (ioe == null)
ioe = new IOException("Invalid character (one of \"#;=\\r\\n\") in key: \"" +
name + "\" = \"" + val + '\"');
continue;
}
if (ILLEGAL_VALUE.matcher(val).find()) {
if (ioe == null)
ioe = new IOException("Invalid character (one of \"\\r\\n\") in value: \"" +
name + "\" = \"" + val + '\"');
continue;
}
out.println(name + "=" + val);
}
if (SHOULD_SYNC) {
out.flush();
fos.getFD().sync();
}
out.close();
if (out.checkError()) {
out = null;
tmpFile.delete();
throw new IOException("Failed to write properties to " + tmpFile);
}
out = null;
if (!FileUtil.rename(tmpFile, file))
throw new IOException("Failed rename from " + tmpFile + " to " + file);
} finally {
if (out != null) out.close();
if (fos != null) try { fos.close(); } catch (IOException e) {}
}
if (ioe != null)
throw ioe;
}
}
......@@ -63,7 +63,7 @@ public class MetaInfo
private final int piece_length;
private final byte[] piece_hashes;
private final long length;
private final boolean privateTorrent;
private final int privateTorrent; // 0: not present; 1: = 1; -1: = 0
private final List<List<String>> announce_list;
private final String comment;
private final String created_by;
......@@ -97,7 +97,7 @@ public class MetaInfo
this.piece_length = piece_length;
this.piece_hashes = piece_hashes;
this.length = length;
this.privateTorrent = privateTorrent;
this.privateTorrent = privateTorrent ? 1 : 0;
this.announce_list = announce_list;
this.comment = comment;
this.created_by = created_by;
......@@ -117,9 +117,72 @@ public class MetaInfo
//infoMap = null;
}
/**
* Preserves privateTorrent int value, for main()
*
* @since 0.9.62
*/
public MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
int piece_length, byte[] piece_hashes, long length, int privateTorrent,
List<List<String>> announce_list, String created_by, List<String> url_list, String comment)
{
this.announce = announce;
this.name = name;
this.name_utf8 = name_utf8;
this.files = files == null ? null : Collections.unmodifiableList(files);
this.files_utf8 = null;
this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths);
this.piece_length = piece_length;
this.piece_hashes = piece_hashes;
this.length = length;
this.privateTorrent = privateTorrent;
this.announce_list = announce_list;
this.comment = comment;
this.created_by = created_by;
this.creation_date = I2PAppContext.getGlobalContext().clock().now();
this.url_list = url_list;
this.attributes = null;
this.info_hash = calculateInfoHash();
}
/**
* Will not change infohash.
* Retains creation date of old MetaInfo if nonzero.
*
* @param new_announce may be null
* @param new_announce_list may be null
* @param new_comment may be null
* @param new_created_by may be null
* @param new_url_list may be null
* @since 0.9.64
*/
public MetaInfo(MetaInfo old, String new_announce, List<List<String>> new_announce_list, String new_comment,
String new_created_by, List<String> new_url_list)
{
this.announce = new_announce;
this.info_hash = old.info_hash;
this.name = old.name;
this.name_utf8 = old.name_utf8;
this.files = old.files;
this.files_utf8 = old.files_utf8;
this.attributes = old.attributes;
this.lengths = old.lengths;
this.piece_length = old.piece_length;
this.piece_hashes = old.piece_hashes;
this.length = old.length;
this.privateTorrent = old.privateTorrent;
this.announce_list = new_announce_list;
this.comment = new_comment;
this.created_by = new_created_by;
this.creation_date = old.creation_date > 0 ? old.creation_date : I2PAppContext.getGlobalContext().clock().now();
this.url_list = new_url_list;
this.infoMap = old.infoMap;
this.infoBytesLength = old.infoBytesLength;
}
/**
* Creates a new MetaInfo from the given InputStream. The
* InputStream must start with a correctly bencoded dictonary
* InputStream must start with a correctly bencoded dictionary
* describing the torrent.
* Caller must close the stream.
*/
......@@ -144,7 +207,7 @@ public class MetaInfo
/**
* Creates a new MetaInfo from a Map of BEValues and the SHA1 over
* the original bencoded info dictonary (this is a hack, we could
* the original bencoded info dictionary (this is a hack, we could
* reconstruct the bencoded stream and recalculate the hash). Will
* NOT throw a InvalidBEncodingException if the given map does not
* contain a valid announce string.
......@@ -186,11 +249,20 @@ public class MetaInfo
if (val == null) {
this.url_list = null;
} else {
List<BEValue> bl1 = val.getList();
this.url_list = new ArrayList<String>(bl1.size());
for (BEValue bev : bl1) {
this.url_list.add(bev.getString());
List<String> urllist;
try {
List<BEValue> bl1 = val.getList();
urllist = new ArrayList<String>(bl1.size());
for (BEValue bev : bl1) {
urllist.add(bev.getString());
}
} catch (InvalidBEncodingException ibee) {
// BEP 19 says it's a list but the example there
// is for a single byte string, and we've seen this
// in the wild.
urllist = Collections.singletonList(val.getString());
}
this.url_list = urllist;
}
// misc. optional top-level stuff
......@@ -248,10 +320,11 @@ public class MetaInfo
// Transmission does numbers. So does libtorrent.
// We handle both as of 0.9.9.
// We switch to storing as number as of 0.9.14.
privateTorrent = "1".equals(o) ||
boolean privat = "1".equals(o) ||
((o instanceof Number) && ((Number) o).intValue() == 1);
privateTorrent = privat ? 1 : -1;
} else {
privateTorrent = false;
privateTorrent = 0;
}
val = info.get("piece length");
......@@ -468,6 +541,14 @@ public class MetaInfo
* @since 0.9
*/
public boolean isPrivate() {
return privateTorrent > 0;
}
/**
* @return 0 (default), 1 (set to 1), -1 (set to 0)
* @since 0.9.62
*/
public int getPrivateTrackerStatus() {
return privateTorrent;
}
......@@ -729,10 +810,10 @@ public class MetaInfo
if (name_utf8 != null)
info.put("name.utf-8", new BEValue(DataHelper.getUTF8(name_utf8)));
// BEP 27
if (privateTorrent)
if (privateTorrent != 0)
// switched to number in 0.9.14
//info.put("private", new BEValue(DataHelper.getUTF8("1")));
info.put("private", new BEValue(Integer.valueOf(1)));
info.put("private", new BEValue(Integer.valueOf(privateTorrent > 0 ? 1 : 0)));
info.put("piece length", new BEValue(Integer.valueOf(piece_length)));
info.put("pieces", new BEValue(piece_hashes));
......@@ -810,7 +891,7 @@ public class MetaInfo
String announce = null;
List<String> url_list = null;
String comment = null;
Getopt g = new Getopt("Storage", args, "a:c:m:w:");
Getopt g = new Getopt("MetaInfo", args, "a:c:m:w:");
try {
int c;
while ((c = g.getopt()) != -1) {
......@@ -865,10 +946,7 @@ public class MetaInfo
String an = announce != null ? announce : meta.getAnnounce();
String cm = comment != null ? comment : meta.getComment();
List<String> urls = url_list != null ? url_list : meta.getWebSeedURLs();
// changes/adds creation date
MetaInfo meta2 = new MetaInfo(an, meta.getName(), null, meta.getFiles(), meta.getLengths(),
meta.getPieceLength(0), meta.getPieceHashes(), meta.getTotalLength(), meta.isPrivate(),
meta.getAnnounceList(), cb, urls, cm);
MetaInfo meta2 = new MetaInfo(meta, an, meta.getAnnounceList(), cm, cb, urls);
java.io.File from = new java.io.File(args[i]);
java.io.File to = new java.io.File(args[i] + ".bak");
if (net.i2p.util.FileUtil.copy(from, to, true, false)) {
......