Merge remote-tracking branch 'origin/master'

This commit is contained in:
Roman Shevchenko
2016-09-19 14:48:42 +03:00
46 changed files with 1276 additions and 832 deletions
@@ -60,14 +60,6 @@
<text resource-bundle="messages/CompilerBundle" key="label.option.clear.output.directory.on.rebuild"/>
</properties>
</component>
<component id="366e" class="javax.swing.JCheckBox" binding="myCbAssertNotNull">
<constraints>
<grid row="2" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/CompilerBundle" key="add.notnull.assertions"/>
</properties>
</component>
<component id="348ee" class="javax.swing.JCheckBox" binding="myCbAutoShowFirstError">
<constraints>
<grid row="3" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
@@ -172,6 +164,37 @@
<text value="Shared build process VM options:"/>
</properties>
</component>
<grid id="6ee49" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="366e" class="javax.swing.JCheckBox" binding="myCbAssertNotNull">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/CompilerBundle" key="add.notnull.assertions"/>
</properties>
</component>
<component id="73479" class="javax.swing.JButton" binding="myConfigureAnnotations">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Configure annotations..."/>
</properties>
</component>
<hspacer id="4b9fc">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</children>
</grid>
</form>
@@ -15,20 +15,23 @@
*/
package com.intellij.compiler.options;
import com.intellij.codeInsight.NullableNotNullDialog;
import com.intellij.compiler.CompilerConfiguration;
import com.intellij.compiler.CompilerConfigurationImpl;
import com.intellij.compiler.CompilerWorkspaceConfiguration;
import com.intellij.compiler.MalformedPatternException;
import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
import com.intellij.compiler.server.BuildManager;
import com.intellij.ide.DataManager;
import com.intellij.ide.PowerSaveMode;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.compiler.CompilerBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.Gray;
@@ -44,6 +47,8 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.List;
@@ -80,6 +85,7 @@ public class CompilerUIConfigurable implements SearchableConfigurable, Configura
private JLabel myResourcePatternsLabel;
private JLabel myEnableAutomakeLegendLabel;
private JLabel myParallelCompilationLegendLabel;
private JButton myConfigureAnnotations;
public CompilerUIConfigurable(@NotNull final Project project) {
myProject = project;
@@ -99,6 +105,14 @@ public class CompilerUIConfigurable implements SearchableConfigurable, Configura
s -> StringUtil.startsWithIgnoreCase(s, "-Xmx")) == null);
}
});
myConfigureAnnotations.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myPanel));
if (project == null) project = ProjectManager.getInstance().getDefaultProject();
new NullableNotNullDialog(project).show();
}
});
}
private void tweakControls(@NotNull Project project) {
@@ -65,7 +65,7 @@ import java.util.zip.ZipFile;
public class PluginManagerCore {
private static final Logger LOG = Logger.getInstance(PluginManagerCore.class);
private static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
public static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
public static final String CORE_PLUGIN_ID = "com.intellij";
private static final String META_INF = "META-INF";
public static final String PLUGIN_XML = "plugin.xml";
@@ -101,6 +101,8 @@ public class PluginManagerCore {
}
}
private static List<Runnable> myDisablePluginListeners;
/**
* do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by IdeaClassLoader
*/
@@ -140,6 +142,7 @@ public class PluginManagerCore {
reader.close();
if (!requiredPlugins.isEmpty()) {
savePluginsList(disabledPlugins, false, new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME));
fireEditDisablePlugins();
}
}
}
@@ -204,6 +207,27 @@ public class PluginManagerCore {
return app != null && app.isUnitTestMode();
}
public static void addDisablePluginListener(@NotNull Runnable listener) {
if (myDisablePluginListeners == null) {
myDisablePluginListeners = new ArrayList<Runnable>();
}
myDisablePluginListeners.add(listener);
}
public static void removeDisablePluginListener(@NotNull Runnable listener) {
if (myDisablePluginListeners != null) {
myDisablePluginListeners.remove(listener);
}
}
private static void fireEditDisablePlugins() {
if (myDisablePluginListeners != null) {
for (Runnable listener : myDisablePluginListeners) {
listener.run();
}
}
}
public static void savePluginsList(@NotNull Collection<String> ids, boolean append, @NotNull File plugins) throws IOException {
if (!plugins.isFile()) {
FileUtil.ensureCanCreateFile(plugins);
@@ -256,6 +280,7 @@ public class PluginManagerCore {
File plugins = new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME);
savePluginsList(ids, append, plugins);
ourDisabledPlugins = null;
fireEditDisablePlugins();
}
public static Logger getLogger() {
@@ -63,7 +63,12 @@ public class HelpManagerImpl extends HelpManager {
if (broker == null) {
ApplicationInfoEx info = ApplicationInfoEx.getInstanceEx();
String productVersion = info.getMajorVersion() + "." + info.getMinorVersion();
String minorVersion = info.getMinorVersion();
int dot = minorVersion.indexOf('.');
if (dot != -1) {
minorVersion = minorVersion.substring(0, dot);
}
String productVersion = info.getMajorVersion() + "." + minorVersion;
String productCode = info.getPackageCode();
String url = info.getWebHelpUrl() + "/" + productVersion + "/?" + id;
@@ -0,0 +1,60 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.cloudConfig;
import com.intellij.ide.customize.AbstractCustomizeWizardStep;
import com.intellij.openapi.application.ConfigImportSettings;
import com.intellij.openapi.application.ImportOldConfigsPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.util.List;
/**
* @author Alexander Lobas
*/
public abstract class CloudConfigProvider {
private static CloudConfigProvider myProvider;
@Nullable
public static CloudConfigProvider getProvider() {
return myProvider;
}
public static void setProvider(@Nullable CloudConfigProvider provider) {
myProvider = provider;
}
public abstract void initConfigsPanel(@NotNull ImportOldConfigsPanel dialog,
@NotNull JPanel parentPanel,
@NotNull ConfigImportSettings settings);
public abstract void importFinished(@NotNull File newConfigDir);
public abstract void beforeStartupWizard();
@Nullable
public abstract String getLafClassName();
@NotNull
public abstract List<String> getInstalledPlugins();
public abstract int initSteps(@NotNull List<AbstractCustomizeWizardStep> steps);
public abstract void startupWizardFinished();
}
@@ -23,6 +23,7 @@ import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.updateSettings.impl.PluginDownloader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
@@ -84,7 +85,7 @@ public class CustomizeFeaturedPluginsStepPanel extends AbstractCustomizeWizardSt
int i = s.indexOf(':');
String topic = s.substring(0, i);
int j = s.indexOf(':', i + 1);
final String description = s.substring(i + 1, j);
String description = s.substring(i + 1, j);
final String pluginId = s.substring(j + 1);
IdeaPluginDescriptor foundDescriptor = null;
for (IdeaPluginDescriptor descriptor : pluginsFromRepository) {
@@ -96,18 +97,29 @@ public class CustomizeFeaturedPluginsStepPanel extends AbstractCustomizeWizardSt
if (foundDescriptor == null) continue;
final IdeaPluginDescriptor descriptor = foundDescriptor;
final boolean isVIM = PluginGroups.IDEA_VIM_PLUGIN_ID.equals(descriptor.getPluginId().getIdString());
boolean isCloud = "#Cloud".equals(topic);
if (isCloud) {
title = descriptor.getName();
description = StringUtil.defaultIfEmpty(descriptor.getDescription(), "No description available");
topic = StringUtil.defaultIfEmpty(descriptor.getCategory(), "Unknown");
}
JLabel titleLabel = new JLabel("<html><body><h2 style=\"text-align:left;\">" + title + "</h2></body></html>");
JLabel topicLabel = new JLabel("<html><body><h4 style=\"text-align:left;color:#808080;font-weight:bold;\">" + topic + "</h4></body></html>");
JLabel descriptionLabel = createHTMLLabel(description);
JLabel warningLabel = null;
if (isVIM) {
warningLabel = createHTMLLabel("Recommended only if you are<br> familiar with Vim.");
warningLabel.setIcon(AllIcons.General.BalloonWarning);
if (isVIM || isCloud) {
if (isCloud) {
warningLabel = createHTMLLabel("JBA account");
warningLabel.setIcon(AllIcons.General.BalloonInformation);
}
else {
warningLabel = createHTMLLabel("Recommended only if you are<br> familiar with Vim.");
warningLabel.setIcon(AllIcons.General.BalloonWarning);
}
if (!SystemInfo.isWindows) UIUtil.applyStyle(UIUtil.ComponentStyle.SMALL, warningLabel);
}
@@ -15,6 +15,7 @@
*/
package com.intellij.ide.customize;
import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.ide.startup.StartupActionScriptManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.ui.DialogWrapper;
@@ -58,6 +59,12 @@ public class CustomizeIDEWizardDialog extends DialogWrapper implements ActionLis
getPeer().setAppIcons();
stepsProvider.initSteps(this, mySteps);
CloudConfigProvider configProvider = CloudConfigProvider.getProvider();
if (configProvider != null) {
myIndex = configProvider.initSteps(mySteps);
}
if (mySteps.isEmpty()) {
throw new IllegalArgumentException(stepsProvider + " provided no steps");
}
@@ -17,6 +17,7 @@ package com.intellij.ide.customize;
import com.intellij.CommonBundle;
import com.intellij.ide.WelcomeWizardUtil;
import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.ide.ui.LafManager;
import com.intellij.ide.ui.laf.IntelliJLaf;
import com.intellij.ide.ui.laf.LafManagerImpl;
@@ -26,7 +27,9 @@ import com.intellij.openapi.options.OptionsBundle;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.IconUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
@@ -89,7 +92,7 @@ public class CustomizeUIThemeStepPanel extends AbstractCustomizeWizardStep {
myColumnMode = myThemes.size() > 2;
JPanel buttonsPanel = new JPanel(new GridLayout(myColumnMode ? myThemes.size() : 1, myColumnMode ? 1 : myThemes.size(), 5, 5));
ButtonGroup group = new ButtonGroup();
final ThemeInfo myDefaultTheme = myThemes.iterator().next();
final ThemeInfo myDefaultTheme = getDefaultTheme();
for (final ThemeInfo theme : myThemes) {
final JRadioButton radioButton = new JRadioButton(theme.name, myDefaultTheme == theme);
@@ -146,6 +149,21 @@ public class CustomizeUIThemeStepPanel extends AbstractCustomizeWizardStep {
}
}
@NotNull
private ThemeInfo getDefaultTheme() {
CloudConfigProvider provider = CloudConfigProvider.getProvider();
if (provider != null) {
String lafClassName = provider.getLafClassName();
if (lafClassName != null) {
ThemeInfo result = ContainerUtil.find(myThemes, theme -> lafClassName.equals(theme.laf));
if (result != null) {
return result;
}
}
}
return myThemes.iterator().next();
}
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
@@ -16,6 +16,7 @@
package com.intellij.ide.customize;
import com.intellij.ide.WelcomeWizardUtil;
import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.ide.plugins.RepositoryHelper;
@@ -32,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
public class PluginGroups {
@@ -84,6 +86,7 @@ public class PluginGroups {
PluginManagerCore.loadDisabledPlugins(new File(PathManager.getConfigPath()).getPath(), myDisabledPluginIds);
initGroups(myTree, myFeaturedPlugins);
initCloudPlugins();
}
public void setLoadingCallback(Runnable loadingCallback) {
@@ -93,6 +96,29 @@ public class PluginGroups {
}
}
private void initCloudPlugins() {
CloudConfigProvider provider = CloudConfigProvider.getProvider();
if (provider == null) {
return;
}
List<String> plugins = provider.getInstalledPlugins();
if (plugins.isEmpty()) {
return;
}
for (Iterator<Entry<String, String>> I = myFeaturedPlugins.entrySet().iterator(); I.hasNext(); ) {
String value = I.next().getValue();
if (ContainerUtil.find(plugins, plugin -> value.endsWith(":" + plugin)) != null) {
I.remove();
}
}
for (String plugin : plugins) {
myFeaturedPlugins.put(plugin, "#Cloud:#Cloud:" + plugin);
}
}
protected void
initGroups(Map<String, Pair<Icon, List<String>>> tree, Map<String, String> featuredPlugins) {
tree.put(CORE, Pair.create((Icon)null, Arrays.asList(
@@ -263,7 +289,7 @@ public class PluginGroups {
private void initIfNeed() {
if (myInitialized) return;
myInitialized = true;
for (Map.Entry<String, Pair<Icon, List<String>>> entry : myTree.entrySet()) {
for (Entry<String, Pair<Icon, List<String>>> entry : myTree.entrySet()) {
final String group = entry.getKey();
if (CORE.equals(group)) continue;
@@ -19,6 +19,7 @@ import com.intellij.ide.DataManager;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.impl.dataRules.*;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
@@ -51,6 +52,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class DataManagerImpl extends DataManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.ide.impl.DataManagerImpl");
@@ -209,13 +211,24 @@ public class DataManagerImpl extends DataManager {
@Override
public AsyncResult<DataContext> getDataContextFromFocus() {
final AsyncResult<DataContext> context = new AsyncResult<>();
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> context.setDone(getDataContext()));
AsyncResult<DataContext> context = new AsyncResult<>();
doWhenFocusSettlesDownInModality(context, ModalityState.current());
return context;
}
private void doWhenFocusSettlesDownInModality(AsyncResult<DataContext> context, ModalityState modality) {
AtomicBoolean immediate = new AtomicBoolean(true);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
if (immediate.get()) {
context.setDone(getDataContext());
return;
}
ApplicationManager.getApplication().invokeLater(() -> doWhenFocusSettlesDownInModality(context, modality), modality);
});
immediate.set(false);
}
public DataContext getDataContextTest(Component component) {
DataContext dataContext = getDataContext(component);
if (myWindowManager == null) {
@@ -45,7 +45,6 @@ import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.ui.StatusText;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -151,7 +150,7 @@ public class InstalledPluginsManagerMain extends PluginManagerMain {
}
}
PluginInstaller.install(file, file.getName(), false);
PluginInstaller.install(file, file.getName(), false, pluginDescriptor);
ourState.onPluginInstall(pluginDescriptor);
checkInstalledPluginDependencies(model, pluginDescriptor, parent);
callback.consume(pair(file, (IdeaPluginDescriptor)pluginDescriptor));
@@ -30,10 +30,10 @@ import com.intellij.openapi.updateSettings.impl.UpdateSettings;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.GuiUtils;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.ZipUtil;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
@@ -232,6 +232,8 @@ public class PluginInstaller {
if (pluginDescriptor != null) {
StartupActionScriptManager.ActionCommand deleteOld = new StartupActionScriptManager.DeleteCommand(pluginDescriptor.getPath());
StartupActionScriptManager.addActionCommand(deleteOld);
fireState(pluginDescriptor, false);
}
else {
PluginManagerMain.LOG.error("Plugin not found: " + pluginId);
@@ -240,7 +242,10 @@ public class PluginInstaller {
}
}
public static void install(final File fromFile, final String pluginName, boolean deleteFromFile) throws IOException {
public static void install(@NotNull File fromFile,
@NotNull String pluginName,
boolean deleteFromFile,
@NotNull IdeaPluginDescriptor descriptor) throws IOException {
//noinspection HardCodedStringLiteral
if (fromFile.getName().endsWith(".jar")) {
// add command to copy file to the IDEA/plugins path
@@ -267,5 +272,37 @@ public class PluginInstaller {
StartupActionScriptManager.ActionCommand deleteTemp = new StartupActionScriptManager.DeleteCommand(fromFile);
StartupActionScriptManager.addActionCommand(deleteTemp);
}
fireState(descriptor, true);
}
private static List<PluginStateListener> myStateListeners;
public static void addStateListener(@NotNull PluginStateListener listener) {
if (myStateListeners == null) {
myStateListeners = new ArrayList<>();
}
myStateListeners.add(listener);
}
public static void removeStateListener(@NotNull PluginStateListener listener) {
if (myStateListeners != null) {
myStateListeners.remove(listener);
}
}
private static void fireState(@NotNull IdeaPluginDescriptor descriptor, boolean install) {
if (myStateListeners != null) {
UIUtil.invokeLaterIfNeeded(() -> {
for (PluginStateListener listener : myStateListeners) {
if (install) {
listener.install(descriptor);
}
else {
listener.uninstall(descriptor);
}
}
});
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.plugins;
import org.jetbrains.annotations.NotNull;
/**
* @author Alexander Lobas
*/
public interface PluginStateListener {
void install(@NotNull IdeaPluginDescriptor descriptor);
void uninstall(@NotNull IdeaPluginDescriptor descriptor);
}
@@ -15,6 +15,7 @@
*/
package com.intellij.idea;
import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.ide.customize.CustomizeIDEWizardDialog;
import com.intellij.ide.customize.CustomizeIDEWizardStepsProvider;
import com.intellij.ide.plugins.PluginManagerCore;
@@ -83,6 +84,8 @@ public class StartupUtil {
interface AppStarter {
void start(boolean newConfigFolder);
default void beforeImportConfigs() {}
}
static void prepareAndStart(String[] args, AppStarter appStarter) {
@@ -125,6 +128,7 @@ public class StartupUtil {
}
if (newConfigFolder) {
appStarter.beforeImportConfigs();
ConfigImportHelper.importConfigsTo(PathManager.getConfigPath());
}
@@ -422,8 +426,18 @@ public class StartupUtil {
return;
}
CloudConfigProvider configProvider = CloudConfigProvider.getProvider();
if (configProvider != null) {
configProvider.beforeStartupWizard();
}
new CustomizeIDEWizardDialog(provider).show();
PluginManagerCore.invalidatePlugins();
if (configProvider != null) {
configProvider.startupWizardFinished();
}
return;
}
@@ -15,6 +15,7 @@
*/
package com.intellij.openapi.application;
import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.plugins.PluginManagerCore;
@@ -79,6 +80,12 @@ public class ConfigImportHelper {
doImport(newConfigDir, oldConfigDir, settings, installationHome);
settings.importFinished(newConfigPath);
System.setProperty(CONFIG_IMPORTED_IN_CURRENT_SESSION_KEY, Boolean.TRUE.toString());
CloudConfigProvider provider = CloudConfigProvider.getProvider();
if (provider != null) {
provider.importFinished(newConfigDir);
}
break;
}
}
@@ -230,7 +237,7 @@ public class ConfigImportHelper {
}
@Nullable
private static File getOldConfigDir(@Nullable File oldInstallHome, ConfigImportSettings settings) {
public static File getOldConfigDir(@Nullable File oldInstallHome, ConfigImportSettings settings) {
if (oldInstallHome == null) {
return null;
}
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.openapi.application.ImportOldConfigsPanel">
<grid id="47e28" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="47e28" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<xy x="28" y="33" width="566" height="272"/>
<xy x="28" y="33" width="575" height="286"/>
</constraints>
<properties/>
<border type="none"/>
@@ -11,7 +11,7 @@
<grid id="1c1db" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -38,7 +38,7 @@
</grid>
<vspacer id="ccd5d">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="865b3" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
@@ -123,6 +123,16 @@
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<grid id="ece75" binding="myCustomPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<visible value="false"/>
</properties>
<border type="none"/>
<children/>
</grid>
</children>
</grid>
</form>
@@ -15,6 +15,7 @@
*/
package com.intellij.openapi.application;
import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.openapi.MnemonicHelper;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.SystemInfo;
@@ -45,6 +46,7 @@ public class ImportOldConfigsPanel extends JDialog {
private JLabel mySuggestLabel;
private JLabel myHomeLabel;
private JRadioButton myRbImportAuto;
private JPanel myCustomPanel;
private final File myGuessedOldConfig;
private final ConfigImportSettings mySettings;
@@ -120,6 +122,11 @@ public class ImportOldConfigsPanel extends JDialog {
}
});
CloudConfigProvider configProvider = CloudConfigProvider.getProvider();
if (configProvider != null) {
configProvider.initConfigsPanel(this, myCustomPanel, mySettings);
}
getContentPane().setLayout(new BorderLayout());
getContentPane().add(myRootPanel);
getRootPane().setDefaultButton(myOkButton);
@@ -218,7 +218,7 @@ public class PluginDownloader {
StartupActionScriptManager.ActionCommand deleteOld = new StartupActionScriptManager.DeleteCommand(myOldFile);
StartupActionScriptManager.addActionCommand(deleteOld);
}
PluginInstaller.install(myFile, getPluginName(), true);
PluginInstaller.install(myFile, getPluginName(), true, myDescriptor);
InstalledPluginsState state = InstalledPluginsState.getInstanceIfLoaded();
if (state != null) {
state.onPluginInstall(myDescriptor);
@@ -24,6 +24,7 @@ import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
import com.jetbrains.edu.learning.stepic.CCStepicConnector;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import org.jetbrains.annotations.NotNull;
@@ -61,7 +62,7 @@ public class CCGetCourseFromStepic extends DumbAwareAction {
private static void createCourse(Project project, String courseId) {
final VirtualFile baseDir = project.getBaseDir();
final CourseInfo info = EduStepicConnector.getCourseInfo(project, courseId);
final CourseInfo info = CCStepicConnector.getCourseInfo(project, courseId);
if (info == null) return;
final Course course = EduStepicConnector.getCourse(project, info);
@@ -15,8 +15,8 @@ import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
import com.jetbrains.edu.learning.stepic.CCStepicConnector;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -59,21 +59,21 @@ public class CCPushCourse extends DumbAwareAction {
public void run(@NotNull ProgressIndicator indicator) {
for (Lesson lesson : course.getLessons()) {
if (lesson.getId() > 0) {
EduStepicConnector.updateLesson(project, lesson, indicator);
CCStepicConnector.updateLesson(project, lesson, indicator);
}
else {
final CourseInfo info = CourseInfo.fromCourse(course);
final int lessonId = EduStepicConnector.postLesson(project, lesson, indicator);
final int lessonId = CCStepicConnector.postLesson(project, lesson, indicator);
final List<Integer> sections = info.getSections();
final Integer sectionId = sections.get(sections.size() - 1);
EduStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
CCStepicConnector.postUnit(project, lessonId, lesson.getIndex(), sectionId);
}
}
}
});
}
else {
EduStepicConnector.postCourseWithProgress(project, course);
CCStepicConnector.postCourseWithProgress(project, course);
}
EduUsagesCollector.courseUploaded();
}
@@ -15,8 +15,8 @@ import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.stepic.CCStepicConnector;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.EduStepicNames;
import org.jetbrains.annotations.NotNull;
@@ -77,14 +77,14 @@ public class CCPushLesson extends DumbAwareAction {
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText("Uploading lesson to " + EduStepicNames.STEPIC_URL);
if (lesson.getId() > 0) {
EduStepicConnector.updateLesson(project, lesson, indicator);
CCStepicConnector.updateLesson(project, lesson, indicator);
}
else {
final CourseInfo info = CourseInfo.fromCourse(course);
final int lessonId = EduStepicConnector.postLesson(project, lesson, indicator);
final int lessonId = CCStepicConnector.postLesson(project, lesson, indicator);
final List<Integer> sections = info.getSections();
final Integer sectionId = sections.get(sections.size()-1);
EduStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
CCStepicConnector.postUnit(project, lessonId, lesson.getIndex(), sectionId);
}
}});
}
@@ -16,7 +16,7 @@ import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.CCStepicConnector;
import com.jetbrains.edu.learning.stepic.EduStepicNames;
import org.jetbrains.annotations.NotNull;
@@ -48,7 +48,7 @@ public class CCPushTask extends DumbAwareAction {
if (lesson != null && lesson.getId() > 0 && course.getId() > 0) {
e.getPresentation().setEnabledAndVisible(true);
final com.jetbrains.edu.learning.courseFormat.Task task = lesson.getTask(taskDir.getName());
if (task.getStepicId() <= 0) {
if (task.getStepId() <= 0) {
e.getPresentation().setText("Upload Task to Stepik");
}
}
@@ -81,11 +81,11 @@ public class CCPushTask extends DumbAwareAction {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText("Uploading task to " + EduStepicNames.STEPIC_URL);
if (task.getStepicId() <= 0) {
EduStepicConnector.postTask(project, task, lesson.getId());
if (task.getStepId() <= 0) {
CCStepicConnector.postTask(project, task, lesson.getId());
}
else {
EduStepicConnector.updateTask(project, task);
CCStepicConnector.updateTask(project, task);
}
}
});
@@ -97,26 +97,19 @@ public class StudyProjectComponent implements ProjectComponent {
}
StudyUtils.registerStudyToolWindow(course, myProject);
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
@Override
public void run() {
Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null) {
final UISettings instance = UISettings.getInstance();
if (instance != null) {
instance.HIDE_TOOL_STRIPES = false;
instance.fireUISettingsChanged();
}
registerShortcuts();
EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
}
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().invokeLater(
(DumbAwareRunnable)() -> ApplicationManager.getApplication().runWriteAction((DumbAwareRunnable)() -> {
Course course1 = StudyTaskManager.getInstance(myProject).getCourse();
if (course1 != null) {
final UISettings instance = UISettings.getInstance();
if (instance != null) {
instance.HIDE_TOOL_STRIPES = false;
instance.fireUISettingsChanged();
}
});
}
}));
registerShortcuts();
EduUsagesCollector.projectTypeOpened(course1.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
}
})));
}
private void registerShortcuts() {
@@ -197,7 +190,7 @@ public class StudyProjectComponent implements ProjectComponent {
final ArrayList<Task> tasks = new ArrayList<>();
for (Task task : lesson.getTaskList()) {
index += 1;
final Task studentTask = studentLesson.getTask(task.getStepicId());
final Task studentTask = studentLesson.getTask(task.getStepId());
if (studentTask != null && StudyStatus.Solved.equals(studentTask.getStatus())) {
studentTask.setIndex(index);
tasks.add(studentTask);
@@ -10,7 +10,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudyPluginConfigurator;
import com.jetbrains.edu.learning.StudyState;
@@ -24,7 +23,6 @@ import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.StepicUser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,7 +38,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
private final Ref<Boolean> myCheckInProcess;
private final Process myTestProcess;
private final String myCommandLine;
private final String FAILED_CHECK_LAUNCH = "Failed to launch checking";
private static final String FAILED_CHECK_LAUNCH = "Failed to launch checking";
public StudyCheckTask(Project project, StudyState studyState, Ref<Boolean> checkInProcess, Process testProcess, String commandLine) {
super(project, "Checking Task");
@@ -100,7 +98,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
runAfterTaskCheckedActions();
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null && EduNames.STUDY.equals(course.getCourseMode())) {
postAttemptToStepic(testsOutput);
EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), myProject);
}
}
}
@@ -211,12 +209,4 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
LOG.warn("No configurator is provided for the plugin");
}
}
protected void postAttemptToStepic(@NotNull StudyTestsOutputParser.TestsOutput testsOutput) {
final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject);
final StepicUser user = studySettings.getUser();
final String login = user.getEmail();
final String password = StringUtil.isEmptyOrSpaces(login) ? "" : user.getPassword();
EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), login, password);
}
}
@@ -151,7 +151,7 @@ public class Course {
for (Lesson lesson : lessons) {
lesson.setUpdateDate(EduStepicConnector.getLessonUpdateDate(lesson.getId()));
for (Task task : lesson.getTaskList()) {
task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepicId()));
task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepId()));
}
}
}
@@ -93,7 +93,7 @@ public class Lesson implements StudyItem {
public Task getTask(int id) {
for (Task task : taskList) {
if (task.getStepicId() == id) {
if (task.getStepId() == id) {
return task;
}
}
@@ -31,7 +31,7 @@ public class Task implements StudyItem {
private StudyStatus myStatus = StudyStatus.Unchecked;
@SerializedName("stepic_id")
@Expose private int myStepicId;
@Expose private int myStepId;
@SerializedName("task_files")
@Expose public Map<String, TaskFile> taskFiles = new HashMap<>();
@@ -204,12 +204,12 @@ public class Task implements StudyItem {
return result;
}
public void setStepicId(int stepicId) {
myStepicId = stepicId;
public void setStepId(int stepId) {
myStepId = stepId;
}
public int getStepicId() {
return myStepicId;
public int getStepId() {
return myStepId;
}
public StudyStatus getStatus() {
@@ -244,8 +244,8 @@ public class Task implements StudyItem {
}
public boolean isUpToDate() {
if (getStepicId() == 0) return true;
final Date date = EduStepicConnector.getTaskUpdateDate(getStepicId());
if (getStepId() == 0) return true;
final Date date = EduStepicConnector.getTaskUpdateDate(getStepId());
if (date == null) return true;
if (myUpdateDate == null) return false;
return !date.after(myUpdateDate);
@@ -0,0 +1,362 @@
package com.jetbrains.edu.learning.stepic;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
public class CCStepicConnector {
private static final Logger LOG = Logger.getInstance(CCStepicConnector.class.getName());
private CCStepicConnector() {
}
public static CourseInfo getCourseInfo(Project project, String courseId) {
final String url = EduStepicNames.COURSES + "/" + courseId;
try {
final StepicWrappers.CoursesContainer coursesContainer =
EduStepicAuthorizedClient.getFromStepic(url, StepicWrappers.CoursesContainer.class, project);
return coursesContainer.courses.get(0);
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return null;
}
public static void postCourseWithProgress(final Project project, @NotNull final Course course) {
ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
postCourse(project, course, indicator);
}
});
}
private static void postCourse(final Project project, @NotNull Course course, @NotNull final ProgressIndicator indicator) {
indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL);
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses");
final StepicUser currentUser = EduStepicAuthorizedClient.getCurrentUser();
if (currentUser != null) {
final List<StepicUser> courseAuthors = course.getAuthors();
for (int i = 0; i < courseAuthors.size(); i++) {
if (courseAuthors.size() > i) {
final StepicUser courseAuthor = courseAuthors.get(i);
currentUser.setFirstName(courseAuthor.getFirstName());
currentUser.setLastName(courseAuthor.getLastName());
}
}
course.setAuthors(Collections.singletonList(currentUser));
}
String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return;
}
final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
course.setId(postedCourse.id);
final int sectionId = postModule(project, postedCourse.id, 1, String.valueOf(postedCourse.getName()));
int position = 1;
for (Lesson lesson : course.getLessons()) {
indicator.checkCanceled();
final int lessonId = postLesson(project, lesson, indicator);
postUnit(project, lessonId, position, sectionId);
position += 1;
}
ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.id, indicator));
}
catch (IOException e) {
LOG.error(e.getMessage());
}
}
private static void postAdditionalFiles(@NotNull final Project project, int id, ProgressIndicator indicator) {
final VirtualFile baseDir = project.getBaseDir();
final List<VirtualFile> files = VfsUtil.getChildren(baseDir, new VirtualFileFilter() {
@Override
public boolean accept(VirtualFile file) {
final String name = file.getName();
return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) &&
!"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith(".");
}
});
if (!files.isEmpty()) {
final int sectionId = postModule(project, id, 2, EduNames.PYCHARM_ADDITIONAL);
final Lesson lesson = new Lesson();
lesson.setName(EduNames.PYCHARM_ADDITIONAL);
final Task task = new Task();
task.setLesson(lesson);
task.setName(EduNames.PYCHARM_ADDITIONAL);
task.setIndex(1);
task.setText(EduNames.PYCHARM_ADDITIONAL);
for (VirtualFile file : files) {
try {
if (file != null) {
if (EduUtils.isImage(file.getName())) {
task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
}
else {
task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
}
}
}
catch (IOException e) {
LOG.error("Can't find file " + file.getPath());
}
}
lesson.addTask(task);
lesson.setIndex(1);
final int lessonId = postLesson(project, lesson, indicator);
postUnit(project, lessonId, 1, sectionId);
}
}
public static void postUnit(@NotNull final Project project, int lessonId, int position, int sectionId) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.UNITS);
final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
unitWrapper.unit = new StepicWrappers.Unit();
unitWrapper.unit.lesson = lessonId;
unitWrapper.unit.position = position;
unitWrapper.unit.section = sectionId;
String requestBody = new Gson().toJson(unitWrapper);
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
}
private static int postModule(@NotNull final Project project, int courseId, int position, @NotNull final String title) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/sections");
final StepicWrappers.Section section = new StepicWrappers.Section();
section.course = courseId;
section.title = title;
section.position = position;
final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
sectionContainer.section = section;
String requestBody = new Gson().toJson(sectionContainer);
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return -1;
}
final StepicWrappers.Section
postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
return postedSection.id;
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return -1;
}
public static int updateTask(@NotNull final Project project, @NotNull final Task task) {
final Lesson lesson = task.getLesson();
final int lessonId = lesson.getId();
final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + "/step-sources/" + String.valueOf(task.getStepId()));
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
ApplicationManager.getApplication().invokeLater(() -> {
task.addTestsTexts("tests.py", task.getTestsText(project));
final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
final StatusLine line = response.getStatusLine();
if (line.getStatusCode() != HttpStatus.SC_OK) {
LOG.error("Failed to push " + responseString);
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
});
return -1;
}
public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId()));
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_OK) {
LOG.error("Failed to push " + responseString);
return -1;
}
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
for (Integer step : postedLesson.steps) {
deleteTask(step, project);
}
for (Task task : lesson.getTaskList()) {
indicator.checkCanceled();
postTask(project, task, lesson.getId());
}
return lesson.getId();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return -1;
}
public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons");
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return 0;
}
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
lesson.setId(postedLesson.getId());
for (Task task : lesson.getTaskList()) {
indicator.checkCanceled();
postTask(project, task, postedLesson.getId());
}
return postedLesson.getId();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return -1;
}
public static void deleteTask(@NotNull final Integer task, Project project) {
final HttpDelete request = new HttpDelete(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES + task);
ApplicationManager.getApplication().invokeLater(() -> {
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
final StatusLine line = response.getStatusLine();
if (line.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
LOG.error("Failed to delete task " + responseString);
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
});
}
public static void postTask(final Project project, @NotNull final Task task, final int lessonId) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources");
//TODO: register type adapter for task files here?
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
ApplicationManager.getApplication().invokeLater(() -> {
final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final StatusLine line = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return;
}
final JsonObject postedTask = new Gson().fromJson(responseString, JsonObject.class);
final JsonObject stepSource = postedTask.getAsJsonArray("step-sources").get(0).getAsJsonObject();
task.setStepId(stepSource.getAsJsonPrimitive("id").getAsInt());
}
catch (IOException e) {
LOG.error(e.getMessage());
}
});
}
}
@@ -50,12 +50,10 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep;
public class EduAdaptiveStepicConnector {
public static final String PYTHON2 = "python2";
@@ -67,12 +65,11 @@ public class EduAdaptiveStepicConnector {
@Nullable
public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
try {
final CloseableHttpClient client = getHttpClient(project);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final URI uri = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATIONS_URL)
.addParameter(EduNames.COURSE, String.valueOf(course.getId()))
.build();
final HttpGet request = new HttpGet(uri);
setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
setTimeout(request);
final CloseableHttpResponse response = client.execute(request);
@@ -89,7 +86,8 @@ public class EduAdaptiveStepicConnector {
final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
final String lessonId = recommendation.lesson;
final StepicWrappers.LessonContainer
lessonContainer = getFromStepic(EduStepicNames.LESSONS + lessonId, StepicWrappers.LessonContainer.class);
lessonContainer = EduStepicAuthorizedClient
.getFromStepic(EduStepicNames.LESSONS + lessonId, StepicWrappers.LessonContainer.class, project);
if (lessonContainer.lessons.size() == 1) {
final Lesson realLesson = lessonContainer.lessons.get(0);
course.getLessons().get(0).setId(Integer.parseInt(lessonId));
@@ -112,9 +110,6 @@ public class EduAdaptiveStepicConnector {
}
}
else {
if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
return getNextRecommendation(project, course);
}
throw new IOException("Stepic returned non 200 status code: " + responseString);
}
}
@@ -161,8 +156,7 @@ public class EduAdaptiveStepicConnector {
final String json = new Gson()
.toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
final CloseableHttpClient client = getHttpClient(project);
setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
setTimeout(post);
try {
final CloseableHttpResponse execute = client.execute(post);
@@ -174,10 +168,6 @@ public class EduAdaptiveStepicConnector {
return true;
}
else {
if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN)
&& login(project)) {
return postRecommendationReaction(project, lessonId, user, reaction);
}
LOG.warn("Stepic returned non-201 status code: " + statusCode + " " + entityString);
return false;
}
@@ -208,7 +198,7 @@ public class EduAdaptiveStepicConnector {
final String lessonName = EduNames.LESSON + String.valueOf(adaptive.getIndex());
if (reaction == 0 || reaction == -1) {
unsolvedTask.setName(task.getName());
unsolvedTask.setStepicId(task.getStepicId());
unsolvedTask.setStepId(task.getStepId());
unsolvedTask.setText(task.getText());
unsolvedTask.getTestsText().clear();
unsolvedTask.setStatus(StudyStatus.Unchecked);
@@ -318,7 +308,7 @@ public class EduAdaptiveStepicConnector {
private static Task getTaskFromStep(Project project, int lessonID, @NotNull final StepicWrappers.Step step, @NotNull String name) {
final Task task = new Task();
task.setName(name);
task.setStepicId(lessonID);
task.setStepId(lessonID);
task.setText(step.text);
task.setStatus(StudyStatus.Unchecked);
if (step.options.samples != null) {
@@ -398,7 +388,7 @@ public class EduAdaptiveStepicConnector {
final Editor editor = StudyUtils.getSelectedEditor(project);
String language = getLanguageString(task, project);
if (editor != null && language != null) {
final CloseableHttpClient client = getHttpClient(project);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
final StepicUser user = StudyTaskManager.getInstance(project).getUser();
@@ -429,7 +419,6 @@ public class EduAdaptiveStepicConnector {
final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, PYCHARM_COMMENT + text);
final HttpPost httpPost = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS);
setHeaders(httpPost, EduStepicNames.CONTENT_TYPE_APPL_JSON);
setTimeout(httpPost);
try {
httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
@@ -463,7 +452,6 @@ public class EduAdaptiveStepicConnector {
.addParameter("user", String.valueOf(id))
.build();
final HttpGet httpGet = new HttpGet(submissionURI);
setHeaders(httpGet, EduStepicNames.CONTENT_TYPE_APPL_JSON);
setTimeout(httpGet);
final CloseableHttpResponse httpResponse = client.execute(httpGet);
final HttpEntity entity = httpResponse.getEntity();
@@ -506,13 +494,12 @@ public class EduAdaptiveStepicConnector {
}
private static int getAttemptId(@NotNull final Project project, @NotNull Task task) throws IOException {
final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepId());
final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS);
post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
final CloseableHttpClient client = getHttpClient(project);
setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
setTimeout(post);
final CloseableHttpResponse httpResponse = client.execute(post);
final int statusCode = httpResponse.getStatusLine().getStatusCode();
@@ -524,11 +511,6 @@ public class EduAdaptiveStepicConnector {
new Gson().fromJson(entityString, StepicWrappers.AttemptContainer.class);
return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
}
else {
if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
return getAttemptId(project, task);
}
}
return -1;
}
@@ -540,4 +522,27 @@ public class EduAdaptiveStepicConnector {
" check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
task.addTestsTexts("tests.py", testText);
}
@NotNull
public static List<Integer> getEnrolledCoursesIds(StepicUser stepicUser) {
try {
final URI enrolledCoursesUri = new URIBuilder(EduStepicNames.COURSES).addParameter("enrolled", "true").build();
final List<CourseInfo> courses = EduStepicAuthorizedClient.getFromStepic(enrolledCoursesUri.toString(),
StepicWrappers.CoursesContainer.class,
stepicUser).courses;
final ArrayList<Integer> ids = new ArrayList<>();
for (CourseInfo course : courses) {
ids.add(course.getId());
}
return ids;
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
catch (URISyntaxException e) {
LOG.warn(e.getMessage());
}
return Collections.emptyList();
}
}
@@ -0,0 +1,256 @@
package com.jetbrains.edu.learning.stepic;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.net.ssl.CertificateManager;
import com.jetbrains.edu.learning.StudyTaskManager;
import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
public class EduStepicAuthorizedClient {
private static final Logger LOG = Logger.getInstance(EduStepicAuthorizedClient.class.getName());
private static final String ourClientId = "hUCWcq3hZHCmz0DKrDtwOWITLcYutzot7p4n59vU";
private static CloseableHttpClient ourClient;
private EduStepicAuthorizedClient() {
}
@NotNull
public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
if (ourClient != null) {
return ourClient;
}
final StepicUser stepicUser = StudyTaskManager.getInstance(project).getUser();
ourClient = initializeClient(stepicUser);
if (ourClient == null) {
final StepicUser user = login(stepicUser);
if (user != null) {
StudyTaskManager.getInstance(project).setUser(user);
ourClient = initializeClient(stepicUser);
}
}
return ourClient;
}
public static <T> T getFromStepic(String link, final Class<T> container, @NotNull final Project project) throws IOException {
return EduStepicClient.getFromStepic(link, container, getHttpClient(project));
}
/*
* This method should be used only in project generation while project is not available.
* Make sure you saved stepic user in task manager after using this method.
*/
@NotNull
public static CloseableHttpClient getHttpClient(@NotNull final StepicUser stepicUser) {
if (ourClient != null) {
return ourClient;
}
ourClient = initializeClient(stepicUser);
if (ourClient == null) {
final StepicUser user = login(stepicUser);
if (user != null) {
ourClient = initializeClient(stepicUser);
}
}
return ourClient;
}
/*
* This method should be used only in project generation while project is not available.
* Make sure you saved stepic user in task manager after using this method.
*/
public static <T> T getFromStepic(String link, final Class<T> container, @NotNull final StepicUser stepicUser) throws IOException {
return EduStepicClient.getFromStepic(link, container, getHttpClient(stepicUser));
}
@Nullable
private static CloseableHttpClient initializeClient(@NotNull final StepicUser stepicUser) {
final List<BasicHeader> headers = new ArrayList<>();
final String accessToken = stepicUser.getAccessToken();
if (accessToken != null && !accessToken.isEmpty()) {
headers.add(new BasicHeader("Authorization", "Bearer " + accessToken));
headers.add(new BasicHeader("Content-type", EduStepicNames.CONTENT_TYPE_APP_JSON));
return getBuilder().setDefaultHeaders(headers).build();
}
return null;
}
@NotNull
private static TrustManager[] trustAllCerts() {
// Create a trust manager that does not validate certificate for this connection
return new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
}
@NotNull
private static HttpClientBuilder getBuilder() {
final HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).
setMaxConnPerRoute(100000).setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
final HttpConfigurable proxyConfigurable = HttpConfigurable.getInstance();
final List<Proxy> proxies = proxyConfigurable.getOnlyBySettingsSelector().select(URI.create(EduStepicNames.STEPIC_URL));
final InetSocketAddress address = proxies.size() > 0 ? (InetSocketAddress)proxies.get(0).address() : null;
if (address != null) {
builder.setProxy(new HttpHost(address.getHostName(), address.getPort()));
}
final TrustManager[] trustAllCerts = trustAllCerts();
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
builder.setSslcontext(sslContext);
}
catch (NoSuchAlgorithmException | KeyManagementException e) {
LOG.error(e.getMessage());
}
return builder;
}
private static StepicUser login(@NotNull final StepicUser user) {
final String login = user.getEmail();
final String refreshToken = user.getRefreshToken();
if (StringUtil.isEmptyOrSpaces(login)) {
return showLoginDialog();
}
else {
if (StringUtil.isNotEmpty(refreshToken)) {
final StepicWrappers.TokenInfo tokenInfo = login(refreshToken);
if (tokenInfo != null) {
user.setupTokenInfo(tokenInfo);
}
}
else {
final StepicUser stepicUser = login(login, user.getPassword());
if (stepicUser == null) {
return showLoginDialog();
}
return stepicUser;
}
}
return null;
}
private static StepicUser showLoginDialog() {
final StepicUser[] stepicUser = new StepicUser[1];
ApplicationManager.getApplication().invokeAndWait(() -> {
final LoginDialog dialog = new LoginDialog();
dialog.show();
stepicUser[0] = dialog.getStepicUser();
});
return stepicUser[0];
}
public static StepicUser login(@NotNull final String email, @NotNull final String password) {
final List<NameValuePair> parameters = new ArrayList<>();
if (password.isEmpty()) return null;
parameters.add(new BasicNameValuePair("client_id", ourClientId));
parameters.add(new BasicNameValuePair("grant_type", "password"));
parameters.add(new BasicNameValuePair("username", email));
parameters.add(new BasicNameValuePair("password", password));
final StepicWrappers.TokenInfo tokenInfo = postCredentials(parameters);
final StepicUser user = new StepicUser(email, password);
final StepicUser currentUser = getCurrentUser();
if (currentUser != null) {
user.setId(currentUser.getId());
}
if (tokenInfo != null) {
user.setupTokenInfo(tokenInfo);
}
return user;
}
private static StepicWrappers.TokenInfo login(@NotNull final String refreshToken) {
final List<NameValuePair> parameters = new ArrayList<>();
if (refreshToken.isEmpty()) return null;
parameters.add(new BasicNameValuePair("client_id", ourClientId));
parameters.add(new BasicNameValuePair("content-type", "application/json"));
parameters.add(new BasicNameValuePair("grant_type", "refresh_token"));
parameters.add(new BasicNameValuePair("refresh_token", refreshToken));
return postCredentials(parameters);
}
@Nullable
static StepicUser getCurrentUser() {
try {
final StepicWrappers.AuthorWrapper wrapper = EduStepicClient.getFromStepic(EduStepicNames.CURRENT_USER,
StepicWrappers.AuthorWrapper.class);
if (wrapper != null && !wrapper.users.isEmpty()) {
return wrapper.users.get(0);
}
}
catch (IOException e) {
LOG.warn("Couldn't get author info");
}
return null;
}
@Nullable
private static StepicWrappers.TokenInfo postCredentials(@NotNull final List<NameValuePair> parameters) {
final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final HttpPost request = new HttpPost(EduStepicNames.TOKEN_URL);
request.setEntity(new UrlEncodedFormEntity(parameters, Consts.UTF_8));
try {
final CloseableHttpClient client = EduStepicClient.getHttpClient();
final CloseableHttpResponse response = client.execute(request);
final StatusLine statusLine = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
return gson.fromJson(responseString, StepicWrappers.TokenInfo.class);
}
else {
LOG.warn("Failed to Login: " + statusLine.getStatusCode() + statusLine.getReasonPhrase());
}
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
return null;
}
}
@@ -0,0 +1,124 @@
package com.jetbrains.edu.learning.stepic;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.net.ssl.CertificateManager;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.List;
public class EduStepicClient {
private static final Logger LOG = Logger.getInstance(EduStepicClient.class.getName());
private static CloseableHttpClient ourClient;
private EduStepicClient() {
}
@NotNull
public static CloseableHttpClient getHttpClient() {
if (ourClient == null) {
initializeClient();
}
return ourClient;
}
public static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
return getFromStepic(link, container, getHttpClient());
}
static <T> T getFromStepic(String link, final Class<T> container, @NotNull final CloseableHttpClient client) throws IOException {
if (!link.startsWith("/")) link = "/" + link;
final HttpGet request = new HttpGet(EduStepicNames.STEPIC_API_URL + link);
if (ourClient == null) {
initializeClient();
}
final CloseableHttpResponse response = client.execute(request);
final StatusLine statusLine = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
throw new IOException("Stepic returned non 200 status code " + responseString);
}
Gson gson = new GsonBuilder().registerTypeAdapter(TaskFile.class, new StudySerializationUtils.Json.StepicTaskFileAdapter()).
registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).
setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").
setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
return gson.fromJson(responseString, container);
}
private static void initializeClient() {
if (ourClient == null) {
final HttpClientBuilder builder = getBuilder();
ourClient = builder.build();
}
}
@NotNull
private static TrustManager[] trustAllCerts() {
// Create a trust manager that does not validate certificate for this connection
return new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
}
@NotNull
private static HttpClientBuilder getBuilder() {
final HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).
setMaxConnPerRoute(100000).setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
final HttpConfigurable proxyConfigurable = HttpConfigurable.getInstance();
final List<Proxy> proxies = proxyConfigurable.getOnlyBySettingsSelector().select(URI.create(EduStepicNames.STEPIC_URL));
final InetSocketAddress address = proxies.size() > 0 ? (InetSocketAddress)proxies.get(0).address() : null;
if (address != null) {
builder.setProxy(new HttpHost(address.getHostName(), address.getPort()));
}
final TrustManager[] trustAllCerts = trustAllCerts();
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
builder.setSslcontext(sslContext);
}
catch (NoSuchAlgorithmException | KeyManagementException e) {
LOG.error(e.getMessage());
}
return builder;
}
}
@@ -1,280 +1,49 @@
package com.jetbrains.edu.learning.stepic;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.net.ssl.CertificateManager;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import static com.jetbrains.edu.learning.stepic.EduStepicNames.CONTENT_TYPE_APPL_JSON;
public class EduStepicConnector {
private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
private static String ourCSRFToken = "";
private static CloseableHttpClient ourClient;
//this prefix indicates that course can be opened by educational plugin
public static final String PYCHARM_PREFIX = "pycharm";
private static BasicCookieStore ourCookieStore;
private static final String ADAPTIVE_NOTE =
"\n\nInitially, the adaptive system may behave somewhat randomly, but the more problems you solve, the smarter it become!";
private EduStepicConnector() {
}
public static StepicUser login(@NotNull final String username, @NotNull final String password) {
initializeClient();
if (postCredentials(username, password)) {
final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser();
if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) {
return stepicUserWrapper.users.get(0);
}
}
return null;
}
@NotNull
public static List<Integer> getEnrolledCoursesIds() {
try {
final URI enrolledCoursesUri = new URIBuilder(EduStepicNames.COURSES).addParameter("enrolled", "true").build();
final List<CourseInfo> courses = getFromStepic(enrolledCoursesUri.toString(), StepicWrappers.CoursesContainer.class).courses;
final ArrayList<Integer> ids = new ArrayList<>();
for (CourseInfo course : courses) {
ids.add(course.getId());
}
return ids;
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
catch (URISyntaxException e) {
LOG.warn(e.getMessage());
}
return Collections.emptyList();
}
@Nullable
public static StepicWrappers.AuthorWrapper getCurrentUser() {
try {
return getFromStepic(EduStepicNames.CURRENT_USER, StepicWrappers.AuthorWrapper.class);
}
catch (IOException e) {
LOG.warn("Couldn't get author info");
}
return null;
}
public static boolean createUser(@NotNull final String user, @NotNull final String password) {
final HttpPost userRequest = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.USERS);
initializeClient();
setHeaders(userRequest, CONTENT_TYPE_APPL_JSON);
String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password));
userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(userRequest);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine statusLine = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to create user " + responseString);
return false;
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return true;
}
public static void initializeClient() {
if (ourClient == null) {
final HttpGet request = new HttpGet(EduStepicNames.STEPIC_URL);
setHeaders(request, CONTENT_TYPE_APPL_JSON);
HttpClientBuilder builder =
HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
final HttpConfigurable proxyConfigurable = HttpConfigurable.getInstance();
final List<Proxy> proxies = proxyConfigurable.getOnlyBySettingsSelector().select(URI.create(EduStepicNames.STEPIC_URL));
final InetSocketAddress address = proxies.size() > 0 ? (InetSocketAddress)proxies.get(0).address() : null;
if (address != null) {
builder.setProxy(new HttpHost(address.getHostName(), address.getPort()));
}
ourCookieStore = new BasicCookieStore();
try {
// Create a trust manager that does not validate certificate for this connection
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
ourClient.execute(request);
saveCSRFToken();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
catch (NoSuchAlgorithmException e) {
LOG.error(e.getMessage());
}
catch (KeyManagementException e) {
LOG.error(e.getMessage());
}
}
}
private static void saveCSRFToken() {
if (ourCookieStore == null) return;
final List<Cookie> cookies = ourCookieStore.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals("csrftoken")) {
ourCSRFToken = cookie.getValue();
}
}
}
private static boolean postCredentials(String user, String password) {
String url = EduStepicNames.STEPIC_URL + EduStepicNames.LOGIN;
final HttpPost request = new HttpPost(url);
List <NameValuePair> nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("csrfmiddlewaretoken", ourCSRFToken));
nvps.add(new BasicNameValuePair("login", user));
nvps.add(new BasicNameValuePair("next", "/"));
nvps.add(new BasicNameValuePair("password", password));
nvps.add(new BasicNameValuePair("remember", "on"));
request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
setHeaders(request, "application/x-www-form-urlencoded");
try {
final CloseableHttpResponse response = ourClient.execute(request);
saveCSRFToken();
final StatusLine line = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase());
LOG.debug("Failed to login " + responseString);
ourClient = null;
return false;
}
}
catch (IOException e) {
LOG.warn(e.getMessage());
ourClient = null;
return false;
}
return true;
}
static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
if (!link.startsWith("/")) link = "/" + link;
final HttpGet request = new HttpGet(EduStepicNames.STEPIC_API_URL + link);
if (ourClient == null) {
initializeClient();
}
setHeaders(request, CONTENT_TYPE_APPL_JSON);
final CloseableHttpResponse response = ourClient.execute(request);
final StatusLine statusLine = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
throw new IOException("Stepic returned non 200 status code " + responseString);
}
Gson gson = new GsonBuilder().registerTypeAdapter(TaskFile.class, new StudySerializationUtils.Json.StepicTaskFileAdapter()).
registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).
setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").
setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
return gson.fromJson(responseString, container);
}
@NotNull
public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
if (ourClient == null) {
login(project);
}
return ourClient;
}
public static boolean enrollToCourse(final int courseId) {
public static boolean enrollToCourse(final int courseId, final StepicUser stepicUser) {
HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ENROLLMENTS);
try {
final StepicWrappers.EnrollmentWrapper enrollment = new StepicWrappers.EnrollmentWrapper(String.valueOf(courseId));
post.setEntity(new StringEntity(new GsonBuilder().create().toJson(enrollment)));
setHeaders(post, CONTENT_TYPE_APPL_JSON);
if (ourClient == null) {
initializeClient();
}
CloseableHttpResponse response = ourClient.execute(post);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(stepicUser);
CloseableHttpResponse response = client.execute(post);
StatusLine line = response.getStatusLine();
return line.getStatusCode() == HttpStatus.SC_CREATED;
}
@@ -300,23 +69,10 @@ public class EduStepicConnector {
return Collections.singletonList(CourseInfo.INVALID_COURSE);
}
public static CourseInfo getCourseInfo(Project project, String courseId) {
final String url = EduStepicNames.COURSES + "/" + courseId;
try {
login(project);
final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class);
return coursesContainer.courses.get(0);
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return null;
}
public static Date getCourseUpdateDate(final int courseId) {
final String url = EduStepicNames.COURSES + "/" + courseId;
try {
final List<CourseInfo> courses = getFromStepic(url, StepicWrappers.CoursesContainer.class).courses;
final List<CourseInfo> courses = EduStepicClient.getFromStepic(url, StepicWrappers.CoursesContainer.class).courses;
if (!courses.isEmpty()) {
return courses.get(0).getUpdateDate();
}
@@ -331,7 +87,7 @@ public class EduStepicConnector {
public static Date getLessonUpdateDate(final int lessonId) {
final String url = EduStepicNames.LESSONS + "/" + lessonId;
try {
List<Lesson> lessons = getFromStepic(url, StepicWrappers.LessonContainer.class).lessons;
List<Lesson> lessons = EduStepicClient.getFromStepic(url, StepicWrappers.LessonContainer.class).lessons;
if (!lessons.isEmpty()) {
return lessons.get(0).getUpdateDate();
}
@@ -346,7 +102,7 @@ public class EduStepicConnector {
public static Date getTaskUpdateDate(final int taskId) {
final String url = EduStepicNames.STEPS + "/" + String.valueOf(taskId);
try {
List<StepicWrappers.StepSource> steps = getFromStepic(url, StepicWrappers.StepContainer.class).steps;
List<StepicWrappers.StepSource> steps = EduStepicClient.getFromStepic(url, StepicWrappers.StepContainer.class).steps;
if (!steps.isEmpty()) {
return steps.get(0).update_date;
}
@@ -368,7 +124,7 @@ public class EduStepicConnector {
LOG.error(e.getMessage());
return false;
}
final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class);
final StepicWrappers.CoursesContainer coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class);
final List<CourseInfo> courseInfos = coursesContainer.courses;
for (CourseInfo info : courseInfos) {
final String courseType = info.getType();
@@ -376,7 +132,7 @@ public class EduStepicConnector {
final List<String> typeLanguage = StringUtil.split(courseType, " ");
if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) {
for (Integer instructor : info.instructors) {
final StepicUser author = getFromStepic(EduStepicNames.USERS + "/" + String.valueOf(instructor),
final StepicUser author = EduStepicClient.getFromStepic(EduStepicNames.USERS + "/" + String.valueOf(instructor),
StepicWrappers.AuthorWrapper.class).users.get(0);
info.addAuthor(author);
}
@@ -433,15 +189,15 @@ public class EduStepicConnector {
public static List<Lesson> getLessons(int sectionId) throws IOException {
final StepicWrappers.SectionContainer
sectionContainer = getFromStepic(EduStepicNames.SECTIONS + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
sectionContainer = EduStepicClient.getFromStepic(EduStepicNames.SECTIONS + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
List<Integer> unitIds = sectionContainer.sections.get(0).units;
final List<Lesson> lessons = new ArrayList<>();
for (Integer unitId : unitIds) {
StepicWrappers.UnitContainer
unit = getFromStepic(EduStepicNames.UNITS + "/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
unit = EduStepicClient.getFromStepic(EduStepicNames.UNITS + "/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
int lessonID = unit.units.get(0).lesson;
StepicWrappers.LessonContainer
lessonContainer = getFromStepic(EduStepicNames.LESSONS + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
lessonContainer = EduStepicClient.getFromStepic(EduStepicNames.LESSONS + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
Lesson lesson = lessonContainer.lessons.get(0);
lesson.taskList = new ArrayList<>();
for (Integer s : lesson.steps) {
@@ -459,7 +215,7 @@ public class EduStepicConnector {
final StepicWrappers.Step block = step.block;
if (!block.name.equals(PYCHARM_PREFIX)) return;
final Task task = new Task();
task.setStepicId(stepicId);
task.setStepId(stepicId);
task.setUpdateDate(step.update_date);
task.setName(block.options != null ? block.options.title : PYCHARM_PREFIX);
task.setText(block.text);
@@ -477,53 +233,25 @@ public class EduStepicConnector {
}
public static StepicWrappers.StepSource getStep(Integer step) throws IOException {
return getFromStepic(EduStepicNames.STEPS + "/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0);
return EduStepicClient.getFromStepic(EduStepicNames.STEPS + "/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0);
}
public static boolean showLoginDialog() {
final boolean[] logged = {false};
ApplicationManager.getApplication().invokeAndWait(() -> {
final LoginDialog dialog = new LoginDialog();
dialog.show();
logged[0] = dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE;
});
return logged[0];
}
public static void postAttempt(@NotNull final Task task, boolean passed, @Nullable String login, @Nullable String password) {
if (task.getStepicId() <= 0) {
public static void postAttempt(@NotNull final Task task, boolean passed, @NotNull final Project project) {
if (task.getStepId() <= 0) {
return;
}
if (ourClient == null) {
if (StringUtil.isEmptyOrSpaces(login) || StringUtil.isEmptyOrSpaces(password)) {
return;
}
else {
if (login(login, password) == null) return;
}
}
final HttpPost attemptRequest = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS);
setHeaders(attemptRequest, CONTENT_TYPE_APPL_JSON);
String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId()));
String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepId()));
attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse attemptResponse = ourClient.execute(attemptRequest);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse attemptResponse = client.execute(attemptRequest);
final HttpEntity responseEntity = attemptResponse.getEntity();
final String attemptResponseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine statusLine = attemptResponse.getStatusLine();
EntityUtils.consume(responseEntity);
if (statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
if (StringUtil.isEmptyOrSpaces(login) || StringUtil.isEmptyOrSpaces(password)) {
return;
}
else {
login(login, password);
postAttempt(task, passed, login, password);
}
}
if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.warn("Failed to make attempt " + attemptResponseString);
}
@@ -534,20 +262,21 @@ public class EduStepicConnector {
for (TaskFile fileEntry : taskFiles.values()) {
files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text));
}
postSubmission(passed, attempt, files);
postSubmission(passed, attempt, project, files);
}
catch (IOException e) {
LOG.error(e.getMessage());
}
}
private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt,
Project project, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS);
setHeaders(request, CONTENT_TYPE_APPL_JSON);
String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
final CloseableHttpResponse response = ourClient.execute(request);
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
final CloseableHttpResponse response = client.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
@@ -556,383 +285,4 @@ public class EduStepicConnector {
LOG.error("Failed to make submission " + responseString);
}
}
public static void postCourseWithProgress(final Project project, @NotNull final Course course) {
postCourseWithProgress(project, course, false);
}
public static void postCourseWithProgress(final Project project, @NotNull final Course course, final boolean relogin) {
ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
postCourse(project, course, relogin, indicator);
}
});
}
private static void postCourse(final Project project, @NotNull Course course, boolean relogin, @NotNull final ProgressIndicator indicator) {
indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL);
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses");
if (ourClient == null || !relogin) {
if (!login(project)) return;
}
final StepicWrappers.AuthorWrapper authors = getCurrentUser();
if (authors != null) {
final List<StepicUser> courseAuthors = course.getAuthors();
for (int i = 0; i < courseAuthors.size(); i++) {
final StepicUser user = authors.users.get(i);
if (courseAuthors.size() > i) {
final StepicUser courseAuthor = courseAuthors.get(i);
user.setFirstName(courseAuthor.getFirstName());
user.setLastName(courseAuthor.getLastName());
}
}
course.setAuthors(authors.users);
}
setHeaders(request, CONTENT_TYPE_APPL_JSON);
String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
if (!relogin) {
login(project);
postCourse(project, course, true, indicator);
}
LOG.error("Failed to push " + responseString);
return;
}
final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
course.setId(postedCourse.id);
final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName()));
int position = 1;
for (Lesson lesson : course.getLessons()) {
indicator.checkCanceled();
final int lessonId = postLesson(project, lesson, indicator);
postUnit(lessonId, position, sectionId);
position += 1;
}
ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.id, indicator));
}
catch (IOException e) {
LOG.error(e.getMessage());
}
}
static boolean login(@NotNull final Project project) {
final StepicUser user = StudyTaskManager.getInstance(project).getUser();
final String login = user.getEmail();
if (StringUtil.isEmptyOrSpaces(login)) {
return showLoginDialog();
}
else {
if (login(login, user.getPassword()) == null) {
return showLoginDialog();
}
}
return true;
}
private static void postAdditionalFiles(@NotNull final Project project, int id, ProgressIndicator indicator) {
final VirtualFile baseDir = project.getBaseDir();
final List<VirtualFile> files = VfsUtil.getChildren(baseDir, new VirtualFileFilter() {
@Override
public boolean accept(VirtualFile file) {
final String name = file.getName();
return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) &&
!"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith("");
}
});
if (!files.isEmpty()) {
final int sectionId = postModule(id, 2, EduNames.PYCHARM_ADDITIONAL);
final Lesson lesson = new Lesson();
lesson.setName(EduNames.PYCHARM_ADDITIONAL);
final Task task = new Task();
task.setLesson(lesson);
task.setName(EduNames.PYCHARM_ADDITIONAL);
task.setIndex(1);
task.setText(EduNames.PYCHARM_ADDITIONAL);
for (VirtualFile file : files) {
try {
if (file != null) {
if (EduUtils.isImage(file.getName())) {
task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
}
else {
task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
}
}
}
catch (IOException e) {
LOG.error("Can't find file " + file.getPath());
}
}
lesson.addTask(task);
lesson.setIndex(1);
final int lessonId = postLesson(project, lesson, indicator);
postUnit(lessonId, 1, sectionId);
}
}
public static void postUnit(int lessonId, int position, int sectionId) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.UNITS);
setHeaders(request, CONTENT_TYPE_APPL_JSON);
final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
unitWrapper.unit = new StepicWrappers.Unit();
unitWrapper.unit.lesson = lessonId;
unitWrapper.unit.position = position;
unitWrapper.unit.section = sectionId;
String requestBody = new Gson().toJson(unitWrapper);
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
}
private static int postModule(int courseId, int position, @NotNull final String title) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/sections");
setHeaders(request, CONTENT_TYPE_APPL_JSON);
final StepicWrappers.Section section = new StepicWrappers.Section();
section.course = courseId;
section.title = title;
section.position = position;
final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
sectionContainer.section = section;
String requestBody = new Gson().toJson(sectionContainer);
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return -1;
}
final StepicWrappers.Section
postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
return postedSection.id;
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return -1;
}
public static int updateTask(@NotNull final Project project, @NotNull final Task task) {
final Lesson lesson = task.getLesson();
final int lessonId = lesson.getId();
if (ourClient == null) {
if (!login(project)) {
LOG.error("Failed to update task");
return -1;
}
}
final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + "/step-sources/" + String.valueOf(task.getStepicId()));
setHeaders(request, CONTENT_TYPE_APPL_JSON);
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
ApplicationManager.getApplication().invokeLater(() -> {
final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
final StatusLine line = response.getStatusLine();
if (line.getStatusCode() == HttpStatus.SC_FORBIDDEN) {
if (login(project)) {
updateTask(project, task);
return;
}
}
if (line.getStatusCode() != HttpStatus.SC_OK) {
LOG.error("Failed to push " + responseString);
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
});
return -1;
}
public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId()));
if (ourClient == null) {
if (!login(project)) {
LOG.error("Failed to push lesson");
return -1;
}
}
setHeaders(request, CONTENT_TYPE_APPL_JSON);
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() == HttpStatus.SC_FORBIDDEN) {
if (login(project)) {
return updateLesson(project, lesson, indicator);
}
}
if (line.getStatusCode() != HttpStatus.SC_OK) {
LOG.error("Failed to push " + responseString);
return -1;
}
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
for (Integer step : postedLesson.steps) {
deleteTask(step);
}
for (Task task : lesson.getTaskList()) {
indicator.checkCanceled();
postTask(project, task, lesson.getId());
}
return lesson.getId();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return -1;
}
public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons");
if (ourClient == null) {
login(project);
}
setHeaders(request, CONTENT_TYPE_APPL_JSON);
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
final StatusLine line = response.getStatusLine();
EntityUtils.consume(responseEntity);
if (line.getStatusCode() == HttpStatus.SC_FORBIDDEN) {
if (login(project)) {
return postLesson(project, lesson, indicator);
}
}
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return 0;
}
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
lesson.setId(postedLesson.getId());
for (Task task : lesson.getTaskList()) {
indicator.checkCanceled();
postTask(project, task, postedLesson.getId());
}
return postedLesson.getId();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
return -1;
}
public static void deleteTask(@NotNull final Integer task) {
final HttpDelete request = new HttpDelete(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES + task);
setHeaders(request, CONTENT_TYPE_APPL_JSON);
ApplicationManager.getApplication().invokeLater(() -> {
try {
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
final StatusLine line = response.getStatusLine();
if (line.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
LOG.error("Failed to delete task " + responseString);
}
}
catch (IOException e) {
LOG.error(e.getMessage());
}
});
}
public static void postTask(final Project project, @NotNull final Task task, final int lessonId) {
if (ourClient == null) {
if (!login(project)) {
LOG.error("Failed to update task");
}
}
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources");
setHeaders(request, CONTENT_TYPE_APPL_JSON);
//TODO: register type adapter for task files here?
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
ApplicationManager.getApplication().invokeLater(() -> {
final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
final CloseableHttpResponse response = ourClient.execute(request);
final StatusLine line = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
EntityUtils.consume(responseEntity);
if (line.getStatusCode() == HttpStatus.SC_FORBIDDEN) {
if (login(project)) {
postTask(project, task, lessonId);
return;
}
}
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
return;
}
final JsonObject postedTask = new Gson().fromJson(responseString, JsonObject.class);
final JsonObject stepSource = postedTask.getAsJsonArray("step-sources").get(0).getAsJsonObject();
task.setStepicId(stepSource.getAsJsonPrimitive("id").getAsInt());
}
catch (IOException e) {
LOG.error(e.getMessage());
}
});
}
static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
request.addHeader(new BasicHeader("referer", EduStepicNames.STEPIC_URL));
request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken));
request.addHeader(new BasicHeader("content-type", contentType));
}
}
@@ -2,9 +2,11 @@ package com.jetbrains.edu.learning.stepic;
public class EduStepicNames {
public static final String STEPIC_URL = "https://stepic.org";
public static final String TOKEN_URL = STEPIC_URL + "/oauth2/token/";
public static final String STEPIC_API_URL = STEPIC_URL + "/api";
public static final String RECOMMENDATIONS_URL = "/recommendations";
public static final String CONTENT_TYPE_APPL_JSON = "application/json";
public static final String CONTENT_TYPE_APP_JSON = "application/json";
public static final String LESSONS = "/lessons/";
public static final String RECOMMENDATION_REACTIONS_URL = "/recommendation-reactions";
public static final String ATTEMPTS = "/attempts";
@@ -1,10 +1,7 @@
package com.jetbrains.edu.learning.stepic;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.ui.LoginPanel;
import org.jetbrains.annotations.NotNull;
@@ -12,6 +9,7 @@ import javax.swing.*;
public class LoginDialog extends DialogWrapper {
protected final LoginPanel myLoginPanel;
private StepicUser myStepicUser;
public LoginDialog() {
super(false);
@@ -44,13 +42,9 @@ public class LoginDialog extends DialogWrapper {
@Override
protected void doOKAction() {
if (!validateLoginAndPasswordFields()) return;
final StepicUser user = EduStepicConnector.login(myLoginPanel.getLogin(), myLoginPanel.getPassword());
if (user != null) {
myStepicUser = EduStepicAuthorizedClient.login(myLoginPanel.getLogin(), myLoginPanel.getPassword());
if (myStepicUser != null) {
doJustOkAction();
final Project project = ProjectUtil.guessCurrentProject(myLoginPanel.getContentPanel());
user.setEmail(myLoginPanel.getLogin());
user.setPassword(myLoginPanel.getPassword());
StudyTaskManager.getInstance(project).setUser(user);
}
else {
setErrorText("Login failed");
@@ -76,4 +70,8 @@ public class LoginDialog extends DialogWrapper {
public void clearErrors() {
setErrorText(null);
}
public StepicUser getStepicUser() {
return myStepicUser;
}
}
@@ -158,7 +158,7 @@ public class StepicAdaptiveReactionsPanel extends JPanel {
public void run(@NotNull ProgressIndicator indicator) {
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false);
ApplicationManager.getApplication().invokeLater(()->setBackground(UIUtil.getLabelBackground()));
EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, myReaction, indicator);
EduAdaptiveStepicConnector.addNextRecommendedTask(StepicAdaptiveReactionsPanel.this.myProject, myReaction, indicator);
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true);
}
});
@@ -151,7 +151,10 @@ public class StepicStudyOptions implements StudyOptionsProvider {
user.setPassword(password);
// login to post credentials
EduStepicConnector.login(login, password);
final StepicUser stepicUser = EduStepicAuthorizedClient.login(login, password);
if (stepicUser != null) {
taskManager.setUser(stepicUser);
}
}
}
else {
@@ -12,6 +12,8 @@ public class StepicUser {
private String myFirstName;
private String myLastName;
private String myEmail;
private String myAccessToken;
private String myRefreshToken;
public StepicUser() {
myFirstName = "";
@@ -74,4 +76,25 @@ public class StepicUser {
public String getName() {
return StringUtil.join(new String[]{myFirstName, myLastName}, " ");
}
public String getAccessToken() {
return myAccessToken;
}
public void setAccessToken(String accessToken) {
this.myAccessToken = accessToken;
}
public String getRefreshToken() {
return myRefreshToken;
}
public void setRefreshToken(String refreshToken) {
this.myRefreshToken = refreshToken;
}
public void setupTokenInfo(@NotNull final StepicWrappers.TokenInfo tokenInfo) {
myAccessToken = tokenInfo.getAccessToken();
myRefreshToken = tokenInfo.getRefreshToken();
}
}
@@ -119,12 +119,12 @@ public class StepicWrappers {
String python27;
@Nullable
public String getTemplateForLanguage(@NotNull final String langauge) {
if (langauge.equals(EduAdaptiveStepicConnector.PYTHON2)) {
public String getTemplateForLanguage(@NotNull final String language) {
if (language.equals(EduAdaptiveStepicConnector.PYTHON2)) {
return python27;
}
if (langauge.equals(EduAdaptiveStepicConnector.PYTHON3)) {
if (language.equals(EduAdaptiveStepicConnector.PYTHON3)) {
return python3;
}
@@ -435,4 +435,25 @@ public class StepicWrappers {
enrollment = new Enrollment(courseId);
}
}
static class TokenInfo {
@Expose String accessToken;
@Expose String refreshToken;
@Expose String tokenType;
@Expose String scope;
@Expose int expiresIn;
public TokenInfo() {
accessToken = "";
refreshToken = "";
}
public String getAccessToken() {
return accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
}
}
@@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.List;
public class CCCreateAnswerPlaceholderPanel {
private static String ourFirstHintText = "Type here to add hint";
private static final String ourFirstHintText = "Type here to add hint";
private JPanel myPanel;
private JTextArea myHintTextArea;
@@ -29,10 +29,7 @@ import com.intellij.util.ui.UIUtil;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.LoginDialog;
import com.jetbrains.edu.learning.stepic.StepicUser;
import com.jetbrains.edu.learning.stepic.*;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -344,14 +341,13 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicConnector.login(myLoginPanel.getLogin(),
myLoginPanel.getPassword()));
final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicAuthorizedClient.login(myLoginPanel.getLogin(),
myLoginPanel.getPassword()));
if (stepicUser != null) {
stepicUser.setEmail(myLoginPanel.getLogin());
stepicUser.setPassword(myLoginPanel.getPassword());
myGenerator.myUser = stepicUser;
myGenerator.setEnrolledCoursesIds(EduStepicConnector.getEnrolledCoursesIds());
myGenerator.setEnrolledCoursesIds(EduAdaptiveStepicConnector.getEnrolledCoursesIds(stepicUser));
final List<CourseInfo> courses = myGenerator.getCourses(true);
if (courses != null && myRefreshCourseList) {
ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses));
@@ -24,7 +24,7 @@ public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware {
final Course course = taskManager.getCourse();
if (course != null) {
final StudyToolWindow studyToolWindow;
if (StudyUtils.hasJavaFx() && StudyTaskManager.getInstance(project).shouldUseJavaFx()) {
if (StudyUtils.hasJavaFx() && taskManager.shouldUseJavaFx()) {
studyToolWindow = new StudyJavaFxToolWindow();
}
else {
@@ -183,7 +183,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
if (course.isAdaptive() && !enrolledCoursesIds.contains(course.getId())) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(course.getId()));
return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(course.getId(), myGenerator.myUser));
}, "Creating Course", true, ProjectManager.getInstance().getDefaultProject());
}
@@ -25,6 +25,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.Pair;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
@@ -115,18 +116,24 @@ public class PyPep8NamingInspection extends PyInspection {
final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, true, PyClass.class);
if (function == null) return;
final Scope scope = ControlFlowCache.getScope(function);
for (PyExpression expression : node.getTargets()) {
final String name = expression.getName();
for (Pair<PyExpression, PyExpression> pair : node.getTargetsToValuesMapping()) {
final String name = pair.getFirst().getName();
if (name == null || scope.isGlobal(name)) continue;
if (expression instanceof PyTargetExpression) {
final PyExpression qualifier = ((PyTargetExpression)expression).getQualifier();
if (pair.getFirst() instanceof PyTargetExpression) {
final PyExpression qualifier = ((PyTargetExpression)pair.getFirst()).getQualifier();
if (qualifier != null) {
return;
}
}
final PyCallExpression assignedValue = PyUtil.as(pair.getSecond(), PyCallExpression.class);
if (assignedValue != null
&& assignedValue.getCallee() != null && PyNames.NAMEDTUPLE.equals(assignedValue.getCallee().getName())) {
return;
}
final String errorCode = "N806";
if (!LOWERCASE_REGEX.matcher(name).matches() && !name.startsWith("_") && !ignoredErrors.contains(errorCode)) {
registerAndAddRenameAndIgnoreErrorQuickFixes(expression, errorCode);
registerAndAddRenameAndIgnoreErrorQuickFixes(pair.getFirst(), errorCode);
}
}
}
@@ -0,0 +1,5 @@
from collections import namedtuple
def f():
Point = namedtuple("Point", ["x1", "x2"], verbose=True)
<weak_warning descr="Variable in function should be lowercase">Test</weak_warning> = "foo"
@@ -112,6 +112,10 @@ public class PyPep8NamingInspectionTest extends PyTestCase {
doTest();
}
public void testNamedTupleInsideFunction() {
doTest();
}
public void testOverrideFromModule() {
myFixture.configureByFiles("inspections/PyPep8NamingInspection/" + getTestName(true) + ".py",
"inspections/PyPep8NamingInspection/tmp1.py");
@@ -103,7 +103,7 @@ statistics.warnings.count={0} {0,choice, 0#warnings|1#warning|2#warnings}
compiler.running.dialog.title=Compiler Running
warning.compiler.running.on.project.close=The compiler is running. Proceed with project closing?
warning.compiler.running.on.toolwindow.close=The compiler is running. Terminate it?
add.notnull.assertions=Add @NotNull &assertions
add.notnull.assertions=Add runtime &assertions for not-null-annotated methods and parameters
compiler.eclipse.name=Eclipse
eclipse.options.group.title=Eclipse Options