├── msdn ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── import.py ├── migrations │ ├── __init__.py │ ├── 0002_file_language.py │ └── 0001_initial.py ├── admin.py ├── tests.py ├── apps.py ├── templates │ ├── 404.html │ ├── msdn │ │ ├── group_list.html │ │ ├── group_detail.html │ │ ├── family_list.html │ │ ├── family_detail.html │ │ ├── about.html │ │ ├── file_detail.html │ │ ├── index.html │ │ └── search_result.html │ └── base.html ├── context_processors.py ├── models.py ├── util │ └── grab.py └── views.py ├── msdnhash ├── __init__.py ├── settings_private.example.py ├── wsgi.py ├── urls.py └── settings.py ├── requirements.txt ├── LICENSE ├── README.md ├── manage.py └── .gitignore /msdn/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /msdnhash/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /msdn/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /msdn/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /msdn/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.28 2 | requests==2.25.1 -------------------------------------------------------------------------------- /msdn/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /msdn/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /msdn/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MsdnConfig(AppConfig): 5 | name = 'msdn' 6 | -------------------------------------------------------------------------------- /msdn/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

404

6 |

Nothing is here.

7 |
8 |
9 | {% endblock content %} -------------------------------------------------------------------------------- /msdn/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def google_analytics(request): 5 | analytics_code = None 6 | if hasattr(settings, 'GOOGLE_ANALYTICS_CODE'): 7 | analytics_code = settings.GOOGLE_ANALYTICS_CODE 8 | return {'GOOGLE_ANALYTICS_CODE': analytics_code} -------------------------------------------------------------------------------- /msdnhash/settings_private.example.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 | 5 | debug = True 6 | allowed_hosts = [] 7 | secret = 'myverysecretkey' 8 | database = { 9 | 'ENGINE': 'django.db.backends.sqlite3', 10 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3') 11 | } -------------------------------------------------------------------------------- /msdnhash/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for msdnhash project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "msdnhash.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Maurice Wahba 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /msdn/migrations/0002_file_language.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-07-06 01:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('msdn', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='file', 18 | name='language', 19 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='msdn.Language'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dnhash 2 | ============= 3 | In June 2017, the MSDN subscriber portal was "taken offline", with the new Visual Studio portal only being open to subscribers. 4 | While it worked for a few months after showing/hiding some DOM elements, it was finally completely removed in October 2017, with Visual Studio Subscriptions being its replacement. 5 | This project aims to preserve the file catalogs of Microsoft's software subscription services for reference purposes. 6 | 7 | ### Instructions 8 | It's mostly your typical Django project. 9 | Copy `settings_private.example.py` to `settings_private.py` and put your configuration in there. 10 | 11 | ### VS Subscriptions support 12 | Support for Visual Studio Subscriptions files may come sometime in 2021, making this a complete reference for new software going forward. 13 | -------------------------------------------------------------------------------- /msdn/templates/msdn/group_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Browse groups | {% endblock %} 3 | {% block content %} 4 |
5 |

Browse groups

6 |
7 |

8 | All Products 9 |

Browse {{all_family_count}} products by starting letter
10 | 11 |
12 | {% for g in groups %} 13 |
14 |

15 | {{g.name}} 16 |

17 |
{{g.productfamily__count}} products in this group
18 |
19 | {% endfor %} 20 |
21 | {% endblock content %} -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "msdnhash.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /msdn/templates/msdn/group_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}{{group.name}} | {% endblock %} 3 | {% block content %} 4 |
5 |

{{group.name}}

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for f in families %} 15 | 16 | 19 | 22 | 25 | 26 | {% endfor %} 27 | 28 |
Product name# of filesLast file posted
17 | {{f.name}} 18 | 20 | {{f.file__count}} 21 | 23 | {{f.file__posted_date__max | date:'Y-m-d H:i'}} 24 |
29 |
30 | {% endblock content %} -------------------------------------------------------------------------------- /msdnhash/urls.py: -------------------------------------------------------------------------------- 1 | """msdnhash URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | from msdn import views 20 | 21 | urlpatterns = [ 22 | path('', views.index, name='index'), 23 | path('about', views.about, name='about'), 24 | path('browse', views.browse_groups, name='browse'), 25 | path('search', views.search_result, name='search_result'), 26 | path('groups/', views.group_detail, name='group_detail'), 27 | path('families/', views.family_list, name='family_list'), 28 | path('families/', views.family_detail, name='family_detail'), 29 | path('files/', views.file_detail, name='file_detail'), 30 | path('admin/', admin.site.urls), 31 | ] 32 | -------------------------------------------------------------------------------- /msdn/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | class Language(models.Model): 6 | code = models.CharField(max_length=4, unique=True) 7 | name = models.CharField(max_length=64) 8 | 9 | def __str__(self): 10 | return self.name 11 | 12 | class ProductGroup(models.Model): 13 | id = models.IntegerField(primary_key=True) 14 | name = models.CharField(max_length=64) 15 | 16 | def __str__(self): 17 | return self.name 18 | 19 | class ProductFamily(models.Model): 20 | id = models.IntegerField(primary_key=True) 21 | name = models.CharField(max_length=255) 22 | group = models.ForeignKey('ProductGroup', on_delete=models.CASCADE) 23 | 24 | def __str__(self): 25 | return self.name 26 | 27 | class File(models.Model): 28 | id = models.IntegerField(primary_key=True) 29 | product_family = models.ForeignKey('ProductFamily', on_delete=models.CASCADE) 30 | file_name = models.CharField(max_length=1024) 31 | sha1_hash = models.CharField(max_length=40) 32 | description = models.CharField(max_length=1024) 33 | language = models.ForeignKey('Language', null=True, on_delete=models.CASCADE) 34 | notes = models.TextField(null=True) 35 | posted_date = models.DateTimeField() 36 | size = models.DecimalField(max_digits=12, decimal_places=3) 37 | product_key_required = models.BooleanField() 38 | 39 | def __str__(self): 40 | return self.description 41 | -------------------------------------------------------------------------------- /msdn/templates/msdn/family_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}All Products | {% endblock %} 3 | {% block content %} 4 |
5 |

6 | All Products 7 | starting with {{first_letter}} 8 |

9 | 10 |
11 | 16 |
17 |
18 | {% if not families %} 19 |

Nothing found.

20 | {% else %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for f in families %} 31 | 32 | 35 | 38 | 41 | 42 | {% endfor %} 43 | 44 |
Product nameGroup# of files
33 | {{f.name}} 34 | 36 | {{f.group.name}} 37 | 39 | {{f.file__count}} 40 |
45 | {% endif %} 46 |
47 | {% endblock content %} -------------------------------------------------------------------------------- /msdn/templates/msdn/family_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}{{family.name}} | {% endblock %} 3 | {% block content %} 4 |
5 | 8 | 9 |
10 |
11 |

{{family.name}}

12 |
13 |
14 |
15 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% for file in files %} 35 | 36 | 39 | 40 | 41 | 42 | {% endfor %} 43 | 44 |
NamePosted DateSize
37 | {{file.description}} 38 | {{file.posted_date | date:'Y-m-d H:i'}}{{file.size | floatformat:'-3'}} MB
45 |
46 | 47 | {% endblock content %} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # IntelliJ project files 99 | .idea 100 | *.iml 101 | out 102 | gen 103 | 104 | # private settings 105 | settings_private.py 106 | 107 | # sqlite development database 108 | *.sqlite3 109 | 110 | # External data 111 | /msdn/data -------------------------------------------------------------------------------- /msdn/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %}dnhash 8 | 9 | 10 | 11 | {% if GOOGLE_ANALYTICS_CODE %} 12 | 13 | 20 | {% endif %} 21 | 22 | 23 | 41 | {% block content %} 42 | {% endblock content %} 43 |
44 |
45 | ©{% now "Y" %} dnhash. 46 | This site is in no way affiliated with Microsoft Corporation. 47 | 48 | Github 49 | 50 |
51 |
52 | 53 | -------------------------------------------------------------------------------- /msdn/templates/msdn/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}About | {% endblock %} 3 | {% block content %} 4 |
5 |

About

6 | 7 |

8 | In June 2017, the aging MSDN subscriber portal was "taken offline", with the new Visual Studio Subcription portal only being accessible to subscribers. 9 | The old portal was finally completely retired in October 2017, leaving many to wonder where to get information to verify digitally-delivered Microsoft products without a subscription. 10 | This project aims to preserve the once publicly available MSDN hashes for reference purposes and make them easy to look up. 11 |

12 | 13 |

14 | This site uses externally scraped data, which is updated on an ad-hoc basis. 15 | There will be a delay between when new files are released by Microsoft before they show up here.
16 | Additionally, there are certain fields that are not easily filterable with the provided data, e.g. architecture (x86/x64). 17 |

18 | 19 |

FAQ

20 |

Where can I download files?

21 |

22 | This site will not be offering any files for download. 23 | It is simply a reference to ensure that files in your possession were the same versions distributed digitally by Microsoft. 24 |

25 |

Why don't you have product XYZ?

26 |

27 | There may be a couple of reasons for this: 28 |

    29 |
  • The product may have never been delivered digitally
  • 30 |
  • It may be a volume license product which is only offered through VLSC (and cannot be tracked)
  • 31 |
  • Only a specific edition of the product was offered
  • 32 |
33 | Additionally, there cannot be full parity with the Visual Studio Subscriptions portal since the product data sources and schemas are different. 34 |

35 |

Why do some beta products disappear?

36 |

37 | Beta products are removed after the corresponding release version is available. 38 | Unfortunately there may not be a chance to go back and salvage information about these products. 39 |

40 |
41 | {% endblock content %} -------------------------------------------------------------------------------- /msdn/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-06-18 20:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='File', 19 | fields=[ 20 | ('id', models.IntegerField(primary_key=True, serialize=False)), 21 | ('file_name', models.CharField(max_length=1024)), 22 | ('sha1_hash', models.CharField(max_length=40)), 23 | ('description', models.CharField(max_length=1024)), 24 | ('notes', models.TextField(null=True)), 25 | ('posted_date', models.DateTimeField()), 26 | ('size', models.DecimalField(decimal_places=3, max_digits=12)), 27 | ('product_key_required', models.BooleanField()), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='Language', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('code', models.CharField(max_length=4, unique=True)), 35 | ('name', models.CharField(max_length=64)), 36 | ], 37 | ), 38 | migrations.CreateModel( 39 | name='ProductFamily', 40 | fields=[ 41 | ('id', models.IntegerField(primary_key=True, serialize=False)), 42 | ('name', models.CharField(max_length=255)), 43 | ], 44 | ), 45 | migrations.CreateModel( 46 | name='ProductGroup', 47 | fields=[ 48 | ('id', models.IntegerField(primary_key=True, serialize=False)), 49 | ('name', models.CharField(max_length=64)), 50 | ], 51 | ), 52 | migrations.AddField( 53 | model_name='productfamily', 54 | name='group', 55 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='msdn.ProductGroup'), 56 | ), 57 | migrations.AddField( 58 | model_name='file', 59 | name='product_family', 60 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='msdn.ProductFamily'), 61 | ), 62 | ] 63 | -------------------------------------------------------------------------------- /msdn/templates/msdn/file_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}{{file.description}} | {% endblock %} 3 | {% block content %} 4 | 9 |
10 | 20 |
21 |

{{file.description}}

22 |

{{file.file_name}}

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% if file.language is not None %} 37 | 38 | 39 | 40 | 41 | {% endif %} 42 |
SHA1 hash{{file.sha1_hash | lower }}
File size{{file.size | floatformat:'-3'}} MB
Date posted{{file.posted_date | date:'Y-m-d H:i'}}
Language{{file.language.name}}
43 |
44 |
SHA1 hash
45 |
{{file.sha1_hash | lower }}
46 |
File size
47 |
{{file.size | floatformat:'-3'}} MB
48 |
Date posted
49 |
{{file.posted_date | date:'Y-m-d H:i'}}
50 | {% if file.language is not None %} 51 |
Language
52 |
{{file.language.name}}
53 | {% endif %} 54 |
55 | {% if file.notes %} 56 |
57 |
58 |

Notes

59 |
60 |
61 | {{file.notes|safe}} 62 |
63 |
64 | {% endif %} 65 |
66 |
67 | {% endblock content %} -------------------------------------------------------------------------------- /msdn/templates/msdn/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | {% if messages %} 4 |
    5 | {% for message in messages %} 6 |
    7 |
    8 | {{ message | safe }} 9 |
    10 |
    11 | {% endfor %} 12 |
13 | {% endif %} 14 |
15 |
16 |
17 |

18 | {{total_count}} 19 | files indexed from the MSDN subscriptions catalog 20 |

21 |
22 |
23 |

24 | 26 |

27 |

28 | 33 |

34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |

Latest updated products

43 |
    44 | {% for f in latest_updated_families %} 45 |
  • 46 | {{f.name}} 47 |
  • 48 | {% endfor %} 49 |
50 |
51 |
52 |

Browse groups

53 | 65 |
66 |
67 |
68 | {% endblock content %} -------------------------------------------------------------------------------- /msdn/util/grab.py: -------------------------------------------------------------------------------- 1 | import json, os, time 2 | import requests 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | DATA_DIR = os.path.join(BASE_DIR, 'data') 6 | FAMILY_DATA_DIR = os.path.join(DATA_DIR, 'families') 7 | 8 | URL_BASE = "https://msdn.microsoft.com/subscriptions/json" 9 | PAGE_SIZE = 100 10 | DELAY_PER_FAMILY = 0.4 11 | 12 | 13 | def get_categories(): 14 | r = requests.get(URL_BASE + "/GetProductCategories?brand=MSDN&localeCode=en-us") 15 | return r.json() 16 | 17 | 18 | def get_families(category_id): 19 | r = requests.get(URL_BASE + "/GetProductFamiliesForCategory?brand=MSDN&categoryId=%d" % category_id) 20 | return r.json() 21 | 22 | 23 | def get_files(family_id, page_num = 0): 24 | payload = { 25 | 'Brand': "MSDN", 26 | 'ProductFamilyId': family_id, 27 | 'PageSize': PAGE_SIZE, 28 | 'PageIndex': page_num, 29 | 'Architectures': '', 30 | 'Languages': "en,ar,bg,cn,cs,da,de,el,es,et,fi,fr,he,hk,hr,hu,it,ja,ko,lt,lv,nl,no,pl,pp,pt,ro,ru,sk,sl,sr,sv,th,tr,uk" 31 | } 32 | r = requests.post(URL_BASE + "/GetFileSearchResult", payload) 33 | return r.json() 34 | 35 | 36 | def main(): 37 | if not os.path.exists(DATA_DIR): 38 | os.makedirs(DATA_DIR) 39 | 40 | if not os.path.exists(FAMILY_DATA_DIR): 41 | os.makedirs(FAMILY_DATA_DIR) 42 | 43 | base_categories = get_categories() 44 | cf = open(os.path.join(DATA_DIR, 'categories.json'), 'w') 45 | cf.write(json.dumps(base_categories)) 46 | cf.close() 47 | 48 | families = [] 49 | ff = open(os.path.join(DATA_DIR, 'families.json'), 'w') 50 | 51 | for cat in base_categories: 52 | if cat['Name'] != ' New Products': 53 | group_id = cat['ProductGroupId'] 54 | print('Dumping ' + cat['Name']) 55 | 56 | cur_group_families = get_families(group_id) 57 | 58 | for f in cur_group_families: 59 | # Fix for the API returning zero for ProductGroupId 60 | f['ProductGroupId'] = group_id 61 | fam_id = f['ProductFamilyId'] 62 | #print("Dumping %s" % f['Title']) 63 | prf = open(os.path.join(FAMILY_DATA_DIR, '%d.json' % fam_id), 'w') 64 | 65 | page_num = 0 66 | fam_files = [] 67 | 68 | while True: 69 | family_file_search = get_files(fam_id, page_num) 70 | fam_files_curpage = family_file_search['Files'] 71 | fam_files = fam_files + fam_files_curpage 72 | 73 | if not family_file_search['Files'] or family_file_search['TotalResults'] < PAGE_SIZE: 74 | break 75 | page_num += 1 76 | 77 | prf.write(json.dumps(fam_files)) 78 | prf.close() 79 | fam_files = [] 80 | time.sleep(DELAY_PER_FAMILY) 81 | 82 | families += cur_group_families 83 | 84 | ff.write(json.dumps(families)) 85 | ff.close() 86 | 87 | 88 | main() -------------------------------------------------------------------------------- /msdnhash/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for msdnhash project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | from . import settings_private 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = settings_private.secret 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = settings_private.debug 28 | 29 | ALLOWED_HOSTS = settings_private.allowed_hosts 30 | 31 | if hasattr(settings_private, 'google_analytics_code'): 32 | GOOGLE_ANALYTICS_CODE = settings_private.google_analytics_code 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 44 | 'msdn', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'msdnhash.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | 'msdn.context_processors.google_analytics', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'msdnhash.wsgi.application' 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 81 | 82 | DATABASES = { 83 | 'default': settings_private.database 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 108 | 109 | LANGUAGE_CODE = 'en-us' 110 | 111 | TIME_ZONE = 'UTC' 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 122 | 123 | STATIC_URL = '/static/' 124 | -------------------------------------------------------------------------------- /msdn/management/commands/import.py: -------------------------------------------------------------------------------- 1 | import json, os, sys, re 2 | from datetime import datetime 3 | 4 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "msdnhash.settings") 5 | 6 | from django.core.management.base import BaseCommand, CommandError 7 | from django.utils import timezone 8 | from msdn.models import * 9 | 10 | 11 | class Command(BaseCommand): 12 | def handle(self, *args, **options): 13 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 14 | DATA_DIR = os.path.join(BASE_DIR, 'data') 15 | FAMILY_DATA_DIR = os.path.join(DATA_DIR, 'families') 16 | 17 | # English is number one! 18 | en = Language() 19 | en.code = 'en' 20 | en.name = 'English' 21 | en.save() 22 | 23 | categories = [] 24 | 25 | with open(os.path.join(DATA_DIR, 'categories.json')) as cat_file: 26 | j_categories = json.load(cat_file) 27 | 28 | for c in j_categories: 29 | if c['ProductGroupId'] != 65: # New Products 30 | cat_obj = ProductGroup() 31 | cat_obj.id = c['ProductGroupId'] 32 | cat_obj.name = c['Name'] 33 | categories.append(cat_obj) 34 | 35 | ProductGroup.objects.bulk_create(categories) 36 | 37 | families = [] 38 | 39 | with open(os.path.join(DATA_DIR, 'families.json')) as family_file: 40 | j_families = json.load(family_file) 41 | 42 | for fam in j_families: 43 | fam_obj = ProductFamily() 44 | fam_obj.id = fam['ProductFamilyId'] 45 | fam_obj.name = fam['Title'] 46 | fam_obj.group_id = fam['ProductGroupId'] 47 | families.append(fam_obj) 48 | 49 | ProductFamily.objects.bulk_create(families) 50 | 51 | files = [] 52 | imported_file_ids = [] 53 | 54 | for file_listing_fname in [os.path.join(os.path.abspath(FAMILY_DATA_DIR), f) for f in os.listdir(FAMILY_DATA_DIR)]: 55 | with open(file_listing_fname) as file_listing_file: 56 | j_files = json.load(file_listing_file) 57 | 58 | for file in j_files: 59 | if file['FileId'] in imported_file_ids: 60 | continue 61 | 62 | file_obj = File() 63 | file_obj.id = file['FileId'] 64 | file_obj.file_name = file['FileName'] 65 | file_obj.description = file['Description'] 66 | file_obj.notes = file['Notes'] 67 | file_obj.product_family_id = file['ProductFamilyId'] 68 | file_obj.product_key_required = file['IsProductKeyRequired'] 69 | file_obj.sha1_hash = file['Sha1Hash'] 70 | 71 | if file['Languages'] and len(file['Languages']) == 1: 72 | lang_code = file['LanguageCodes'][0] 73 | lang = Language.objects.filter(code=lang_code) 74 | if not lang: 75 | new_lang = Language() 76 | new_lang.code = file['LanguageCodes'][0] 77 | new_lang.name = file['Languages'][0] 78 | new_lang.save() 79 | file_obj.language = new_lang 80 | else: 81 | file_obj.language = lang[0] 82 | 83 | date_str = re.findall(r'\d+', file['PostedDate']) 84 | if len(date_str) > 0: 85 | file_obj.posted_date = datetime.fromtimestamp(int(date_str[0]) / 1000, timezone.utc) 86 | 87 | size = file['Size'][:-3] 88 | file_obj.size = size 89 | 90 | files.append(file_obj) 91 | imported_file_ids.append(file_obj.id) 92 | 93 | File.objects.bulk_create(files) -------------------------------------------------------------------------------- /msdn/templates/msdn/search_result.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Search results | {% endblock %} 3 | {% block content %} 4 | 17 |
18 |
19 | {% if not query %} 20 |

21 | Search through the database by product name, file name, or start of the SHA-1 hash. 22 |

23 | {% elif too_short %} 24 |
25 |
26 |

Query too short

27 |
28 |
29 | Your search query must be at least {{min_length}} characters. 30 |
31 |
32 | {% endif %} 33 | 34 |
35 |
36 |
37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | {% if query and not too_short %} 46 |

Search results

47 | {% if not file_results and not product_results %} 48 |

Nothing found.

49 | {% else %} 50 | 51 | {% if product_results %} 52 |

53 | Products 54 | 55 | {% if too_many_product_results %} 56 |

Showing first 20 results

57 | {% else %} 58 |

Showing {{product_results | length }} results

59 | {% endif %} 60 |
61 |

62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {% for f in product_results %} 73 | 74 | 77 | 80 | 83 | 84 | {% endfor %} 85 | 86 |
Product nameGroup# of files
75 | {{f.name}} 76 | 78 | {{f.group.name}} 79 | 81 | {{f.file__count}} 82 |
87 | 88 | {% endif %} 89 | 90 | {% if file_results %} 91 |

92 | Files 93 | 94 | {% if too_many_file_results %} 95 |

Showing first 100 results

96 | {% else %} 97 |

Showing {{file_results | length}} results

98 | {% endif %} 99 |
100 |

101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | {% for f in file_results %} 112 | 113 | 116 | 119 | 122 | 123 | {% endfor %} 124 | 125 |
ProductFile nameSHA-1 hash
114 | {{f.description}} 115 | 117 | {{f.file_name}} 118 | 120 | {{f.sha1_hash}} 121 |
126 | {% endif %} 127 | {% endif %} 128 | {% endif %} 129 |
130 |
131 | {% endblock content %} -------------------------------------------------------------------------------- /msdn/views.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import string 3 | import json 4 | import os 5 | from django.shortcuts import render, get_object_or_404, redirect 6 | from django.db.models import Max, Count, Q 7 | from django.contrib import messages 8 | 9 | # Create your views here. 10 | from .models import File, Language, ProductGroup, ProductFamily 11 | 12 | 13 | def index(request): 14 | latest_files = File.objects.order_by("-posted_date")[:10] 15 | latest_updated_families = ProductFamily.objects \ 16 | .annotate(last_posted_date=Max('file__posted_date')) \ 17 | .order_by('-last_posted_date')[:10] 18 | 19 | groups = ProductGroup.objects.order_by("name") 20 | total_count = File.objects.count() 21 | 22 | try: 23 | msg_file = open(os.path.dirname(__file__) + '/data/messages.json') 24 | # Input file should be an array of strings 25 | msgs = json.loads(msg_file.read()) 26 | for msg in msgs: 27 | messages.add_message(request, messages.INFO, msg, extra_tags='safe') 28 | except Exception as e: 29 | # No messages 30 | pass 31 | 32 | context = { 33 | 'latest_files': latest_files, 34 | 'latest_updated_families': latest_updated_families, 35 | 'groups': groups, 'total_count': total_count, 36 | } 37 | return render(request, 'msdn/index.html', context) 38 | 39 | 40 | def about(request): 41 | return render(request, 'msdn/about.html') 42 | 43 | 44 | def browse_groups(request): 45 | groups = ProductGroup.objects.annotate(Count('productfamily')).order_by("name") 46 | all_family_count = ProductFamily.objects.count() 47 | 48 | context = {'groups': groups, 'all_family_count': all_family_count} 49 | return render(request, 'msdn/group_list.html', context) 50 | 51 | 52 | def family_list(request): 53 | start_letter = request.GET.get('start_letter') 54 | if start_letter: 55 | first_letter = start_letter[0] 56 | else: 57 | first_letter = 'a' 58 | 59 | families = ProductFamily.objects \ 60 | .prefetch_related('group') \ 61 | .annotate(Count('file')) \ 62 | .order_by('name') 63 | 64 | if first_letter == '#': 65 | families = families.exclude(name__regex=r'^[A-Za-z]') 66 | else: 67 | families = families.filter(name__istartswith=first_letter) 68 | 69 | all_letters = '#' + string.ascii_lowercase 70 | 71 | context = {'families': families, 'first_letter': first_letter, 'all_letters': all_letters} 72 | return render(request, 'msdn/family_list.html', context) 73 | 74 | def group_detail(request, group_id): 75 | group = get_object_or_404(ProductGroup, pk=group_id) 76 | families = ProductFamily.objects.filter(group_id=group_id) \ 77 | .annotate(Count('file'), Max('file__posted_date')) \ 78 | .order_by("name") 79 | 80 | context = {'group': group, 'families': families} 81 | return render(request, 'msdn/group_detail.html', context) 82 | 83 | 84 | def family_detail(request, family_id): 85 | family = get_object_or_404(ProductFamily, pk=family_id) 86 | files = File.objects.filter(product_family_id= family.id).order_by("-posted_date", "description") 87 | file_languages = Language.objects.filter(file__product_family_id= family.id).order_by('name').distinct() 88 | 89 | lang = request.GET.get('lang') 90 | if lang: 91 | files = files.filter(language__code=lang) 92 | 93 | context = { 94 | 'family': family, 95 | 'files': files, 96 | 'file_languages': file_languages, 97 | 'selected_language': lang, 98 | } 99 | return render(request, 'msdn/family_detail.html', context) 100 | 101 | 102 | def file_detail(request, file_id): 103 | file_obj = get_object_or_404(File, pk=file_id) 104 | 105 | context = {'file': file_obj} 106 | return render(request, 'msdn/file_detail.html', context) 107 | 108 | 109 | def search_result(request): 110 | query = request.GET.get('q') 111 | min_query_length = 2 112 | if not query: 113 | return render(request, 'msdn/search_result.html') 114 | elif len(query) < min_query_length: 115 | return render(request, 'msdn/search_result.html', { 116 | 'query': query, 117 | 'too_short': True, 118 | 'min_length': min_query_length}) 119 | else: 120 | products_matching = ProductFamily.objects \ 121 | .filter(name__icontains=query) \ 122 | .annotate(Count('file')) \ 123 | .order_by('name') 124 | 125 | files_matching = File.objects.filter( 126 | Q(sha1_hash__istartswith=query) | 127 | Q(file_name__icontains=query) | 128 | Q(description__icontains=query) 129 | ) 130 | 131 | if len(files_matching) == 1: 132 | return redirect('file_detail', files_matching[0].id) 133 | 134 | too_many_files = files_matching.count() > 100 135 | too_many_products = products_matching.count() > 20 136 | 137 | context = {'file_results': files_matching[:100], 138 | 'product_results': products_matching[:20], 139 | 'too_many_file_results': too_many_files, 140 | 'too_many_product_results': too_many_products, 141 | 'query': query 142 | } 143 | return render(request, 'msdn/search_result.html', context) --------------------------------------------------------------------------------